From f8632e40e729f06a5fb8f1fdd898c60aa13fce38 Mon Sep 17 00:00:00 2001 From: Andrew Zambazos Date: Sat, 23 May 2026 21:19:24 +1200 Subject: [PATCH] Add Qt Bigscreen (QML/CMake), remove Tauri Add a native Qt "Bigscreen" shell: CMakeLists, C++ entry (main.cpp, InputRouter), QML module (Theme, ShellWindow, views and components) and a Bigscreen/.gitignore; update top-level .gitignore and README with Qt build/run instructions. Remove the legacy Tauri/web prototype files (package.json, package-lock.json, src-tauri and many web assets) as part of the migration to the Qt/CMake-based shell. --- .gitignore | 1 + Bigscreen/.gitignore | 1 + Bigscreen/CMakeLists.txt | 69 + Bigscreen/README-Bigscreen.md | 44 +- Bigscreen/package-lock.json | 252 - Bigscreen/package.json | 18 - Bigscreen/qml/Main.qml | 4 + Bigscreen/qml/ShellWindow.qml | 140 + Bigscreen/qml/Theme.qml | 45 + Bigscreen/qml/components/AppTile.qml | 89 + Bigscreen/qml/components/NebulaBackground.qml | 53 + Bigscreen/qml/components/PowerOverlay.qml | 92 + Bigscreen/qml/components/TileRail.qml | 52 + Bigscreen/qml/components/TopBar.qml | 80 + Bigscreen/qml/views/HomeView.qml | 59 + Bigscreen/qml/views/LibraryView.qml | 99 + Bigscreen/qml/views/SettingsView.qml | 120 + Bigscreen/src-tauri/.gitignore | 7 - Bigscreen/src-tauri/Cargo.lock | 5509 ----------------- Bigscreen/src-tauri/Cargo.toml | 27 - Bigscreen/src-tauri/build.rs | 3 - Bigscreen/src-tauri/capabilities/default.json | 10 - Bigscreen/src-tauri/icons/128x128.png | Bin 3512 -> 0 bytes Bigscreen/src-tauri/icons/128x128@2x.png | Bin 7012 -> 0 bytes Bigscreen/src-tauri/icons/32x32.png | Bin 974 -> 0 bytes .../src-tauri/icons/Square107x107Logo.png | Bin 2863 -> 0 bytes .../src-tauri/icons/Square142x142Logo.png | Bin 3858 -> 0 bytes .../src-tauri/icons/Square150x150Logo.png | Bin 3966 -> 0 bytes .../src-tauri/icons/Square284x284Logo.png | Bin 7737 -> 0 bytes Bigscreen/src-tauri/icons/Square30x30Logo.png | Bin 903 -> 0 bytes .../src-tauri/icons/Square310x310Logo.png | Bin 8591 -> 0 bytes Bigscreen/src-tauri/icons/Square44x44Logo.png | Bin 1299 -> 0 bytes Bigscreen/src-tauri/icons/Square71x71Logo.png | Bin 2011 -> 0 bytes Bigscreen/src-tauri/icons/Square89x89Logo.png | Bin 2468 -> 0 bytes Bigscreen/src-tauri/icons/StoreLogo.png | Bin 1523 -> 0 bytes Bigscreen/src-tauri/icons/icon.icns | Bin 98451 -> 0 bytes Bigscreen/src-tauri/icons/icon.ico | Bin 86642 -> 0 bytes Bigscreen/src-tauri/icons/icon.png | Bin 14183 -> 0 bytes Bigscreen/src-tauri/src/lib.rs | 210 - Bigscreen/src-tauri/src/library/commands.rs | 98 - Bigscreen/src-tauri/src/library/db.rs | 480 -- Bigscreen/src-tauri/src/library/images.rs | 114 - .../src-tauri/src/library/metadata/mod.rs | 250 - Bigscreen/src-tauri/src/library/mod.rs | 142 - Bigscreen/src-tauri/src/library/models.rs | 249 - .../src-tauri/src/library/scanners/epic.rs | 118 - .../src-tauri/src/library/scanners/gog.rs | 153 - .../src-tauri/src/library/scanners/local.rs | 146 - .../src-tauri/src/library/scanners/mod.rs | 69 - .../src-tauri/src/library/scanners/steam.rs | 227 - Bigscreen/src-tauri/src/main.rs | 9 - Bigscreen/src-tauri/src/storage.rs | 48 - Bigscreen/src-tauri/tauri.conf.json | 39 - Bigscreen/src/InputRouter.cpp | 49 + Bigscreen/src/InputRouter.h | 33 + Bigscreen/src/assets/javascript.svg | 1 - Bigscreen/src/assets/tauri.svg | 6 - Bigscreen/src/core/gamepadProfile.js | 183 - Bigscreen/src/core/guideSidebar.js | 478 -- Bigscreen/src/core/input.js | 324 - Bigscreen/src/core/nav.js | 451 -- Bigscreen/src/core/passkey.js | 184 - Bigscreen/src/core/router.js | 34 - Bigscreen/src/core/sidebarData.js | 58 - Bigscreen/src/core/state.js | 149 - Bigscreen/src/core/users.js | 105 - Bigscreen/src/index.html | 105 - Bigscreen/src/main.cpp | 99 + Bigscreen/src/main.js | 388 -- Bigscreen/src/styles.css | 112 - Bigscreen/src/styles/base.css | 230 - Bigscreen/src/styles/components.css | 159 - Bigscreen/src/styles/guide.css | 618 -- Bigscreen/src/styles/shell-guidelines.md | 53 - Bigscreen/src/styles/theme.css | 58 - Bigscreen/src/views/home/home.css | 594 -- Bigscreen/src/views/home/home.html | 60 - Bigscreen/src/views/home/home.js | 456 -- Bigscreen/src/views/library/library.css | 901 --- Bigscreen/src/views/library/library.html | 22 - Bigscreen/src/views/library/library.js | 292 - Bigscreen/src/views/library/libraryBridge.js | 124 - .../src/views/library/libraryComponents.js | 440 -- .../src/views/library/libraryController.js | 14 - Bigscreen/src/views/library/libraryFilters.js | 83 - Bigscreen/src/views/library/libraryModel.js | 309 - Bigscreen/src/views/lock/lock.css | 347 -- Bigscreen/src/views/lock/lock.html | 39 - Bigscreen/src/views/lock/lock.js | 473 -- Bigscreen/src/views/onboarding/userSetup.css | 84 - Bigscreen/src/views/onboarding/userSetup.js | 203 - Bigscreen/src/views/overlays/guidePanel.css | 105 - Bigscreen/src/views/overlays/guidePanel.js | 152 - Bigscreen/src/views/overlays/keyboard.css | 95 - Bigscreen/src/views/overlays/keyboard.js | 312 - Bigscreen/src/views/overlays/powerMenu.css | 34 - Bigscreen/src/views/overlays/powerMenu.html | 12 - Bigscreen/src/views/overlays/powerMenu.js | 107 - .../src/views/placeholder/placeholder.js | 50 - Bigscreen/src/views/settings/settings.css | 181 - Bigscreen/src/views/settings/settings.html | 48 - Bigscreen/src/views/settings/settings.js | 228 - 102 files changed, 1123 insertions(+), 17645 deletions(-) create mode 100644 Bigscreen/.gitignore create mode 100644 Bigscreen/CMakeLists.txt delete mode 100644 Bigscreen/package-lock.json delete mode 100644 Bigscreen/package.json create mode 100644 Bigscreen/qml/Main.qml create mode 100644 Bigscreen/qml/ShellWindow.qml create mode 100644 Bigscreen/qml/Theme.qml create mode 100644 Bigscreen/qml/components/AppTile.qml create mode 100644 Bigscreen/qml/components/NebulaBackground.qml create mode 100644 Bigscreen/qml/components/PowerOverlay.qml create mode 100644 Bigscreen/qml/components/TileRail.qml create mode 100644 Bigscreen/qml/components/TopBar.qml create mode 100644 Bigscreen/qml/views/HomeView.qml create mode 100644 Bigscreen/qml/views/LibraryView.qml create mode 100644 Bigscreen/qml/views/SettingsView.qml delete mode 100644 Bigscreen/src-tauri/.gitignore delete mode 100644 Bigscreen/src-tauri/Cargo.lock delete mode 100644 Bigscreen/src-tauri/Cargo.toml delete mode 100644 Bigscreen/src-tauri/build.rs delete mode 100644 Bigscreen/src-tauri/capabilities/default.json delete mode 100644 Bigscreen/src-tauri/icons/128x128.png delete mode 100644 Bigscreen/src-tauri/icons/128x128@2x.png delete mode 100644 Bigscreen/src-tauri/icons/32x32.png delete mode 100644 Bigscreen/src-tauri/icons/Square107x107Logo.png delete mode 100644 Bigscreen/src-tauri/icons/Square142x142Logo.png delete mode 100644 Bigscreen/src-tauri/icons/Square150x150Logo.png delete mode 100644 Bigscreen/src-tauri/icons/Square284x284Logo.png delete mode 100644 Bigscreen/src-tauri/icons/Square30x30Logo.png delete mode 100644 Bigscreen/src-tauri/icons/Square310x310Logo.png delete mode 100644 Bigscreen/src-tauri/icons/Square44x44Logo.png delete mode 100644 Bigscreen/src-tauri/icons/Square71x71Logo.png delete mode 100644 Bigscreen/src-tauri/icons/Square89x89Logo.png delete mode 100644 Bigscreen/src-tauri/icons/StoreLogo.png delete mode 100644 Bigscreen/src-tauri/icons/icon.icns delete mode 100644 Bigscreen/src-tauri/icons/icon.ico delete mode 100644 Bigscreen/src-tauri/icons/icon.png delete mode 100644 Bigscreen/src-tauri/src/lib.rs delete mode 100644 Bigscreen/src-tauri/src/library/commands.rs delete mode 100644 Bigscreen/src-tauri/src/library/db.rs delete mode 100644 Bigscreen/src-tauri/src/library/images.rs delete mode 100644 Bigscreen/src-tauri/src/library/metadata/mod.rs delete mode 100644 Bigscreen/src-tauri/src/library/mod.rs delete mode 100644 Bigscreen/src-tauri/src/library/models.rs delete mode 100644 Bigscreen/src-tauri/src/library/scanners/epic.rs delete mode 100644 Bigscreen/src-tauri/src/library/scanners/gog.rs delete mode 100644 Bigscreen/src-tauri/src/library/scanners/local.rs delete mode 100644 Bigscreen/src-tauri/src/library/scanners/mod.rs delete mode 100644 Bigscreen/src-tauri/src/library/scanners/steam.rs delete mode 100644 Bigscreen/src-tauri/src/main.rs delete mode 100644 Bigscreen/src-tauri/src/storage.rs delete mode 100644 Bigscreen/src-tauri/tauri.conf.json create mode 100644 Bigscreen/src/InputRouter.cpp create mode 100644 Bigscreen/src/InputRouter.h delete mode 100644 Bigscreen/src/assets/javascript.svg delete mode 100644 Bigscreen/src/assets/tauri.svg delete mode 100644 Bigscreen/src/core/gamepadProfile.js delete mode 100644 Bigscreen/src/core/guideSidebar.js delete mode 100644 Bigscreen/src/core/input.js delete mode 100644 Bigscreen/src/core/nav.js delete mode 100644 Bigscreen/src/core/passkey.js delete mode 100644 Bigscreen/src/core/router.js delete mode 100644 Bigscreen/src/core/sidebarData.js delete mode 100644 Bigscreen/src/core/state.js delete mode 100644 Bigscreen/src/core/users.js delete mode 100644 Bigscreen/src/index.html create mode 100644 Bigscreen/src/main.cpp delete mode 100644 Bigscreen/src/main.js delete mode 100644 Bigscreen/src/styles.css delete mode 100644 Bigscreen/src/styles/base.css delete mode 100644 Bigscreen/src/styles/components.css delete mode 100644 Bigscreen/src/styles/guide.css delete mode 100644 Bigscreen/src/styles/shell-guidelines.md delete mode 100644 Bigscreen/src/styles/theme.css delete mode 100644 Bigscreen/src/views/home/home.css delete mode 100644 Bigscreen/src/views/home/home.html delete mode 100644 Bigscreen/src/views/home/home.js delete mode 100644 Bigscreen/src/views/library/library.css delete mode 100644 Bigscreen/src/views/library/library.html delete mode 100644 Bigscreen/src/views/library/library.js delete mode 100644 Bigscreen/src/views/library/libraryBridge.js delete mode 100644 Bigscreen/src/views/library/libraryComponents.js delete mode 100644 Bigscreen/src/views/library/libraryController.js delete mode 100644 Bigscreen/src/views/library/libraryFilters.js delete mode 100644 Bigscreen/src/views/library/libraryModel.js delete mode 100644 Bigscreen/src/views/lock/lock.css delete mode 100644 Bigscreen/src/views/lock/lock.html delete mode 100644 Bigscreen/src/views/lock/lock.js delete mode 100644 Bigscreen/src/views/onboarding/userSetup.css delete mode 100644 Bigscreen/src/views/onboarding/userSetup.js delete mode 100644 Bigscreen/src/views/overlays/guidePanel.css delete mode 100644 Bigscreen/src/views/overlays/guidePanel.js delete mode 100644 Bigscreen/src/views/overlays/keyboard.css delete mode 100644 Bigscreen/src/views/overlays/keyboard.js delete mode 100644 Bigscreen/src/views/overlays/powerMenu.css delete mode 100644 Bigscreen/src/views/overlays/powerMenu.html delete mode 100644 Bigscreen/src/views/overlays/powerMenu.js delete mode 100644 Bigscreen/src/views/placeholder/placeholder.js delete mode 100644 Bigscreen/src/views/settings/settings.css delete mode 100644 Bigscreen/src/views/settings/settings.html delete mode 100644 Bigscreen/src/views/settings/settings.js diff --git a/.gitignore b/.gitignore index a547bf3..14ed924 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ pnpm-debug.log* lerna-debug.log* node_modules +Bigscreen/build/ dist dist-ssr *.local diff --git a/Bigscreen/.gitignore b/Bigscreen/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/Bigscreen/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/Bigscreen/CMakeLists.txt b/Bigscreen/CMakeLists.txt new file mode 100644 index 0000000..9c75757 --- /dev/null +++ b/Bigscreen/CMakeLists.txt @@ -0,0 +1,69 @@ +cmake_minimum_required(VERSION 3.21) + +project(NebulaBigscreen VERSION 0.1.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +find_package(Qt6 6.5 REQUIRED COMPONENTS Quick Gui) + +qt_standard_project_setup(REQUIRES 6.5) +qt_policy(SET QTP0004 NEW) + +set(BIGSCREEN_QML_FILES + qml/Main.qml + qml/ShellWindow.qml + qml/Theme.qml + qml/components/NebulaBackground.qml + qml/components/TopBar.qml + qml/components/AppTile.qml + qml/components/TileRail.qml + qml/components/PowerOverlay.qml + qml/views/HomeView.qml + qml/views/LibraryView.qml + qml/views/SettingsView.qml +) + +set_source_files_properties(qml/Theme.qml PROPERTIES QT_QML_SINGLETON_TYPE TRUE) + +qt_add_executable(nebula-bigscreen + src/main.cpp + src/InputRouter.cpp + src/InputRouter.h +) + +qt_add_qml_module(nebula-bigscreen + URI Nebula.Bigscreen + VERSION 1.0 + QML_FILES ${BIGSCREEN_QML_FILES} +) + +target_link_libraries(nebula-bigscreen + PRIVATE + Qt6::Quick + Qt6::Gui +) + +if(WIN32) + set_target_properties(nebula-bigscreen PROPERTIES WIN32_EXECUTABLE TRUE) + + get_target_property(_qmake_executable Qt6::qmake IMPORTED_LOCATION) + get_filename_component(_qt_bin_dir "${_qmake_executable}" DIRECTORY) + find_program(WINDEPLOYQT_EXECUTABLE windeployqt HINTS "${_qt_bin_dir}" REQUIRED) + + add_custom_command( + TARGET nebula-bigscreen + POST_BUILD + COMMAND "${WINDEPLOYQT_EXECUTABLE}" + $<$:--debug> + $<$>:--release> + --qmldir "${CMAKE_CURRENT_SOURCE_DIR}/qml" + "$" + COMMENT "Deploying Qt runtime DLLs next to nebula-bigscreen" + ) +endif() + +install(TARGETS nebula-bigscreen RUNTIME DESTINATION bin) diff --git a/Bigscreen/README-Bigscreen.md b/Bigscreen/README-Bigscreen.md index 5d36377..feddc27 100644 --- a/Bigscreen/README-Bigscreen.md +++ b/Bigscreen/README-Bigscreen.md @@ -304,15 +304,47 @@ The repo still contains an early **Tauri/HTML v0 prototype** under `Bigscreen/` ## Run -### Qt shell (target) +### Qt shell -Build and run instructions will be added when the Qt project lands in `Bigscreen/`. Expect a standard CMake workflow, for example: +From `Bigscreen/` (Qt 6.5+ with Quick modules installed): ```bash -cmake -S Bigscreen -B build/bigscreen -DCMAKE_BUILD_TYPE=Debug -cmake --build build/bigscreen +cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug +cmake --build build --config Debug ``` +On Windows, the build runs `windeployqt` automatically so Qt DLLs sit next to the executable. If you see missing `Qt6*.dll` errors, rebuild once from `Bigscreen/`: + +```powershell +cmake --build build --config Debug +``` + +From the repo root instead: + +```bash +cmake -S Bigscreen -B Bigscreen/build -DCMAKE_BUILD_TYPE=Debug +cmake --build Bigscreen/build --config Debug +``` + +Run the binary: + +```bash +# Linux (from Bigscreen/) +./build/nebula-bigscreen + +# Windows (from Bigscreen/) +build\Debug\nebula-bigscreen.exe +``` + +To run without redeploying, you can instead put Qt on `PATH` for that session (adjust the kit path if needed): + +```powershell +$env:PATH = "C:\Qt\6.10.2\msvc2022_64\bin;" + $env:PATH +.\build\Debug\nebula-bigscreen.exe +``` + +**Controls (v0):** arrow keys to navigate, Enter to accept, Escape/Backspace for back, Menu key (or Start when mapped later) for the power overlay. Gamepad mapping is planned next. + ### Legacy Tauri prototype To run the existing v0 web prototype while migration is in progress, from `Bigscreen/`: @@ -326,9 +358,9 @@ npm run dev ## Build -### Qt shell (target) +### Qt shell -Release builds will use the Qt/CMake project once it is in the tree. +Same CMake workflow as above with `-DCMAKE_BUILD_TYPE=Release`. ### Legacy Tauri prototype diff --git a/Bigscreen/package-lock.json b/Bigscreen/package-lock.json deleted file mode 100644 index e7e90fe..0000000 --- a/Bigscreen/package-lock.json +++ /dev/null @@ -1,252 +0,0 @@ -{ - "name": "nebula-os", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "nebula-os", - "version": "0.1.0", - "dependencies": { - "@nebulaproject/core": "^0.1.3", - "@tauri-apps/api": "^2.10.1" - }, - "devDependencies": { - "@tauri-apps/cli": "^2" - } - }, - "node_modules/@nebulaproject/core": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@nebulaproject/core/-/core-0.1.3.tgz", - "integrity": "sha512-sOjH10J1qSyqRNNi0yM3GAhkQk6lLGgmPPoyEljGfPC2Ty3iBoY44ML0sfepiiedVtedbkeDPpDcyx/UbVis6g==", - "license": "MIT" - }, - "node_modules/@tauri-apps/api": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.10.1.tgz", - "integrity": "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw==", - "license": "Apache-2.0 OR MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/tauri" - } - }, - "node_modules/@tauri-apps/cli": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.10.0.tgz", - "integrity": "sha512-ZwT0T+7bw4+DPCSWzmviwq5XbXlM0cNoleDKOYPFYqcZqeKY31KlpoMW/MOON/tOFBPgi31a2v3w9gliqwL2+Q==", - "dev": true, - "license": "Apache-2.0 OR MIT", - "bin": { - "tauri": "tauri.js" - }, - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/tauri" - }, - "optionalDependencies": { - "@tauri-apps/cli-darwin-arm64": "2.10.0", - "@tauri-apps/cli-darwin-x64": "2.10.0", - "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.0", - "@tauri-apps/cli-linux-arm64-gnu": "2.10.0", - "@tauri-apps/cli-linux-arm64-musl": "2.10.0", - "@tauri-apps/cli-linux-riscv64-gnu": "2.10.0", - "@tauri-apps/cli-linux-x64-gnu": "2.10.0", - "@tauri-apps/cli-linux-x64-musl": "2.10.0", - "@tauri-apps/cli-win32-arm64-msvc": "2.10.0", - "@tauri-apps/cli-win32-ia32-msvc": "2.10.0", - "@tauri-apps/cli-win32-x64-msvc": "2.10.0" - } - }, - "node_modules/@tauri-apps/cli-darwin-arm64": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.10.0.tgz", - "integrity": "sha512-avqHD4HRjrMamE/7R/kzJPcAJnZs0IIS+1nkDP5b+TNBn3py7N2aIo9LIpy+VQq0AkN8G5dDpZtOOBkmWt/zjA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-darwin-x64": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.10.0.tgz", - "integrity": "sha512-keDmlvJRStzVFjZTd0xYkBONLtgBC9eMTpmXnBXzsHuawV2q9PvDo2x6D5mhuoMVrJ9QWjgaPKBBCFks4dK71Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.10.0.tgz", - "integrity": "sha512-e5u0VfLZsMAC9iHaOEANumgl6lfnJx0Dtjkd8IJpysZ8jp0tJ6wrIkto2OzQgzcYyRCKgX72aKE0PFgZputA8g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-arm64-gnu": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.10.0.tgz", - "integrity": "sha512-YrYYk2dfmBs5m+OIMCrb+JH/oo+4FtlpcrTCgiFYc7vcs6m3QDd1TTyWu0u01ewsCtK2kOdluhr/zKku+KP7HA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-arm64-musl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.10.0.tgz", - "integrity": "sha512-GUoPdVJmrJRIXFfW3Rkt+eGK9ygOdyISACZfC/bCSfOnGt8kNdQIQr5WRH9QUaTVFIwxMlQyV3m+yXYP+xhSVA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.10.0.tgz", - "integrity": "sha512-JO7s3TlSxshwsoKNCDkyvsx5gw2QAs/Y2GbR5UE2d5kkU138ATKoPOtxn8G1fFT1aDW4LH0rYAAfBpGkDyJJnw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-x64-gnu": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.10.0.tgz", - "integrity": "sha512-Uvh4SUUp4A6DVRSMWjelww0GnZI3PlVy7VS+DRF5napKuIehVjGl9XD0uKoCoxwAQBLctvipyEK+pDXpJeoHng==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-x64-musl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.10.0.tgz", - "integrity": "sha512-AP0KRK6bJuTpQ8kMNWvhIpKUkQJfcPFeba7QshOQZjJ8wOS6emwTN4K5g/d3AbCMo0RRdnZWwu67MlmtJyxC1Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-win32-arm64-msvc": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.10.0.tgz", - "integrity": "sha512-97DXVU3dJystrq7W41IX+82JEorLNY+3+ECYxvXWqkq7DBN6FsA08x/EFGE8N/b0LTOui9X2dvpGGoeZKKV08g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-win32-ia32-msvc": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.10.0.tgz", - "integrity": "sha512-EHyQ1iwrWy1CwMalEm9z2a6L5isQ121pe7FcA2xe4VWMJp+GHSDDGvbTv/OPdkt2Lyr7DAZBpZHM6nvlHXEc4A==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-win32-x64-msvc": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.10.0.tgz", - "integrity": "sha512-NTpyQxkpzGmU6ceWBTY2xRIEaS0ZLbVx1HE1zTA3TY/pV3+cPoPPOs+7YScr4IMzXMtOw7tLw5LEXo5oIG3qaQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - } - } -} diff --git a/Bigscreen/package.json b/Bigscreen/package.json deleted file mode 100644 index 3846750..0000000 --- a/Bigscreen/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "nebula-os", - "private": true, - "version": "0.1.0", - "type": "module", - "scripts": { - "dev": "tauri dev", - "build": "tauri build", - "tauri": "tauri" - }, - "devDependencies": { - "@tauri-apps/cli": "^2" - }, - "dependencies": { - "@nebulaproject/core": "^0.1.3", - "@tauri-apps/api": "^2.10.1" - } -} diff --git a/Bigscreen/qml/Main.qml b/Bigscreen/qml/Main.qml new file mode 100644 index 0000000..8659126 --- /dev/null +++ b/Bigscreen/qml/Main.qml @@ -0,0 +1,4 @@ +import QtQuick + +ShellWindow { +} diff --git a/Bigscreen/qml/ShellWindow.qml b/Bigscreen/qml/ShellWindow.qml new file mode 100644 index 0000000..dc5ef9d --- /dev/null +++ b/Bigscreen/qml/ShellWindow.qml @@ -0,0 +1,140 @@ +import QtQuick +import QtQuick.Controls +import Nebula.Bigscreen + +ApplicationWindow { + id: window + + width: 1280 + height: 720 + visible: true + title: "Nebula Bigscreen" + color: Theme.backgroundDeep + + property string currentView: "home" + property bool powerOverlayVisible: false + property string signedInUser: "Player" + + NebulaBackground { + anchors.fill: parent + } + + Column { + anchors.fill: parent + spacing: 0 + + TopBar { + id: topBar + width: parent.width + userName: window.signedInUser + accentFocus: contentLoader.item && contentLoader.item.accentFocus !== undefined + ? contentLoader.item.accentFocus() + : 0 + } + + Loader { + id: contentLoader + width: parent.width + height: parent.height - topBar.height + sourceComponent: viewComponentFor(window.currentView) + } + } + + Connections { + target: contentLoader.item + ignoreUnknownSignals: true + function onNavigate(route) { + window.onChildNavigate(route) + } + function onGoBack() { + window.onChildGoBack() + } + } + + PowerOverlay { + id: powerOverlay + visible: window.powerOverlayVisible + z: 10 + + onDismissed: window.powerOverlayVisible = false + onActionChosen: function(actionId) { + console.log("Power action:", actionId) + window.powerOverlayVisible = false + } + } + + Connections { + target: InputRouter + function onActionTriggered(action) { + if (window.powerOverlayVisible) { + window.handlePowerInput(action) + return + } + if (action === InputRouter.Menu) { + window.powerOverlayVisible = true + return + } + if (contentLoader.item && contentLoader.item.handleInput) { + contentLoader.item.handleInput(action) + } + } + } + + Component { + id: homeComponent + HomeView {} + } + + Component { + id: libraryComponent + LibraryView {} + } + + Component { + id: settingsComponent + SettingsView {} + } + + function viewComponentFor(viewId) { + switch (viewId) { + case "library": + return libraryComponent + case "settings": + return settingsComponent + default: + return homeComponent + } + } + + function onChildNavigate(route) { + if (route === "power") { + window.powerOverlayVisible = true + return + } + window.currentView = route + } + + function onChildGoBack() { + window.currentView = "home" + } + + function handlePowerInput(action) { + switch (action) { + case InputRouter.Up: + powerOverlay.moveFocus(-1) + break + case InputRouter.Down: + powerOverlay.moveFocus(1) + break + case InputRouter.Accept: + powerOverlay.activateFocused() + break + case InputRouter.Back: + window.powerOverlayVisible = false + break + default: + break + } + } + +} diff --git a/Bigscreen/qml/Theme.qml b/Bigscreen/qml/Theme.qml new file mode 100644 index 0000000..ecf1265 --- /dev/null +++ b/Bigscreen/qml/Theme.qml @@ -0,0 +1,45 @@ +pragma Singleton +import QtQuick + +QtObject { + readonly property color backgroundDeep: "#050A17" + readonly property color backgroundMid: "#111321" + readonly property color backgroundPanel: "#181A2E" + + readonly property color accentCyan: "#4FD8FF" + readonly property color accentPurple: "#6F5CFF" + readonly property color accentLine: "#30325A" + + readonly property color textPrimary: "#F2F7FF" + readonly property color textMuted: "#A8BDD8" + + readonly property int tileWidth: 280 + readonly property int tileHeight: 160 + readonly property int tileGap: 20 + readonly property int topBarHeight: 72 + readonly property int focusScaleDuration: 180 + + readonly property real tileFocusScale: 1.06 + readonly property real tilePressScale: 0.96 + + readonly property font brandFont: Qt.font({ + family: "Segoe UI", + pixelSize: 28, + weight: Font.DemiBold + }) + readonly property font titleFont: Qt.font({ + family: "Segoe UI", + pixelSize: 22, + weight: Font.DemiBold + }) + readonly property font bodyFont: Qt.font({ + family: "Segoe UI", + pixelSize: 16, + weight: Font.Normal + }) + readonly property font metaFont: Qt.font({ + family: "Segoe UI", + pixelSize: 13, + weight: Font.Normal + }) +} diff --git a/Bigscreen/qml/components/AppTile.qml b/Bigscreen/qml/components/AppTile.qml new file mode 100644 index 0000000..658b1a4 --- /dev/null +++ b/Bigscreen/qml/components/AppTile.qml @@ -0,0 +1,89 @@ +import QtQuick +import Nebula.Bigscreen + +Item { + id: root + + property string label: "" + property string meta: "" + property string iconText: "" + property bool focused: false + property bool pressed: false + + signal activated() + + implicitWidth: Theme.tileWidth + implicitHeight: Theme.tileHeight + + scale: pressed ? Theme.tilePressScale : (focused ? Theme.tileFocusScale : 1.0) + + Behavior on scale { + NumberAnimation { duration: Theme.focusScaleDuration; easing.type: Easing.OutCubic } + } + + Rectangle { + id: card + anchors.fill: parent + radius: 14 + color: Theme.backgroundPanel + border.width: focused ? 2 : 1 + border.color: focused ? Theme.accentCyan : Theme.accentLine + + Rectangle { + anchors.fill: parent + radius: parent.radius + visible: focused + gradient: Gradient { + GradientStop { position: 0.0; color: "#224FD8FF" } + GradientStop { position: 1.0; color: "transparent" } + } + } + + Column { + anchors.fill: parent + anchors.margins: 20 + spacing: 10 + + Text { + text: root.iconText + font.pixelSize: 36 + } + + Text { + text: root.label + font: Theme.titleFont + color: Theme.textPrimary + } + + Text { + text: root.meta + font: Theme.metaFont + color: Theme.textMuted + } + } + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.margins: 12 + height: 3 + radius: 2 + color: Theme.accentCyan + opacity: focused ? 1.0 : 0.0 + + Behavior on opacity { + NumberAnimation { duration: Theme.focusScaleDuration } + } + } + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onClicked: root.activated() + onPressed: root.pressed = true + onReleased: root.pressed = false + onCanceled: root.pressed = false + } +} diff --git a/Bigscreen/qml/components/NebulaBackground.qml b/Bigscreen/qml/components/NebulaBackground.qml new file mode 100644 index 0000000..82180e8 --- /dev/null +++ b/Bigscreen/qml/components/NebulaBackground.qml @@ -0,0 +1,53 @@ +import QtQuick + +Item { + id: root + anchors.fill: parent + + Rectangle { + anchors.fill: parent + gradient: Gradient { + GradientStop { position: 0.0; color: "#050A17" } + GradientStop { position: 0.55; color: "#0E1630" } + GradientStop { position: 1.0; color: "#1A1342" } + } + } + + Repeater { + model: 48 + Rectangle { + width: (index % 5) + 1 + height: width + radius: width / 2 + color: index % 3 === 0 ? "#88FFFFFF" : "#44B8D4FF" + x: (index * 137) % root.width + y: (index * 89) % root.height + opacity: 0.35 + (index % 7) * 0.05 + + SequentialAnimation on opacity { + loops: Animation.Infinite + running: true + NumberAnimation { + from: 0.2 + to: 0.7 + duration: 2000 + (index % 5) * 400 + } + NumberAnimation { + from: 0.7 + to: 0.2 + duration: 2000 + (index % 5) * 400 + } + } + } + } + + Rectangle { + anchors.fill: parent + opacity: 0.35 + gradient: Gradient { + orientation: Gradient.Vertical + GradientStop { position: 0.0; color: "transparent" } + GradientStop { position: 1.0; color: "#CC000000" } + } + } +} diff --git a/Bigscreen/qml/components/PowerOverlay.qml b/Bigscreen/qml/components/PowerOverlay.qml new file mode 100644 index 0000000..c510269 --- /dev/null +++ b/Bigscreen/qml/components/PowerOverlay.qml @@ -0,0 +1,92 @@ +import QtQuick +import QtQuick.Layouts +import Nebula.Bigscreen + +Rectangle { + id: root + + property int focusIndex: 0 + readonly property var actions: [ + { label: "Sleep", meta: "Suspend system" }, + { label: "Restart", meta: "Reboot NebulaOS" }, + { label: "Shut down", meta: "Power off" }, + { label: "Log out", meta: "Return to login" }, + { label: "Desktop Mode", meta: "Switch session" }, + { label: "Close", meta: "Back to Home" } + ] + + signal dismissed() + signal actionChosen(string actionId) + + anchors.fill: parent + color: "#CC050A17" + + Keys.onPressed: function(event) { event.accepted = true } + + ColumnLayout { + anchors.centerIn: parent + width: Math.min(parent.width - 80, 520) + spacing: 12 + + Text { + text: "Power" + font: Theme.brandFont + color: Theme.textPrimary + Layout.alignment: Qt.AlignHCenter + } + + Repeater { + model: root.actions + + Rectangle { + Layout.fillWidth: true + height: 56 + radius: 10 + color: index === root.focusIndex ? "#22304A66" : Theme.backgroundPanel + border.color: index === root.focusIndex ? Theme.accentCyan : Theme.accentLine + border.width: index === root.focusIndex ? 2 : 1 + + RowLayout { + anchors.fill: parent + anchors.margins: 16 + + Text { + text: modelData.label + font: Theme.titleFont + color: Theme.textPrimary + } + + Item { Layout.fillWidth: true } + + Text { + text: modelData.meta + font: Theme.metaFont + color: Theme.textMuted + } + } + + MouseArea { + anchors.fill: parent + onClicked: { + root.focusIndex = index + root.activateFocused() + } + } + } + } + } + + function moveFocus(delta) { + focusIndex = Math.max(0, Math.min(actions.length - 1, focusIndex + delta)) + } + + function activateFocused() { + const labels = ["sleep", "restart", "shutdown", "logout", "desktop", "close"] + const id = labels[focusIndex] + if (id === "close") { + dismissed() + } else { + actionChosen(id) + } + } +} diff --git a/Bigscreen/qml/components/TileRail.qml b/Bigscreen/qml/components/TileRail.qml new file mode 100644 index 0000000..3433b90 --- /dev/null +++ b/Bigscreen/qml/components/TileRail.qml @@ -0,0 +1,52 @@ +import QtQuick +import Nebula.Bigscreen + +Item { + id: root + + property var tiles: [] + property int focusIndex: 0 + property real accentFocus: 0 + + signal tileActivated(int index) + + implicitHeight: Theme.tileHeight + 24 + + onFocusIndexChanged: root.accentFocus = focusIndex / Math.max(1, tiles.length - 1) + + Row { + id: rail + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: 40 + spacing: Theme.tileGap + + Repeater { + model: root.tiles + + AppTile { + label: modelData.label + meta: modelData.meta + iconText: modelData.icon + focused: index === root.focusIndex + + onActivated: root.tileActivated(index) + } + } + } + + function moveFocus(delta) { + if (tiles.length === 0) { + return + } + const next = Math.max(0, Math.min(tiles.length - 1, focusIndex + delta)) + focusIndex = next + } + + function activateFocused() { + if (tiles.length === 0) { + return + } + tileActivated(focusIndex) + } +} diff --git a/Bigscreen/qml/components/TopBar.qml b/Bigscreen/qml/components/TopBar.qml new file mode 100644 index 0000000..c8abf84 --- /dev/null +++ b/Bigscreen/qml/components/TopBar.qml @@ -0,0 +1,80 @@ +import QtQuick +import QtQuick.Layouts +import Nebula.Bigscreen + +Item { + id: root + + property string userName: "Player" + property real accentFocus: 0 + + implicitHeight: Theme.topBarHeight + + RowLayout { + anchors.fill: parent + anchors.leftMargin: 40 + anchors.rightMargin: 40 + spacing: 16 + + Text { + text: "Nebula OS" + font: Theme.brandFont + color: Theme.textPrimary + style: Text.Outline + styleColor: Theme.accentCyan + Layout.alignment: Qt.AlignVCenter + } + + Item { Layout.fillWidth: true } + + RowLayout { + spacing: 14 + Layout.alignment: Qt.AlignVCenter + + Rectangle { + width: 36 + height: 36 + radius: 18 + color: Theme.backgroundPanel + border.color: Theme.accentCyan + border.width: 2 + + Text { + anchors.centerIn: parent + text: root.userName.length > 0 ? root.userName.charAt(0).toUpperCase() : "?" + font: Theme.titleFont + color: Theme.textPrimary + } + } + + Text { + id: clockLabel + font: Theme.bodyFont + color: Theme.textMuted + } + } + } + + Rectangle { + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.leftMargin: 40 + root.accentFocus * 320 + width: 120 + height: 2 + radius: 1 + color: Theme.accentCyan + opacity: 0.85 + + Behavior on anchors.leftMargin { + NumberAnimation { duration: Theme.focusScaleDuration; easing.type: Easing.OutCubic } + } + } + + Timer { + interval: 1000 + running: true + repeat: true + triggeredOnStart: true + onTriggered: clockLabel.text = Qt.formatTime(new Date(), "hh:mm AP") + } +} diff --git a/Bigscreen/qml/views/HomeView.qml b/Bigscreen/qml/views/HomeView.qml new file mode 100644 index 0000000..71b8234 --- /dev/null +++ b/Bigscreen/qml/views/HomeView.qml @@ -0,0 +1,59 @@ +import QtQuick +import QtQuick.Layouts +import Nebula.Bigscreen + +ColumnLayout { + id: root + + property var homeTiles: [ + { label: "Library", meta: "Games and apps", icon: "📚", route: "library" }, + { label: "Settings", meta: "System controls", icon: "⚙", route: "settings" }, + { label: "Power", meta: "Sleep and sessions", icon: "⏻", route: "power" } + ] + + signal navigate(string route) + + spacing: 28 + + Text { + text: "Home" + font: Theme.brandFont + color: Theme.textPrimary + Layout.leftMargin: 40 + } + + TileRail { + id: rail + Layout.fillWidth: true + tiles: root.homeTiles + + onTileActivated: function(index) { + const route = root.homeTiles[index].route + if (route === "power") { + root.navigate("power") + } else { + root.navigate(route) + } + } + } + + function handleInput(action) { + switch (action) { + case InputRouter.Left: + rail.moveFocus(-1) + break + case InputRouter.Right: + rail.moveFocus(1) + break + case InputRouter.Accept: + rail.activateFocused() + break + default: + break + } + } + + function accentFocus() { + return rail.accentFocus + } +} diff --git a/Bigscreen/qml/views/LibraryView.qml b/Bigscreen/qml/views/LibraryView.qml new file mode 100644 index 0000000..876de64 --- /dev/null +++ b/Bigscreen/qml/views/LibraryView.qml @@ -0,0 +1,99 @@ +import QtQuick +import QtQuick.Layouts +import Nebula.Bigscreen + +ColumnLayout { + id: root + + signal goBack() + + readonly property var entries: [ + { title: "Nebula Demo", meta: "Local • Ready", icon: "🎮" }, + { title: "Retro Runner", meta: "Wine • Installed", icon: "👾" }, + { title: "Media Hub", meta: "App • Installed", icon: "🎬" } + ] + + property int focusIndex: 0 + + spacing: 20 + + Text { + text: "Library" + font: Theme.brandFont + color: Theme.textPrimary + Layout.leftMargin: 40 + } + + Text { + text: "Mock entries for v0 — scanners and launchers come later." + font: Theme.metaFont + color: Theme.textMuted + Layout.leftMargin: 40 + Layout.bottomMargin: 8 + } + + ListView { + id: list + Layout.fillWidth: true + Layout.fillHeight: true + Layout.leftMargin: 40 + Layout.rightMargin: 40 + clip: true + spacing: 12 + model: root.entries + + delegate: Rectangle { + width: list.width + height: 72 + radius: 12 + color: index === root.focusIndex ? "#22304A66" : Theme.backgroundPanel + border.color: index === root.focusIndex ? Theme.accentCyan : Theme.accentLine + border.width: index === root.focusIndex ? 2 : 1 + + RowLayout { + anchors.fill: parent + anchors.margins: 16 + spacing: 16 + + Text { + text: modelData.icon + font.pixelSize: 28 + } + + ColumnLayout { + spacing: 2 + Text { + text: modelData.title + font: Theme.titleFont + color: Theme.textPrimary + } + Text { + text: modelData.meta + font: Theme.metaFont + color: Theme.textMuted + } + } + } + } + } + + function handleInput(action) { + switch (action) { + case InputRouter.Up: + focusIndex = Math.max(0, focusIndex - 1) + list.currentIndex = focusIndex + break + case InputRouter.Down: + focusIndex = Math.min(entries.length - 1, focusIndex + 1) + list.currentIndex = focusIndex + break + case InputRouter.Back: + root.goBack() + break + case InputRouter.Accept: + break + default: + break + } + } +} diff --git a/Bigscreen/qml/views/SettingsView.qml b/Bigscreen/qml/views/SettingsView.qml new file mode 100644 index 0000000..f165e6e --- /dev/null +++ b/Bigscreen/qml/views/SettingsView.qml @@ -0,0 +1,120 @@ +import QtQuick +import QtQuick.Layouts +import Nebula.Bigscreen + +ColumnLayout { + id: root + + signal goBack() + + readonly property var categories: [ + "Network", + "Audio", + "Display", + "Controller", + "System" + ] + + property int categoryIndex: 0 + property int itemIndex: 0 + + spacing: 20 + + Text { + text: "Settings" + font: Theme.brandFont + color: Theme.textPrimary + Layout.leftMargin: 40 + } + + Row { + Layout.leftMargin: 40 + spacing: 16 + + Repeater { + model: root.categories + + Rectangle { + height: 40 + width: categoryLabel.implicitWidth + 24 + radius: 8 + color: index === root.categoryIndex ? "#22304A66" : "transparent" + border.color: index === root.categoryIndex ? Theme.accentCyan : "transparent" + border.width: index === root.categoryIndex ? 2 : 0 + + Text { + id: categoryLabel + anchors.centerIn: parent + text: modelData + font: Theme.bodyFont + color: index === root.categoryIndex ? Theme.accentCyan : Theme.textMuted + } + } + } + } + + Rectangle { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.margins: 40 + radius: 16 + color: Theme.backgroundPanel + border.color: Theme.accentLine + + ColumnLayout { + anchors.fill: parent + anchors.margins: 24 + spacing: 12 + + Text { + text: root.categories[root.categoryIndex] + font: Theme.titleFont + color: Theme.textPrimary + } + + Repeater { + model: 3 + + Rectangle { + Layout.fillWidth: true + height: 52 + radius: 10 + color: index === root.itemIndex ? "#22304A66" : Theme.backgroundMid + border.color: index === root.itemIndex ? Theme.accentCyan : Theme.accentLine + border.width: index === root.itemIndex ? 2 : 1 + + Text { + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: 16 + text: "Placeholder setting " + (index + 1) + font: Theme.bodyFont + color: Theme.textPrimary + } + } + } + } + } + + function handleInput(action) { + switch (action) { + case InputRouter.Left: + categoryIndex = Math.max(0, categoryIndex - 1) + break + case InputRouter.Right: + categoryIndex = Math.min(categories.length - 1, categoryIndex + 1) + break + case InputRouter.Up: + itemIndex = Math.max(0, itemIndex - 1) + break + case InputRouter.Down: + itemIndex = Math.min(2, itemIndex + 1) + break + case InputRouter.Back: + root.goBack() + break + default: + break + } + } +} diff --git a/Bigscreen/src-tauri/.gitignore b/Bigscreen/src-tauri/.gitignore deleted file mode 100644 index b21bd68..0000000 --- a/Bigscreen/src-tauri/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ - -# Generated by Tauri -# will have schema files for capabilities auto-completion -/gen/schemas diff --git a/Bigscreen/src-tauri/Cargo.lock b/Bigscreen/src-tauri/Cargo.lock deleted file mode 100644 index 88dc581..0000000 --- a/Bigscreen/src-tauri/Cargo.lock +++ /dev/null @@ -1,5509 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[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.101" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" - -[[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-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.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" -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.115", -] - -[[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" - -[[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.115", -] - -[[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" - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[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 = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" -dependencies = [ - "serde_core", -] - -[[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.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" -dependencies = [ - "objc2", -] - -[[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 = "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 = "bumpalo" -version = "3.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" - -[[package]] -name = "bytemuck" -version = "1.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" -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.11.0", - "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.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" -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.18", -] - -[[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.12+spec-1.1.0", -] - -[[package]] -name = "cc" -version = "1.2.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" -dependencies = [ - "find-msvc-tools", - "shlex", -] - -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - -[[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.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "chrono" -version = "0.4.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" -dependencies = [ - "iana-time-zone", - "num-traits", - "serde", - "windows-link 0.2.1", -] - -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[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.11.0", - "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.11.0", - "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 = "crossbeam-channel" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -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 = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -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.115", -] - -[[package]] -name = "ctor" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" -dependencies = [ - "quote", - "syn 2.0.115", -] - -[[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.115", -] - -[[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.115", -] - -[[package]] -name = "deranged" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" -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.115", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "dirs" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" -dependencies = [ - "dirs-sys", -] - -[[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", - "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.11.0", - "objc2", -] - -[[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.115", -] - -[[package]] -name = "dlopen2" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" -dependencies = [ - "dlopen2_derive", - "libc", - "once_cell", - "winapi", -] - -[[package]] -name = "dlopen2_derive" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[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.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" - -[[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 = "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.12+spec-1.1.0", - "vswhom", - "winreg", -] - -[[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.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" - -[[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.115", -] - -[[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.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3" -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 = "fallible-iterator" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" - -[[package]] -name = "fallible-streaming-iterator" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[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.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" - -[[package]] -name = "flate2" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[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.115", -] - -[[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 = "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-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", -] - -[[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.115", -] - -[[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-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 = "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.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasip2", -] - -[[package]] -name = "getrandom" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasip2", - "wasip3", -] - -[[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 = "glib" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" -dependencies = [ - "bitflags 2.11.0", - "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.2", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[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 = "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.115", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", -] - -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash", -] - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" - -[[package]] -name = "hashlink" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" -dependencies = [ - "hashbrown 0.14.5", -] - -[[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 = "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.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" -dependencies = [ - "bytes", - "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 = "http-range" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "hyper" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "pin-utils", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-util" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" -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.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" -dependencies = [ - "byteorder", - "png", -] - -[[package]] -name = "icu_collections" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" -dependencies = [ - "displaydoc", - "potential_utf", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" - -[[package]] -name = "icu_properties" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" - -[[package]] -name = "icu_provider" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - -[[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 = "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.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" -dependencies = [ - "equivalent", - "hashbrown 0.16.1", - "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 = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - -[[package]] -name = "iri-string" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" -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 = "itoa" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" - -[[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 = "js-sys" -version = "0.3.85" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" -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.11.0", - "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.13.0", - "selectors", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - -[[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", - "once_cell", -] - -[[package]] -name = "libc" -version = "0.2.182" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" - -[[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 = "libredox" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" -dependencies = [ - "bitflags 2.11.0", - "libc", -] - -[[package]] -name = "libsqlite3-sys" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - -[[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.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" - -[[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.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" - -[[package]] -name = "mac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" - -[[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.115", -] - -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - -[[package]] -name = "memchr" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" - -[[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 = "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.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" -dependencies = [ - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.61.2", -] - -[[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", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "once_cell", - "png", - "serde", - "thiserror 2.0.18", - "windows-sys 0.60.2", -] - -[[package]] -name = "ndk" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" -dependencies = [ - "bitflags 2.11.0", - "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 = "nebula-os" -version = "0.1.0" -dependencies = [ - "rusqlite", - "serde", - "serde_json", - "tauri", - "tauri-build", - "tauri-plugin-opener", - "ureq", -] - -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" - -[[package]] -name = "num-conv" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" - -[[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.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" -dependencies = [ - "num_enum_derive", - "rustversion", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" -dependencies = [ - "proc-macro-crate 3.4.0", - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[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.11.0", - "block2", - "libc", - "objc2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-core-image", - "objc2-core-text", - "objc2-core-video", - "objc2-foundation", - "objc2-quartz-core", -] - -[[package]] -name = "objc2-cloud-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" -dependencies = [ - "bitflags 2.11.0", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-data" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" -dependencies = [ - "bitflags 2.11.0", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" -dependencies = [ - "bitflags 2.11.0", - "dispatch2", - "objc2", -] - -[[package]] -name = "objc2-core-graphics" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" -dependencies = [ - "bitflags 2.11.0", - "dispatch2", - "objc2", - "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", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-text" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" -dependencies = [ - "bitflags 2.11.0", - "objc2", - "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.11.0", - "objc2", - "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.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" -dependencies = [ - "bitflags 2.11.0", - "block2", - "libc", - "objc2", - "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.11.0", - "objc2", - "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", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" -dependencies = [ - "bitflags 2.11.0", - "objc2", - "objc2-core-foundation", - "objc2-foundation", -] - -[[package]] -name = "objc2-security" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a" -dependencies = [ - "bitflags 2.11.0", - "objc2", - "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.11.0", - "objc2", - "objc2-core-foundation", - "objc2-foundation", -] - -[[package]] -name = "objc2-web-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" -dependencies = [ - "bitflags 2.11.0", - "block2", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "objc2-javascript-core", - "objc2-security", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "open" -version = "5.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43bb73a7fa3799b198970490a51174027ba0d4ec504b03cd08caf513d40024bc" -dependencies = [ - "dunce", - "is-wsl", - "libc", - "pathdiff", -] - -[[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 = "pathdiff" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" - -[[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.115", -] - -[[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.2", -] - -[[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.13.0", - "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 = "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 = "potential_utf" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" -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.115", -] - -[[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.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" -dependencies = [ - "toml_datetime 0.6.3", - "toml_edit 0.20.2", -] - -[[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.10+spec-1.0.0", -] - -[[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.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quick-xml" -version = "0.38.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" -dependencies = [ - "memchr", -] - -[[package]] -name = "quote" -version = "1.0.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" -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_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_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.17", -] - -[[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 = "raw-window-handle" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags 2.11.0", -] - -[[package]] -name = "redox_users" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" -dependencies = [ - "getrandom 0.2.17", - "libredox", - "thiserror 2.0.18", -] - -[[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.115", -] - -[[package]] -name = "regex" -version = "1.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" - -[[package]] -name = "reqwest" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "js-sys", - "log", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "sync_wrapper", - "tokio", - "tokio-util", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", -] - -[[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.17", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rusqlite" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae" -dependencies = [ - "bitflags 2.11.0", - "fallible-iterator", - "fallible-streaming-iterator", - "hashlink", - "libsqlite3-sys", - "smallvec", -] - -[[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.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" -dependencies = [ - "bitflags 2.11.0", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls" -version = "0.23.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" -dependencies = [ - "log", - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pki-types" -version = "1.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" -dependencies = [ - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" -dependencies = [ - "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 = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[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.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" -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.115", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[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", - "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_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.115", -] - -[[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.115", -] - -[[package]] -name = "serde_json" -version = "1.0.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[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.115", -] - -[[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.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" -dependencies = [ - "serde_core", -] - -[[package]] -name = "serde_with" -version = "3.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" -dependencies = [ - "base64 0.22.1", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.13.0", - "schemars 0.9.0", - "schemars 1.2.1", - "serde_core", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[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.115", -] - -[[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 = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[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.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" -dependencies = [ - "errno", - "libc", -] - -[[package]] -name = "simd-adler32" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" - -[[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.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "socket2" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" -dependencies = [ - "libc", - "windows-sys 0.60.2", -] - -[[package]] -name = "softbuffer" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" -dependencies = [ - "bytemuck", - "js-sys", - "ndk", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation", - "objc2-quartz-core", - "raw-window-handle", - "redox_syscall", - "tracing", - "wasm-bindgen", - "web-sys", - "windows-sys 0.61.2", -] - -[[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 = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[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.115" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" -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.115", -] - -[[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.2", - "version-compare", -] - -[[package]] -name = "tao" -version = "0.34.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a753bdc39c07b192151523a3f77cd0394aa75413802c883a0f6f6a0e5ee2e7" -dependencies = [ - "bitflags 2.11.0", - "block2", - "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", - "objc2-app-kit", - "objc2-foundation", - "once_cell", - "parking_lot", - "raw-window-handle", - "scopeguard", - "tao-macros", - "unicode-segmentation", - "url", - "windows", - "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.115", -] - -[[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.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463ae8677aa6d0f063a900b9c41ecd4ac2b7ca82f0b058cc4491540e55b20129" -dependencies = [ - "anyhow", - "bytes", - "cookie", - "dirs", - "dunce", - "embed_plist", - "getrandom 0.3.4", - "glob", - "gtk", - "heck 0.5.0", - "http", - "http-range", - "jni", - "libc", - "log", - "mime", - "muda", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "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.18", - "tokio", - "tray-icon", - "url", - "webkit2gtk", - "webview2-com", - "window-vibrancy", - "windows", -] - -[[package]] -name = "tauri-build" -version = "2.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca7bd893329425df750813e95bd2b643d5369d929438da96d5bbb7cc2c918f74" -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.12+spec-1.1.0", - "walkdir", -] - -[[package]] -name = "tauri-codegen" -version = "2.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac423e5859d9f9ccdd32e3cf6a5866a15bedbf25aa6630bcb2acde9468f6ae3" -dependencies = [ - "base64 0.22.1", - "brotli", - "ico", - "json-patch", - "plist", - "png", - "proc-macro2", - "quote", - "semver", - "serde", - "serde_json", - "sha2", - "syn 2.0.115", - "tauri-utils", - "thiserror 2.0.18", - "time", - "url", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-macros" -version = "2.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b6a1bd2861ff0c8766b1d38b32a6a410f6dc6532d4ef534c47cfb2236092f59" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.115", - "tauri-codegen", - "tauri-utils", -] - -[[package]] -name = "tauri-plugin" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692a77abd8b8773e107a42ec0e05b767b8d2b7ece76ab36c6c3947e34df9f53f" -dependencies = [ - "anyhow", - "glob", - "plist", - "schemars 0.8.22", - "serde", - "serde_json", - "tauri-utils", - "toml 0.9.12+spec-1.1.0", - "walkdir", -] - -[[package]] -name = "tauri-plugin-opener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc624469b06f59f5a29f874bbc61a2ed737c0f9c23ef09855a292c389c42e83f" -dependencies = [ - "dunce", - "glob", - "objc2-app-kit", - "objc2-foundation", - "open", - "schemars 0.8.22", - "serde", - "serde_json", - "tauri", - "tauri-plugin", - "thiserror 2.0.18", - "url", - "windows", - "zbus", -] - -[[package]] -name = "tauri-runtime" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b885ffeac82b00f1f6fd292b6e5aabfa7435d537cef57d11e38a489956535651" -dependencies = [ - "cookie", - "dpi", - "gtk", - "http", - "jni", - "objc2", - "objc2-ui-kit", - "objc2-web-kit", - "raw-window-handle", - "serde", - "serde_json", - "tauri-utils", - "thiserror 2.0.18", - "url", - "webkit2gtk", - "webview2-com", - "windows", -] - -[[package]] -name = "tauri-runtime-wry" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5204682391625e867d16584fedc83fc292fb998814c9f7918605c789cd876314" -dependencies = [ - "gtk", - "http", - "jni", - "log", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "once_cell", - "percent-encoding", - "raw-window-handle", - "softbuffer", - "tao", - "tauri-runtime", - "tauri-utils", - "url", - "webkit2gtk", - "webview2-com", - "windows", - "wry", -] - -[[package]] -name = "tauri-utils" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcd169fccdff05eff2c1033210b9b94acd07a47e6fa9a3431cf09cfd4f01c87e" -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.18", - "toml 0.9.12+spec-1.1.0", - "url", - "urlpattern", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-winres" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1087b111fe2b005e42dbdc1990fc18593234238d47453b0c99b7de1c9ab2c1e0" -dependencies = [ - "dunce", - "embed-resource", - "toml 0.9.12+spec-1.1.0", -] - -[[package]] -name = "tempfile" -version = "3.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" -dependencies = [ - "fastrand", - "getrandom 0.4.1", - "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.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" -dependencies = [ - "thiserror-impl 2.0.18", -] - -[[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.115", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "time" -version = "0.3.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" - -[[package]] -name = "time-macros" -version = "0.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tinystr" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tokio" -version = "1.49.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" -dependencies = [ - "bytes", - "libc", - "mio", - "pin-project-lite", - "socket2", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-util" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" -dependencies = [ - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.3", - "toml_edit 0.20.2", -] - -[[package]] -name = "toml" -version = "0.9.12+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" -dependencies = [ - "indexmap 2.13.0", - "serde_core", - "serde_spanned 1.0.4", - "toml_datetime 0.7.5+spec-1.1.0", - "toml_parser", - "toml_writer", - "winnow 0.7.14", -] - -[[package]] -name = "toml_datetime" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_datetime" -version = "0.7.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" -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.13.0", - "toml_datetime 0.6.3", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" -dependencies = [ - "indexmap 2.13.0", - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.3", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.23.10+spec-1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" -dependencies = [ - "indexmap 2.13.0", - "toml_datetime 0.7.5+spec-1.1.0", - "toml_parser", - "winnow 0.7.14", -] - -[[package]] -name = "toml_parser" -version = "1.0.8+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc" -dependencies = [ - "winnow 0.7.14", -] - -[[package]] -name = "toml_writer" -version = "1.0.6+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" - -[[package]] -name = "tower" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" -dependencies = [ - "bitflags 2.11.0", - "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.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "tray-icon" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e85aa143ceb072062fc4d6356c1b520a51d636e7bc8e77ec94be3608e5e80c" -dependencies = [ - "crossbeam-channel", - "dirs", - "libappindicator", - "muda", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation", - "once_cell", - "png", - "serde", - "thiserror 2.0.18", - "windows-sys 0.60.2", -] - -[[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.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" - -[[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 = "ureq" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" -dependencies = [ - "base64 0.22.1", - "flate2", - "log", - "percent-encoding", - "rustls", - "rustls-pki-types", - "ureq-proto", - "utf8-zero", - "webpki-roots", -] - -[[package]] -name = "ureq-proto" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c" -dependencies = [ - "base64 0.22.1", - "http", - "httparse", - "log", -] - -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", - "serde_derive", -] - -[[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-zero" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" - -[[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.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" -dependencies = [ - "getrandom 0.4.1", - "js-sys", - "serde_core", - "wasm-bindgen", -] - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version-compare" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[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 = "wasip2" -version = "1.0.2+wasi-0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" -dependencies = [ - "cfg-if", - "futures-util", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn 2.0.115", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap 2.13.0", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasm-streams" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.11.0", - "hashbrown 0.15.5", - "indexmap 2.13.0", - "semver", -] - -[[package]] -name = "web-sys" -version = "0.3.85" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webkit2gtk" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793" -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.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5" -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-roots" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "webview2-com" -version = "0.38.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" -dependencies = [ - "webview2-com-macros", - "webview2-com-sys", - "windows", - "windows-core 0.61.2", - "windows-implement", - "windows-interface", -] - -[[package]] -name = "webview2-com-macros" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "webview2-com-sys" -version = "0.38.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" -dependencies = [ - "thiserror 2.0.18", - "windows", - "windows-core 0.61.2", -] - -[[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", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "raw-window-handle", - "windows-sys 0.59.0", - "windows-version", -] - -[[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.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "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", - "windows-interface", - "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.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[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.115", -] - -[[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.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.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.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.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.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.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.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.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.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.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.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" -dependencies = [ - "memchr", -] - -[[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.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck 0.5.0", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck 0.5.0", - "indexmap 2.13.0", - "prettyplease", - "syn 2.0.115", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn 2.0.115", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.11.0", - "indexmap 2.13.0", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap 2.13.0", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - -[[package]] -name = "writeable" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" - -[[package]] -name = "wry" -version = "0.54.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb26159b420aa77684589a744ae9a9461a95395b848764ad12290a14d960a11a" -dependencies = [ - "base64 0.22.1", - "block2", - "cookie", - "crossbeam-channel", - "dirs", - "dpi", - "dunce", - "gdkx11", - "gtk", - "html5ever", - "http", - "javascriptcore-rs", - "jni", - "kuchikiki", - "libc", - "ndk", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "objc2-ui-kit", - "objc2-web-kit", - "once_cell", - "percent-encoding", - "raw-window-handle", - "sha2", - "soup3", - "tao-macros", - "thiserror 2.0.18", - "url", - "webkit2gtk", - "webkit2gtk-sys", - "webview2-com", - "windows", - "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.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", - "synstructure", -] - -[[package]] -name = "zbus" -version = "5.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfeff997a0aaa3eb20c4652baf788d2dfa6d2839a0ead0b3ff69ce2f9c4bdd1" -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", - "libc", - "ordered-stream", - "rustix", - "serde", - "serde_repr", - "tracing", - "uds_windows", - "uuid", - "windows-sys 0.61.2", - "winnow 0.7.14", - "zbus_macros", - "zbus_names", - "zvariant", -] - -[[package]] -name = "zbus_macros" -version = "5.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bbd5a90dbe8feee5b13def448427ae314ccd26a49cac47905cafefb9ff846f1" -dependencies = [ - "proc-macro-crate 3.4.0", - "proc-macro2", - "quote", - "syn 2.0.115", - "zbus_names", - "zvariant", - "zvariant_utils", -] - -[[package]] -name = "zbus_names" -version = "4.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" -dependencies = [ - "serde", - "winnow 0.7.14", - "zvariant", -] - -[[package]] -name = "zerocopy" -version = "0.8.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[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.115", - "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.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" - -[[package]] -name = "zvariant" -version = "5.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b64ef4f40c7951337ddc7023dd03528a57a3ce3408ee9da5e948bd29b232c4" -dependencies = [ - "endi", - "enumflags2", - "serde", - "winnow 0.7.14", - "zvariant_derive", - "zvariant_utils", -] - -[[package]] -name = "zvariant_derive" -version = "5.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "484d5d975eb7afb52cc6b929c13d3719a20ad650fea4120e6310de3fc55e415c" -dependencies = [ - "proc-macro-crate 3.4.0", - "proc-macro2", - "quote", - "syn 2.0.115", - "zvariant_utils", -] - -[[package]] -name = "zvariant_utils" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "syn 2.0.115", - "winnow 0.7.14", -] diff --git a/Bigscreen/src-tauri/Cargo.toml b/Bigscreen/src-tauri/Cargo.toml deleted file mode 100644 index 5d207b4..0000000 --- a/Bigscreen/src-tauri/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "nebula-os" -version = "0.1.0" -description = "A Tauri App" -authors = ["you"] -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -# The `_lib` suffix may seem redundant but it is necessary -# to make the lib name unique and wouldn't conflict with the bin name. -# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519 -name = "nebula_os_lib" -crate-type = ["staticlib", "cdylib", "rlib"] - -[build-dependencies] -tauri-build = { version = "2", features = [] } - -[dependencies] -tauri = { version = "2", features = ["protocol-asset"] } -tauri-plugin-opener = "2" -serde = { version = "1", features = ["derive"] } -serde_json = "1" -rusqlite = { version = "0.31", features = ["bundled"] } -ureq = "3.3.0" - diff --git a/Bigscreen/src-tauri/build.rs b/Bigscreen/src-tauri/build.rs deleted file mode 100644 index d860e1e..0000000 --- a/Bigscreen/src-tauri/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - tauri_build::build() -} diff --git a/Bigscreen/src-tauri/capabilities/default.json b/Bigscreen/src-tauri/capabilities/default.json deleted file mode 100644 index 4cdbf49..0000000 --- a/Bigscreen/src-tauri/capabilities/default.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "$schema": "../gen/schemas/desktop-schema.json", - "identifier": "default", - "description": "Capability for the main window", - "windows": ["main"], - "permissions": [ - "core:default", - "opener:default" - ] -} diff --git a/Bigscreen/src-tauri/icons/128x128.png b/Bigscreen/src-tauri/icons/128x128.png deleted file mode 100644 index 6be5e50e9b9ae84d9e2ee433f32ef446495eaf3b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3512 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 diff --git a/Bigscreen/src-tauri/icons/128x128@2x.png b/Bigscreen/src-tauri/icons/128x128@2x.png deleted file mode 100644 index e81becee571e96f76aa5667f9324c05e5e7a4479..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7012 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 Sh@|Ye~|0>}wMPLT8jm@Y!H33Sz}5aFI6 zM9Lzqz|;A*0sGs=2A1uU!1nk2dGF7knQwr99SAFen)x(eCO;F8y2C~0FD1YxRTPcy zPWVxkUYmeuz}Tv?7&Fe-!UE{)ZW)Mb;H)^#eHDv$`dkZGguJz@^MA!ZNGAUqt{|0H zpZ7Ch9S`q5!>R%}>}62!+(T^evyO+ImSo2wpu)su4^3nw5(%)KD%gbSev^*HZZ&3( z#&c@Z0gH|}Ck)w6fh0&NBJ62ib%R}(3@$VFl*_#l2W$wQ-~4RmZZAt5O*^2Q5}Xr8Hy@c`#pM?kc?hFWxRXr*mUfUCXf4ka5DD~ zat6d85COB05l#(P9*cQZ3EC8fVdS~?&vN#rce(aF9@xp80O2{{FBvU+{X>Hoh;xI` z{$e^Nw1y*VbO8wv`8|-m?NwNaKGTGaF{P^JLB^DbOYWIbn%eT`*!^C1H36=O8Z-M> zkD~88ry`eSo`tEBN4>w7OWZwUzlh{WM1m8R6zepqGcGMaV7vWY9b?K4b6~|HVG)ec wi>I@ws#sZo7or4_*4M>7;p5{nr2pZ?Uu4>Krr0kU)&Kwi07*qoM6N<$f)&@lf&c&j diff --git a/Bigscreen/src-tauri/icons/Square107x107Logo.png b/Bigscreen/src-tauri/icons/Square107x107Logo.png deleted file mode 100644 index 0ca4f27198838968bd60ed7d371bfa23496b7fe5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2863 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 Y2S8W#vM)6=T>t<807*qoM6N<$f*y@n<^TWy diff --git a/Bigscreen/src-tauri/icons/Square284x284Logo.png b/Bigscreen/src-tauri/icons/Square284x284Logo.png deleted file mode 100644 index c021d2ba76619c08969ab688db3b27f29257aa6f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7737 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 diff --git a/Bigscreen/src-tauri/icons/Square30x30Logo.png b/Bigscreen/src-tauri/icons/Square30x30Logo.png deleted file mode 100644 index 621970023096ed9f494ba18ace15421a45cd65fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 903 zcmV;219<$2P)2 z+CUKPMqaqGiH;zb!R4$B-WXS^YzQr=@UH>k4?*L)&R=zYjBrZenKdc9|JlS$SO*RJ zKt8FSTDAdk1g_WPAO!p^V!AuL;Lm;uQyV;zKq)J3i(;q*;k+pD%f3eltU`PYdy9(k0&%` zuWAPcV6|-y?|?7O1W!KSK}pbk8#~!|FA@(VJkt^V@0lio{afoAeo*f&$W2s6${5!1eKvAGD2$GZwSB98L2ZVS- zKn8ENRkZ*sb!@QugOrQNK3(sy1v%J#m|rpB+h|Nkqa3FRT>74xSs{#&saU2Lf!_Iq zKmuKAESh`gs!fneGWn+nf}l?7jE$HW!Af&vE5=G!QU)U2v&HLIBGXKk4nQx{hsHjL zLPMAo5=*uInFbq7(aa`Y2VX5wCmaeqvECOFv)a>0t>ZaEb*cJccER=BB?KFZhV$c^ znL*l8x*UYZv4WK|j?~Jt6~~F%{pk~z5A*>^M`?r5m9@RJ_x|uEtX(6Vk@Y()MVto* z93wr)%3m%|#OZ~srm>zF(JvDuTq*@;d&^>_BJm5hOU`3FjG70L#Vzv9I?`<7$T@

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 diff --git a/Bigscreen/src-tauri/icons/Square310x310Logo.png b/Bigscreen/src-tauri/icons/Square310x310Logo.png deleted file mode 100644 index f9bc04839491e66c07b16ab03743c0c53b4109cc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8591 zcmbtahc}$h_twIy(GxYgAVgi!!xDs*)f2s!wX2s9Bo-?nB+*%-1*_LxM2i}|mu0o+ zU80NN=kxs+esj*8_ssL&Gk4CMdGGr?_s$21o+dQ~D+K`o0kyW4x&Z+JA@IKrAiYI) znp%o(ALO1|uY3pyC>j3igaqjs_isT$9|KJ_g7P8ut=j>Kvnp7XfS~FVJ7pZI}8ladf{o!;c zm1(K;-KkdRXO-n=L1P0pQv0P`U(b2~9nEJ=@_rst-RE_UCEIhCS6ZC{wgP%L=ch&T zC*gow@BgnRJVg7H?|jR*KU64`|5#Jg~WpHZ+L{j}|Li4|snUleLlZI)ZeC zOI^*wECuanft|Cy7L!avUqb|s`zkL-uUniu+&?`PC1In=Ea{>DZXXUSFYUIYtR83C zra$`5(dV9>JAOL}$hJclnH&JSKk%j1Hve%5+nA;Kpc0mQn*Ti~f?BK;JrIBAa$eE+ z@j#pupdkvqx*TZ}?&Ia-L_V0(F#w!2UsUGF^sb*3d{2s?9{L8Tb?6NZ_#{1)7Mm{N zhK+vn?p+Kqf?CgLD02|sP;&<{&SF;h@qwL~*dr1)_9B3E&BtHsceG7qR>%PL;B> zB_F)S$_$6{RbkQlTRg>ezn)f360DC+Y})U`pU@+ouf%$!z|czk5$U9&=5D1k8>Jvm zAv8|7*o77+9P1kQH1BKXo5q-&tu8K{F#3rez}W20aldEBAFYju9G9-dBUkeXND0x! zyV>gDE&8^GTdUO{!K}&NM%s2J;s^f9_oGeJ|Fmy7BDN)+Cjb5J4?!4mbx|T{?NjrxhJ61zx;_vPzEwo7$v&}AL|(FD9o-n zI99cr^aZ_<$bIbA$(l#CNSf84z*f@X7@<^}6y_GHC z9`IfYQ0F(;5Tl!7`I`mtDcjDlKrNQ2=tt20CZ~N+;vby{Nn|&UPE*%!3g<^Rx@(Il zm^fJ}vYu87Q3Lrh?tJXkI8z&Xqy;_Tm@FgYgS};gCyNHdZ%!PIoQNyiP^02Z=J_HZi(^*)}oDJjS!}u4hms?hy7s-Cg?{7h*k= zn=>J?uK9a1;W;kqefG`vB~#EvTZOx(984*jwL$_7jb1Il6iHqj58c{WT<%KXgF?-W z2OhfkK-uw}*Sig_5$VBCZ6C76@O`0FFk_^~b5(YTM9g;K0(-~|`1KW`GJG0c%wav> zv%7*>v1?Qs4IKOAU57cw78`YXOi|IIq<;oVnDAb-P|yk%s68#6T!5H+%|Fh`6lFs> zP!=A>vl8)VAck!0mHn_9wzT5TT8^^#@UBn;X42=E~h@Jd7nVf^qZr65Sp_-rT;j z|Bb`c$Hafo$r7p?HW?gShdf2TYRk4(H8;P-jt1r1-8O(dV#`Nf@Sp7Ts+P0 z1=YjoOaZ2{Sx8kRZIfBY7Q2LJ7<~|(heip|2=-M2Qg$-1%elQ!+RqJ$kNp{xj#iQ!xdt&U}`4h~bXnikM-7RQ+db4QFj$M*0Q( z=6?L;m)xt5u5Yi%bC@ft4gbDV)83>p1_%Q`y|#Z=jA5pJL1%|tHJzpr3i|KkAc6j| zcKS*x-w&RW)-zg@P7w&Z=Z}{7i0?X^`!h#xCkMBoHoN24bl*iw-fEwl+Ej*y4l$U5 zOsmW4+>ixG+JEoiicM8u z{p*QtFrRQulAI=Z>PM>Ce;!sgJG+`9ExIa$=kKD06*FQ&$ehjhGqz~>{E^Lm=?j7l+D#JLlMa0&Se}V*n)qA0`sy&k1DlFLiKVB)AbADG0~~puma1DHs7_NN}_R>+cpikj+ZS+X+C)7 zVxY6LU{AuPUebgMh-2;b!|S^nN*wsabFz%{4w1cay)>fRuhJUuSWQ}3S)qf`a!ixM zQs1maTy)8X_jBSuJ}_CU7dW8wPn*_ltka^fjVn_#GjCim9Jb0dnN-&y8f*@93?xn% z_+znuyU?&s#V?r;{2$7`n05S@8Y~&KF$1X*nwp)1$Bth5yT{K&90C(uCH~Crpr(yN z`o7zm@V=^IYA1?~-|ZSaZ<*qT%CRTy1zyKV8^{kMZ48~feHul}UUw)8s-E^f&_XvK z%_pX3Qm+viH6%4@gzhH!Xoi+#asO$3n|M!J+2mz*$q%l9hq9CouPuiBR(O>YV3?`5 zSMxGTIoLmY@mD((7mg(yHBLA43{IyhG_Jh(!=9aM{j}Mqm2IBvOirget~WJeLbl=g z_BX7*{rRl0D#S&Ubs3?)WDn2nKK99(lbEYJ9KMCAWI6Xaj$uQ(#T9;_H?Je_VhBTi znPgNdj0;+W0tAxUkmW8Ud?T>PDc6=ke>l3g&Z?ig9#kGii0|AEAhZ}A&M zhJ?P0J*r82tj%HsBkc7Yzb`d>xuquI=>J8BjBt!7P^e;{3rBiW=gNhzrc}Imcq%3| zG@>#^nIN`7o(VquCx0}AMwK_+R3UCF5w*J_nBs7Wh^D4N{d0Yzoldki;v=1UiuJgf zS){!BhxB??`yf_bl^}uLW>(Ppqw5z*0G2K-2&tkp!G_4sH?$yb?~$Q$H2msdd`6w4&pX{8p*8W z7M-lhF{$Du3+Ylvyy0b=gdG4Y6%XmxJ!J$X`ixw?+=2zY3%5}qp3$&Dk-Wfwvxz2{ z(#Zx;Q?6#YKNub=gxIedHW7&Jkyvi#h z=Bo>uB!l>JcKaG25qp-Ri(>m-*iTPlCO}9bnD2K9sOx-rc zbIZQ=2)07go5G&MU-Pm1(rEJDbv!^FOU3!%7bIw5{I3cNFqbo0HOv}4@QEq8Z#(!b zrPHiN4P{G-DtEjBJtCIoQOhJVRF|GT({~r#Gyq^;=JLgH_0v$N z%U7R$Cd6{wRO00o7Qq^CRjWD1l#;WOq{~)^x46584tj;Q3mBl*RWheFamkPxl?^ky z!>vq|VV!XVEA%Fp>)IkDA@z=E$Dou@G4@V$z@D+S4#vc4d$;EAUVr8{hNw$iVVXvVC%+nWM zKVP_sgP``51Vri6`Lhy5hnO%FKo-O^xeBM(GR=pVdwb^7!mTQ!NPIB~c^4vZ9+@78 zY$LNeP?|Tae0jluNw@cj@wDfmgt1B29nE8&Q!BjSRc&Xh=I?o=|5E9aU0qS}+DNW- z-Q!_j>0t*J$b_O&%}Y0}0SzaP^$q4{CQ;X2s*1?s2{9eZ_=SUwrY7LUx8uYFGZJ$c z2m)#n0KFL0d4g=CCJY~Fn32Qyd+6Ju>160zkKE+-LzgbV!R#n@@k3 z5`OG@emYkvyTNkQkvyBznrWQ?Icf+6JFYx6lE*oOE2QzoaX(bsGdcy=o^mfCrCgN& zwd6%(Ml?!yp?m>7g88w;`dj5LNAT~R0*Iu20LJIbyBg~$Sfu3M6ij09i`)u5*?KwZ zH_*w_$Im}i;bnYaSg_=`-#tZ$oM`VlEb5jifY8*jl;4pTc_HC-%74kcd4oERH#u$$ zLyY~YE*D##e)ywc`Un(|4;t+w#ZMe@%us%R%FR7tqjgJVl)ss;zK}R5GUDIB%}Fe_ zfnrVRpyE_mGq;3;4q^wbikJN1qEfGL$gp1vL$Pjj`yWV>SbG&Ok~cH08ImZmBa`Xu za*69RmPGf7>LR0wo4!gJ%)c(OsEjP1k{p7z<`E##bT$p~97w1~yOA(X&D0I~nmmWJ zgTB;Es`go*@hxQH=KZ+sbkOb3qB}{DG?A#-@Rp`QITSPsyu)<_^`4<1q|&a0merrB zUYY&q+g1Fml+zZ+FR5Ml_Q))Y0Ld?5J49o&K+S>H?dtwO?j8G;O4WKXb;74qT77s= z65z81Ui>#=s6xe*1i%($1r#=0X##)LMsYu+N?=0>2n@`nA8Is^8Ryyc*NCTZ3f4x8 zJ)|-o6?f4Gn2E(GhZj?6;8)Y6sVW^QkiFEZawFdS;1rFlu)j8qf9;&bw8nn`sQ@-w z2pUxlyD7BV1etmJ>e+84;bIwSDjPKGzE&=Cv*jGtOaWfi;HCR?%0eV&DLti6gT zo{_4;pbM@135?7^UXTZ_7GqG;6JHJQczK=O=j+~aJExu8DCf}h>teRM9}T5O=4Y5v z28WydXtdPSx`fn%Ic?oRy#%9^Ii<$+XbFfi<`P^dB0- zDYRg8Z<^a4)Wl5<2JPS6(lpXGQq#z9x=QsbD?y zxoOtH@m`%JzBaJw=*lQ%X@Djo{buiNl!T~3j) zGUGh;(=u1Qq`Q8L*EML+rvv-kqNa~7;)YG&H=2FPu#j`U!OqFm(z`Gx{%M+}3(n0XU!oB>& z>N0%})PC_3P(K!dPil}y-0j=nVD6%W^2KR(ZkfeD?nkFi^<)~A+ zUqt%8f81vhi}7!b*xY?uM%ii2(W`$?lLID}&x7*&mHvqx^&FmUpN{s9_`p^@a=%|cF#|YANVICIMT%?io8XlzMB7u zOlLz(ZSOwyYg=#j%7%rCg2x0UB4!D75>&3>AB4sFa-3}|^gttoer??X9$z%KaHy1T z5vbaYm)||e_+pvr)C&>cp0BhH;GWtS>4Nqz6_Ff>scg!i)Ry(IX<4ze+DAv9xzW0_ zhTmY$7y52)BJHx*T|E}*Wn(7uBT}2Mpn{(x>t(hOoCS|@ABSIPj0^HRSjFprp4Wsx_qMo>R$QHPmoCMe&Jc&=Wcuceio+`ZQL=SiCr&b9pj7&fx+qO-6Ts331~VhMamuyQ@#6snW-yuSjRv&q05A;Mb_z&|xk6l5 z{o~`0sSLUz7VK(!i~t~@-No$9y%bKhJ>MXYqT&V*;LYq|9T_ptXvw8XQO&I`bKw&7 zt9^r!k3E+ZXEfgSVEW#~qSwI@F?+##vHd1uRg)UN&OGDBPc{VuocbE0-_n#stZo<0fFgZYb6bUqI zab!gC2{LXCKo6VM%YNvP(H)eczGSn)uaITZztR+?Jv|hj(OgC`?b-b*d{HCtczCOR z`V;2DRyU@7vr)LLAb^pIZ5~WRDHYv7+m7ye7ExdY@R!IE{K3EwM(O=`5cKuQWNd}KWuu8W z=!%PNAP;PF_U`RAVsK}l7|)V=f zF(-ewaf3|VGC9lCY9AlyWJ{YoBl)GOufnV)DH*@-7n<|0<`xPr6t{wl^>!)X#LL}} z-m44?nz&nH$o0B@=6P)FD_n~o_$M^Te&||J$Ipq4XwCCTnMhO_$(SBo)x73sm$l_D zH(=PMtk-|)eDK*>vM|}f*Hj1H5ZUnIVsBMt6`8)1IBriRwNiNE`>FhD?J+Lek-*a6 znQ&dnV}C1wj0*8I=8I8`4>YF2qe%W&T}bC5zQz{2e~MW@=55!#m(=F80k@j9r3o|~ zs3}tHIzEZ*J^AnG_v_lvAn`=8(Hudn9hrNm>ElejQLTL(EncKVlDwK4rZo*-gG|hi zIHWhO>ig%9&R(60h^B0Dx^8cnj%T2la=C%(upE6`DB7s-SE8v{{jy!JeL;~LbPAotrW{D%$&V-(1RlqPIW88iKMmhDV23GudMR(% zg6r!9(q5}GNnISBKGNPW#eUKTt*2)Ds6Nvk{=8+73`cMItBGz=V+Tzsv39T3m4)`= zzE1y|XP%8(f~Y{l%P<&)g}E1Rd0W3L$QHUY5U7LqMwj*hyf-@Hv#ffPchCy+0h}aH z6k0F#W8RQ>k|&_>aKx7}4w&4{>P1Y^zbOVf4Vc0ndH_mOfdrnFfgJ6RZ!3}~2g(;wzyAy)r!Qsc zpe;rPb__Y`02<^seV-${o1n$qhywV#kY1Qs_v(0}py&g``$B~b=&652dRYs#FboDmB8#tnYzQ_*^+gGi)d9$pUCHs=Yh(mUQiGoCdx*cs%nQxkY7i0{N z%ULUVd|kdTHYWT((JtL1nN67B3ur2_sBG|=Z8w2C9Ik%xodqDCgN1+otb0gXG*#&? z`f;0DLnyi!-efCsC&K*6ExYT9GDoSYVVHIK!@_LRu zy-BktNmRh9t1FBQN=)@^twC?AQH5(x(R+|hPT*l>;ZC0!s=wt$V5uTiQ!CutSFNvK@S|*s|&sn1wz9#z%$o1c7X&?I>g} zeS9Hhk)}n>xj)lxLk#RE8AtRx1?mX4Ir*_Nv-|p!hl6yQc9^-r=%X%yC)o-P`sccKAHm${4R4(y=z*n)P9IuXE z23YI&)FS7`ad%Bs^_*wOTaok!4X$i>hRDfQpjWoth!n{3P-$zz&w#IMn>%BDMONbw z9S(qWs|yb5@b?o=4~6H_EG`e~a#`Y&9To<~A1^D`tu(AGo*Bw1<%6rV(Xp}nUPa(8 zfjQ+d*seRHrc4#G0=v(JA zXzoSb!F%jE-$!TxceFZ5*qf9S%1Lo8V2oPls9blxY z&bN;{x%7SskKWdY?3j%lZRkm&hf=*=akbhk(v-fcl^nFk?Q7ikBQgelc2(j6wr5IQ zq0&wmJ#vs*>8!Tj)3PZVkj{&}r)9O{?Uc$8Fw-5=Q+blWE;{9&D_*??-IJIEN`W$=~J3n>(DxK~SH)77}VK5s%PoI(c zI1Mb4(`4EEGp4c>Btn9xb70YOVtrBa*GcIMwTk`WC*ejjWg5P_k*|Kx&}P!Yexm*A z3Dv+2W^jbcr`DMd%g9V|ET~*rHKd0-8z6H6smjbnP~Uk%!+IwvEP9V|Ok1}?+5jU`?BGe1>gHDD=@3GHyJKq)}Q_JxJk&qHbBiKF9ldd6)_6rL6 zf<6|j`3A2&Wz{tNnt>)gmpPg;a1 zEy)}|*T@nh0Q-Y)Nq30ye(u+yJ=W~*?aSfoGYKMUJ%mk6rwz?esQFBcz8E2x@X0+A za|bhX^A&rK8}Xmr1BRJVMQff?Il))AoXVR1ha4A<#{@PGol8)Vchm1;I-@Q{MNHq; zI~=)iiJ#3U8?>>}QhU$$G?i$b{!>e-3gNc5Rm;`&74)c6!W{QHHiQ|IDLf`B<__FJ z57;o$!k8ewCJC;185mn%VIC{C&mt}7D+!BW0ZL{OmMt8v52`f&EX|dE&{{8Mo5Jvd zZ8@2(C9b+!L@$57Uudfjd`RwfaD{sraE7l44*c0#a5MUkn()8N5&yr&d8J}TlB+X4 Riu&JN+8TQ58XP)}x#CqR3GU7ujt6U06NkcaF#4@P;6 zg@bZ};3_9&yplTI19+v8Mj(OnwBG|iLr>2~tLN*U0l3FKA`tKifx~K%-ioWQbJ4Wt zup{;uEl`-HCB6J4UTeI=lB1pbS+5&V5B2~zto0QXd0oBj!vI*r9^2mD^_ma zbPsQw;Wsb;XeE;1LSl%&Wv=rEGsHxyM4~Z1S4Om&o|*9BuTHP<-k%`^yqg<_ck9O1 zXB7bKE5mDLh$Da(Q3o1bhYUK*Q7tSyUa-L)*SP&WPFVI68aEteN)1~XS5rk>-nSzB z?e(nWFZ>}UR5Z6%%eLuE@fGZVjf6R}OR`vs{D2e{1Cm8PfUzdoT=8TwPFe=G#Ks&p z7rv#E6@UZpvv=j`qe`OoE?Y;mlwp>uQ%FX1lL@djcIgr3RPey-D$XqD(b2{t!G(nK z^=g&R^Q7M5BTVsQXj?F}gj036ax=Z8=ypOwqv>&FV}p_ftG;3u8C(_)H_2X`5*%HH zEO_Ys1p7v`%CRO7(s~JPO89Ww2tNQKKX6aJbCYa&V;(GmHj1Fg8*X}18Nn8y;zFA? zwwY7YO`pTUs6!;N#PcLGu5{wPe~AK%(wzR|;k9!{q%F`9<&teu1w>S;Bz1f#(Pd~; zLRALCU;LHm0L^n?vSA456X`~x-(|_3(E@5ox3}r|w1kC1*m?YYZ09nmm_FZmuB$_# zk{v%y>m^Tdy90z-*!iA8Ha^SqoV$&AN=gVf{Js3@&#zS*=V95VC*dZ|_X01eJuHPj z&t)6guurq})cOc3)yB9D8i{uP!Kq4`zV|eWQlf~CDCb*JYct+SEPZQGxqjV25jnSM zi$-ZODVp9Fbu$QxA0GVsB6CBO0b0Vcous}uq5ufZZ8bLCugAyzK0RM+`mi$2GJiv9 zeodu0bcZ0&_8$Dx%o9Ow{K3RFpuA9F*>v9=AC(~^QdPo4KdOtgn7R1!95RCBkF*!g z*JLGxVL=XTJcJ&;bovwyD>{oJ9UPpxCuKKnE zx(p0Ic;-AliYQ8n8m9ty9dh4Qt01R>kA73vm+XbG+$bNs;p)ye4it3y2wdq9p-6wE zlxVgiS?NEEF{KCPA@m?0M%80hRL1X|AV(KFZsa^L(M{^rz0 zfLvUvu~gv$st_YIao`u;jrUnd_I6dZ?ln-nefudZ-97H1;6JET9r9*AF){!E002ov JPDHLkV1lm|RXG3v diff --git a/Bigscreen/src-tauri/icons/Square71x71Logo.png b/Bigscreen/src-tauri/icons/Square71x71Logo.png deleted file mode 100644 index 63440d7984936a9caa89275928d8dce97e4d033b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/Bigscreen/src-tauri/icons/icon.ico b/Bigscreen/src-tauri/icons/icon.ico deleted file mode 100644 index b3636e4b22ba65db9061cd60a77b02c92022dfd6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 86642 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 AKmY&$ diff --git a/Bigscreen/src-tauri/icons/icon.png b/Bigscreen/src-tauri/icons/icon.png deleted file mode 100644 index e1cd2619e0b5ec089cbba5ec7b03ddf2b1dfceb6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14183 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

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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, - created_at_unix_ms: i64, -} - -fn now_unix_ms() -> i64 { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .map(|duration| duration.as_millis() as i64) - .unwrap_or(0) -} - -fn sanitize_name(name: &str) -> Result { - let trimmed = name.trim(); - if trimmed.is_empty() { - return Err("User name cannot be empty.".to_string()); - } - if trimmed.chars().count() > 64 { - return Err("User name is too long (max 64 chars).".to_string()); - } - Ok(trimmed.to_string()) -} - -fn sanitize_optional_name(name: Option) -> Result, String> { - let Some(value) = name else { - return Ok(None); - }; - let trimmed = value.trim(); - if trimmed.is_empty() { - return Ok(None); - } - if trimmed.chars().count() > 64 { - return Err("Name is too long (max 64 chars).".to_string()); - } - Ok(Some(trimmed.to_string())) -} - -fn build_display_name(first_name: &str, last_name: Option<&str>) -> String { - let last = last_name.map(str::trim).unwrap_or(""); - if last.is_empty() { - first_name.to_string() - } else { - format!("{first_name} {last}") - } -} - -fn ensure_users_schema(conn: &Connection) -> Result<(), String> { - conn.execute( - "CREATE TABLE IF NOT EXISTS users ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - created_at_unix_ms INTEGER NOT NULL - )", - [], - ) - .map_err(|err| format!("Failed to prepare users table: {err}"))?; - - let mut stmt = conn - .prepare("PRAGMA table_info(users)") - .map_err(|err| format!("Failed to inspect users schema: {err}"))?; - let mut rows = stmt - .query([]) - .map_err(|err| format!("Failed to read users schema: {err}"))?; - - let mut has_first_name = false; - let mut has_last_name = false; - while let Some(row) = rows - .next() - .map_err(|err| format!("Failed to iterate users schema: {err}"))? - { - let column_name: String = row - .get(1) - .map_err(|err| format!("Failed to parse users schema: {err}"))?; - if column_name == "first_name" { - has_first_name = true; - } - if column_name == "last_name" { - has_last_name = true; - } - } - - if !has_first_name { - conn.execute("ALTER TABLE users ADD COLUMN first_name TEXT", []) - .map_err(|err| format!("Failed to add first_name column: {err}"))?; - conn.execute( - "UPDATE users SET first_name = TRIM(name) WHERE first_name IS NULL OR TRIM(first_name) = ''", - [], - ) - .map_err(|err| format!("Failed to backfill first_name: {err}"))?; - } - - if !has_last_name { - conn.execute("ALTER TABLE users ADD COLUMN last_name TEXT", []) - .map_err(|err| format!("Failed to add last_name column: {err}"))?; - } - - Ok(()) -} - -#[tauri::command] -fn get_first_user(storage: tauri::State<'_, AppStorage>) -> Result, String> { - let conn = storage.connect_users()?; - ensure_users_schema(&conn)?; - conn.query_row( - "SELECT - id, - COALESCE( - NULLIF(TRIM(COALESCE(first_name, '') || ' ' || COALESCE(last_name, '')), ''), - name - ) AS display_name, - COALESCE(NULLIF(TRIM(first_name), ''), name) AS first_name, - NULLIF(TRIM(COALESCE(last_name, '')), '') AS last_name, - created_at_unix_ms - FROM users - ORDER BY id ASC - LIMIT 1", - [], - |row| { - Ok(UserRecord { - id: row.get(0)?, - name: row.get(1)?, - first_name: row.get(2)?, - last_name: row.get(3)?, - created_at_unix_ms: row.get(4)?, - }) - }, - ) - .optional() - .map_err(|err| format!("Failed to load user: {err}")) -} - -#[tauri::command] -fn create_user( - first_name: String, - last_name: Option, - storage: tauri::State<'_, AppStorage>, -) -> Result { - let safe_first_name = sanitize_name(&first_name)?; - let safe_last_name = sanitize_optional_name(last_name)?; - let display_name = build_display_name(&safe_first_name, safe_last_name.as_deref()); - let created_at_unix_ms = now_unix_ms(); - let conn = storage.connect_users()?; - ensure_users_schema(&conn)?; - conn.execute( - "INSERT INTO users (name, first_name, last_name, created_at_unix_ms) VALUES (?1, ?2, ?3, ?4)", - params![ - display_name, - safe_first_name, - safe_last_name, - created_at_unix_ms - ], - ) - .map_err(|err| format!("Failed to create user: {err}"))?; - - let id = conn.last_insert_rowid(); - Ok(UserRecord { - id, - name: build_display_name(&safe_first_name, safe_last_name.as_deref()), - first_name: safe_first_name, - last_name: safe_last_name, - created_at_unix_ms, - }) -} - -#[cfg_attr(mobile, tauri::mobile_entry_point)] -pub fn run() { - tauri::Builder::default() - .plugin(tauri_plugin_opener::init()) - .setup(|app| { - let app_data_dir = app - .path() - .app_data_dir() - .map_err(|err| format!("Failed to resolve app data dir: {err}"))?; - let storage = AppStorage::new(app_data_dir)?; - let users_conn = storage.connect_users()?; - ensure_users_schema(&users_conn)?; - drop(users_conn); - library::initialize(&storage)?; - - app.manage(storage); - Ok(()) - }) - .invoke_handler(tauri::generate_handler![ - get_first_user, - create_user, - list_library_games, - launch_library_game, - scan_library_command, - update_library_game - ]) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); -} diff --git a/Bigscreen/src-tauri/src/library/commands.rs b/Bigscreen/src-tauri/src/library/commands.rs deleted file mode 100644 index a02409d..0000000 --- a/Bigscreen/src-tauri/src/library/commands.rs +++ /dev/null @@ -1,98 +0,0 @@ -use super::db::{ensure_library_schema, list_games, update_customization}; -use super::models::{GameCustomizationRequest, LibraryGame, LibraryScanRequest, ScanSummary}; -use super::{list_visible_games, scan_library}; -use crate::storage::AppStorage; -use serde::Serialize; -use std::path::PathBuf; - -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct LaunchLibraryGameResult { - pub launched: bool, - pub action: String, - pub message: String, -} - -#[tauri::command] -pub async fn scan_library_command( - request: LibraryScanRequest, - storage: tauri::State<'_, AppStorage>, -) -> Result { - let storage = storage.inner().clone(); - let local_folders = request - .local_folders - .into_iter() - .map(PathBuf::from) - .collect::>(); - - tauri::async_runtime::spawn_blocking(move || scan_library(storage, local_folders)) - .await - .map_err(|err| format!("Library scanner task failed: {err}"))? -} - -#[tauri::command] -pub async fn list_library_games( - storage: tauri::State<'_, AppStorage>, -) -> Result, String> { - let storage = storage.inner().clone(); - tauri::async_runtime::spawn_blocking(move || list_visible_games(&storage)) - .await - .map_err(|err| format!("Library list task failed: {err}"))? -} - -#[tauri::command] -pub async fn update_library_game( - request: GameCustomizationRequest, - storage: tauri::State<'_, AppStorage>, -) -> Result, String> { - let storage = storage.inner().clone(); - tauri::async_runtime::spawn_blocking(move || { - let conn = storage.connect_library()?; - ensure_library_schema(&conn)?; - update_customization(&conn, request) - }) - .await - .map_err(|err| format!("Library update task failed: {err}"))? -} - -#[tauri::command] -pub async fn launch_library_game( - game_id: i64, - storage: tauri::State<'_, AppStorage>, -) -> Result { - let storage = storage.inner().clone(); - tauri::async_runtime::spawn_blocking(move || { - let conn = storage.connect_library()?; - ensure_library_schema(&conn)?; - let game = list_games(&conn, true)? - .into_iter() - .find(|game| game.id == game_id) - .ok_or_else(|| "Library app was not found.".to_string())?; - - let launch_target = game - .launch_command - .as_deref() - .or(game.executable_path.as_deref()) - .unwrap_or(game.install_path.as_str()); - - // Future provider launchers plug in here: - // Steam: steam://run/, GOG/Epic URI handlers, emulator profiles, - // and native executable spawning with per-app environment overrides. - println!( - "[NebulaOS] launch placeholder: {} via {}", - game.user_title.as_deref().unwrap_or(&game.title), - launch_target - ); - - Ok(LaunchLibraryGameResult { - launched: true, - action: "placeholder".to_string(), - message: format!( - "Launch requested for {}.", - game.user_title.unwrap_or(game.title) - ), - }) - }) - .await - .map_err(|err| format!("Library launch task failed: {err}"))? -} diff --git a/Bigscreen/src-tauri/src/library/db.rs b/Bigscreen/src-tauri/src/library/db.rs deleted file mode 100644 index f461f2a..0000000 --- a/Bigscreen/src-tauri/src/library/db.rs +++ /dev/null @@ -1,480 +0,0 @@ -use super::models::{ - infer_library_item_kind, GameCandidate, GameCustomizationRequest, GameMetadata, GameSource, - LibraryGame, LibraryItemKind, MetadataStatus, -}; -use rusqlite::{params, Connection, OptionalExtension}; -use std::time::{SystemTime, UNIX_EPOCH}; - -pub fn now_unix_ms() -> i64 { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .map(|duration| duration.as_millis() as i64) - .unwrap_or(0) -} - -pub fn ensure_library_schema(conn: &Connection) -> Result<(), String> { - conn.execute_batch( - "PRAGMA foreign_keys = ON; - - CREATE TABLE IF NOT EXISTS games ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - title TEXT NOT NULL, - platform_source TEXT NOT NULL, - source_store TEXT NOT NULL, - source_app_id TEXT, - install_path TEXT NOT NULL, - executable_path TEXT, - launch_command TEXT, - description TEXT, - app_kind TEXT NOT NULL DEFAULT 'game', - steam_app_type TEXT, - genres_json TEXT NOT NULL DEFAULT '[]', - steam_categories_json TEXT NOT NULL DEFAULT '[]', - release_date TEXT, - developer TEXT, - publisher TEXT, - cover_image TEXT, - hero_image TEXT, - icon_image TEXT, - metadata_status TEXT NOT NULL DEFAULT 'pending', - last_scanned INTEGER NOT NULL, - user_hidden INTEGER NOT NULL DEFAULT 0, - user_favourite INTEGER NOT NULL DEFAULT 0, - user_title TEXT, - manual_metadata_json TEXT, - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL - ); - - CREATE UNIQUE INDEX IF NOT EXISTS idx_games_source_app - ON games (platform_source, source_app_id) - WHERE source_app_id IS NOT NULL; - - CREATE UNIQUE INDEX IF NOT EXISTS idx_games_install_path - ON games (install_path); - - CREATE INDEX IF NOT EXISTS idx_games_hidden ON games (user_hidden); - CREATE INDEX IF NOT EXISTS idx_games_metadata_status ON games (metadata_status); - - CREATE TABLE IF NOT EXISTS scan_runs ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - started_at INTEGER NOT NULL, - completed_at INTEGER, - status TEXT NOT NULL, - discovered_count INTEGER NOT NULL DEFAULT 0, - metadata_matched_count INTEGER NOT NULL DEFAULT 0, - unmatched_count INTEGER NOT NULL DEFAULT 0, - error TEXT - ); - - CREATE TABLE IF NOT EXISTS metadata_cache ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - provider TEXT NOT NULL, - lookup_key TEXT NOT NULL, - metadata_json TEXT NOT NULL, - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL, - UNIQUE(provider, lookup_key) - );", - ) - .map_err(|err| format!("Failed to prepare library schema: {err}"))?; - - ensure_column( - conn, - "games", - "app_kind", - "app_kind TEXT NOT NULL DEFAULT 'game'", - )?; - ensure_column(conn, "games", "steam_app_type", "steam_app_type TEXT")?; - ensure_column( - conn, - "games", - "steam_categories_json", - "steam_categories_json TEXT NOT NULL DEFAULT '[]'", - )?; - - Ok(()) -} - -pub fn upsert_candidate( - conn: &Connection, - candidate: &GameCandidate, - metadata: Option<&GameMetadata>, -) -> Result<(), String> { - let now = now_unix_ms(); - let metadata_status = metadata - .map(|value| value.status.as_str()) - .unwrap_or(MetadataStatus::NeedsReview.as_str()); - let title = metadata - .and_then(|value| value.title.as_deref()) - .unwrap_or(&candidate.title); - let source = candidate.source.as_str(); - let app_kind = metadata - .map(|value| value.app_kind.clone()) - .unwrap_or_else(|| infer_library_item_kind(None, Some(&candidate.title), &[], &[])); - let genres_json = metadata - .map(|value| serde_json::to_string(&value.genres)) - .transpose() - .map_err(|err| format!("Failed to encode genres: {err}"))? - .unwrap_or_else(|| "[]".to_string()); - let steam_categories_json = metadata - .map(|value| serde_json::to_string(&value.steam_categories)) - .transpose() - .map_err(|err| format!("Failed to encode Steam categories: {err}"))? - .unwrap_or_else(|| "[]".to_string()); - - conn.execute( - "INSERT INTO games ( - title, - platform_source, - source_store, - source_app_id, - install_path, - executable_path, - launch_command, - description, - app_kind, - steam_app_type, - genres_json, - steam_categories_json, - release_date, - developer, - publisher, - cover_image, - hero_image, - icon_image, - metadata_status, - last_scanned, - created_at, - updated_at - ) - VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22) - ON CONFLICT(platform_source, source_app_id) WHERE source_app_id IS NOT NULL DO UPDATE SET - title = excluded.title, - source_store = excluded.source_store, - install_path = excluded.install_path, - executable_path = excluded.executable_path, - launch_command = excluded.launch_command, - description = COALESCE(excluded.description, games.description), - app_kind = excluded.app_kind, - steam_app_type = COALESCE(excluded.steam_app_type, games.steam_app_type), - genres_json = excluded.genres_json, - steam_categories_json = excluded.steam_categories_json, - release_date = COALESCE(excluded.release_date, games.release_date), - developer = COALESCE(excluded.developer, games.developer), - publisher = COALESCE(excluded.publisher, games.publisher), - cover_image = excluded.cover_image, - hero_image = excluded.hero_image, - icon_image = excluded.icon_image, - metadata_status = excluded.metadata_status, - last_scanned = excluded.last_scanned, - updated_at = excluded.updated_at - ON CONFLICT(install_path) DO UPDATE SET - title = excluded.title, - platform_source = excluded.platform_source, - source_store = excluded.source_store, - source_app_id = COALESCE(excluded.source_app_id, games.source_app_id), - executable_path = excluded.executable_path, - launch_command = excluded.launch_command, - description = COALESCE(excluded.description, games.description), - app_kind = excluded.app_kind, - steam_app_type = COALESCE(excluded.steam_app_type, games.steam_app_type), - genres_json = excluded.genres_json, - steam_categories_json = excluded.steam_categories_json, - release_date = COALESCE(excluded.release_date, games.release_date), - developer = COALESCE(excluded.developer, games.developer), - publisher = COALESCE(excluded.publisher, games.publisher), - cover_image = excluded.cover_image, - hero_image = excluded.hero_image, - icon_image = excluded.icon_image, - metadata_status = excluded.metadata_status, - last_scanned = excluded.last_scanned, - updated_at = excluded.updated_at", - params![ - title, - source, - source, - candidate.app_id, - candidate.install_path.to_string_lossy(), - candidate - .executable_path - .as_ref() - .map(|path| path.to_string_lossy().to_string()), - candidate.launch_command, - metadata.and_then(|value| value.description.as_deref()), - app_kind.as_str(), - metadata.and_then(|value| value.steam_app_type.as_deref()), - genres_json, - steam_categories_json, - metadata.and_then(|value| value.release_date.as_deref()), - metadata.and_then(|value| value.developer.as_deref()), - metadata.and_then(|value| value.publisher.as_deref()), - metadata - .and_then(|value| value.cover_image.as_ref()) - .map(|path| path.to_string_lossy().to_string()), - metadata - .and_then(|value| value.hero_image.as_ref()) - .map(|path| path.to_string_lossy().to_string()), - metadata - .and_then(|value| value.icon_image.as_ref()) - .map(|path| path.to_string_lossy().to_string()), - metadata_status, - now, - now, - now, - ], - ) - .map_err(|err| format!("Failed to upsert library game: {err}"))?; - - Ok(()) -} - -pub fn list_games(conn: &Connection, include_hidden: bool) -> Result, String> { - let where_clause = if include_hidden { - "" - } else { - "WHERE user_hidden = 0" - }; - let sql = format!( - "SELECT - id, - title, - platform_source, - source_store, - source_app_id, - install_path, - executable_path, - launch_command, - description, - app_kind, - steam_app_type, - genres_json, - steam_categories_json, - release_date, - developer, - publisher, - cover_image, - hero_image, - icon_image, - metadata_status, - last_scanned, - user_hidden, - user_favourite, - user_title - FROM games - {where_clause} - ORDER BY COALESCE(user_title, title) COLLATE NOCASE ASC" - ); - - let mut stmt = conn - .prepare(&sql) - .map_err(|err| format!("Failed to load library games: {err}"))?; - - let rows = stmt - .query_map([], |row| { - let genres_json: String = row.get(11)?; - let genres = parse_string_list(&genres_json); - let steam_categories_json: String = row.get(12)?; - let steam_categories = parse_string_list(&steam_categories_json); - let platform_source: String = row.get(2)?; - let stored_app_kind: String = row.get(9)?; - let metadata_status: String = row.get(19)?; - let steam_app_type: Option = row.get(10)?; - let title: String = row.get(1)?; - let inferred_app_kind = infer_library_item_kind( - steam_app_type.as_deref(), - Some(&title), - &genres, - &steam_categories, - ); - let app_kind = if inferred_app_kind == LibraryItemKind::Unknown { - LibraryItemKind::from_str(&stored_app_kind) - } else { - inferred_app_kind - }; - Ok(LibraryGame { - id: row.get(0)?, - title, - platform_source: GameSource::from_str(&platform_source), - source_store: row.get(3)?, - source_app_id: row.get(4)?, - install_path: row.get(5)?, - executable_path: row.get(6)?, - launch_command: row.get(7)?, - description: row.get(8)?, - app_kind, - steam_app_type, - genres, - steam_categories, - release_date: row.get(13)?, - developer: row.get(14)?, - publisher: row.get(15)?, - cover_image: row.get(16)?, - hero_image: row.get(17)?, - icon_image: row.get(18)?, - metadata_status: MetadataStatus::from_str(&metadata_status), - last_scanned: row.get(20)?, - user_hidden: row.get::<_, i64>(21)? != 0, - user_favourite: row.get::<_, i64>(22)? != 0, - user_title: row.get(23)?, - }) - }) - .map_err(|err| format!("Failed to read library games: {err}"))?; - - rows.collect::, _>>() - .map_err(|err| format!("Failed to parse library games: {err}")) -} - -pub fn update_customization( - conn: &Connection, - request: GameCustomizationRequest, -) -> Result, String> { - let now = now_unix_ms(); - let current = conn - .query_row( - "SELECT id FROM games WHERE id = ?1", - params![request.game_id], - |row| row.get::<_, i64>(0), - ) - .optional() - .map_err(|err| format!("Failed to find library game: {err}"))?; - - if current.is_none() { - return Ok(None); - } - - conn.execute( - "UPDATE games SET - user_title = COALESCE(?1, user_title), - executable_path = COALESCE(?2, executable_path), - user_hidden = COALESCE(?3, user_hidden), - user_favourite = COALESCE(?4, user_favourite), - metadata_status = CASE WHEN ?1 IS NULL THEN metadata_status ELSE 'manual' END, - updated_at = ?5 - WHERE id = ?6", - params![ - request.title.and_then(|value| { - let trimmed = value.trim().to_string(); - if trimmed.is_empty() { - None - } else { - Some(trimmed) - } - }), - request.executable_path, - request.hidden.map(|value| if value { 1 } else { 0 }), - request.favourite.map(|value| if value { 1 } else { 0 }), - now, - request.game_id, - ], - ) - .map_err(|err| format!("Failed to update library game: {err}"))?; - - conn.query_row( - "SELECT - id, - title, - platform_source, - source_store, - source_app_id, - install_path, - executable_path, - launch_command, - description, - app_kind, - steam_app_type, - genres_json, - steam_categories_json, - release_date, - developer, - publisher, - cover_image, - hero_image, - icon_image, - metadata_status, - last_scanned, - user_hidden, - user_favourite, - user_title - FROM games - WHERE id = ?1", - params![request.game_id], - |row| { - let genres_json: String = row.get(11)?; - let steam_categories_json: String = row.get(12)?; - let platform_source: String = row.get(2)?; - let stored_app_kind: String = row.get(9)?; - let metadata_status: String = row.get(19)?; - let steam_app_type: Option = row.get(10)?; - let title: String = row.get(1)?; - let genres = parse_string_list(&genres_json); - let steam_categories = parse_string_list(&steam_categories_json); - let inferred_app_kind = infer_library_item_kind( - steam_app_type.as_deref(), - Some(&title), - &genres, - &steam_categories, - ); - let app_kind = if inferred_app_kind == LibraryItemKind::Unknown { - LibraryItemKind::from_str(&stored_app_kind) - } else { - inferred_app_kind - }; - Ok(LibraryGame { - id: row.get(0)?, - title, - platform_source: GameSource::from_str(&platform_source), - source_store: row.get(3)?, - source_app_id: row.get(4)?, - install_path: row.get(5)?, - executable_path: row.get(6)?, - launch_command: row.get(7)?, - description: row.get(8)?, - app_kind, - steam_app_type, - genres, - steam_categories, - release_date: row.get(13)?, - developer: row.get(14)?, - publisher: row.get(15)?, - cover_image: row.get(16)?, - hero_image: row.get(17)?, - icon_image: row.get(18)?, - metadata_status: MetadataStatus::from_str(&metadata_status), - last_scanned: row.get(20)?, - user_hidden: row.get::<_, i64>(21)? != 0, - user_favourite: row.get::<_, i64>(22)? != 0, - user_title: row.get(23)?, - }) - }, - ) - .optional() - .map_err(|err| format!("Failed to reload library game: {err}")) -} - -fn ensure_column( - conn: &Connection, - table: &str, - column: &str, - definition: &str, -) -> Result<(), String> { - let mut stmt = conn - .prepare(&format!("PRAGMA table_info({table})")) - .map_err(|err| format!("Failed to inspect {table} schema: {err}"))?; - let columns = stmt - .query_map([], |row| row.get::<_, String>(1)) - .map_err(|err| format!("Failed to read {table} schema: {err}"))? - .collect::, _>>() - .map_err(|err| format!("Failed to parse {table} schema: {err}"))?; - - if !columns.iter().any(|name| name == column) { - conn.execute(&format!("ALTER TABLE {table} ADD COLUMN {definition}"), []) - .map_err(|err| format!("Failed to add {table}.{column}: {err}"))?; - } - - Ok(()) -} - -fn parse_string_list(value: &str) -> Vec { - serde_json::from_str(value).unwrap_or_default() -} diff --git a/Bigscreen/src-tauri/src/library/images.rs b/Bigscreen/src-tauri/src/library/images.rs deleted file mode 100644 index 262fb57..0000000 --- a/Bigscreen/src-tauri/src/library/images.rs +++ /dev/null @@ -1,114 +0,0 @@ -use std::fs; -use std::path::PathBuf; - -#[derive(Clone)] -pub struct ImageCache { - root: PathBuf, -} - -impl ImageCache { - pub fn new(root: PathBuf) -> Self { - Self { root } - } - - #[allow(dead_code)] - pub fn game_dir(&self, source: &str, app_id_or_slug: &str) -> Result { - let safe_slug = app_id_or_slug - .chars() - .map(|ch| if ch.is_ascii_alphanumeric() { ch } else { '-' }) - .collect::(); - let dir = self.root.join(source).join(safe_slug); - fs::create_dir_all(&dir).map_err(|err| format!("Failed to prepare image cache: {err}"))?; - Ok(dir) - } - - #[allow(dead_code)] - pub fn local_image_path( - &self, - source: &str, - app_id_or_slug: &str, - image_kind: &str, - extension: &str, - ) -> Result { - Ok(self - .game_dir(source, app_id_or_slug)? - .join(format!("{image_kind}.{extension}"))) - } - - pub fn cache_remote_image( - &self, - source: &str, - app_id_or_slug: &str, - image_kind: &str, - extension: &str, - url: &str, - ) -> Result, String> { - let target = self.local_image_path(source, app_id_or_slug, image_kind, extension)?; - if target.is_file() { - if is_valid_image_file(&target) { - return Ok(Some(target)); - } - let _ = fs::remove_file(&target); - } - - let response = match ureq::get(url) - .header("User-Agent", "NebulaOS/0.1 library-scanner") - .call() - { - Ok(response) => response, - Err(_) => return Ok(None), - }; - - if !response.status().is_success() { - return Ok(None); - } - - let mut response = response; - let bytes = response - .body_mut() - .read_to_vec() - .map_err(|err| format!("Failed to download image {url}: {err}"))?; - if !looks_like_image(&bytes) { - return Ok(None); - } - fs::write(&target, bytes) - .map_err(|err| format!("Failed to write image cache {}: {err}", target.display()))?; - - Ok(Some(target)) - } - - pub fn cache_first_remote_image( - &self, - source: &str, - app_id_or_slug: &str, - image_kind: &str, - extension: &str, - urls: &[String], - ) -> Result, String> { - for url in urls { - if let Some(path) = - self.cache_remote_image(source, app_id_or_slug, image_kind, extension, url)? - { - return Ok(Some(path)); - } - } - - Ok(None) - } -} - -fn is_valid_image_file(path: &PathBuf) -> bool { - fs::read(path) - .map(|bytes| looks_like_image(&bytes)) - .unwrap_or(false) -} - -fn looks_like_image(bytes: &[u8]) -> bool { - if bytes.len() < 1024 { - return false; - } - - bytes.starts_with(&[0xff, 0xd8, 0xff]) - || bytes.starts_with(&[0x89, b'P', b'N', b'G']) - || bytes.starts_with(b"RIFF") && bytes.get(8..12) == Some(b"WEBP") -} diff --git a/Bigscreen/src-tauri/src/library/metadata/mod.rs b/Bigscreen/src-tauri/src/library/metadata/mod.rs deleted file mode 100644 index b38629e..0000000 --- a/Bigscreen/src-tauri/src/library/metadata/mod.rs +++ /dev/null @@ -1,250 +0,0 @@ -use super::images::ImageCache; -use super::models::{ - infer_library_item_kind, GameCandidate, GameMetadata, GameSource, MetadataStatus, -}; -use serde_json::Value; - -pub mod providers { - pub mod igdb { - use crate::library::metadata::MetadataProvider; - - #[allow(dead_code)] - pub trait IgdbMetadataProvider: MetadataProvider {} - } - - pub mod rawg { - use crate::library::metadata::MetadataProvider; - - #[allow(dead_code)] - pub trait RawgMetadataProvider: MetadataProvider {} - } - - pub mod steam { - use crate::library::metadata::MetadataProvider; - - #[allow(dead_code)] - pub trait SteamMetadataProvider: MetadataProvider {} - } -} - -pub trait MetadataProvider: Send + Sync { - #[allow(dead_code)] - fn provider_id(&self) -> &'static str; - fn resolve(&self, candidate: &GameCandidate) -> Result, String>; -} - -pub struct LocalCacheMetadataProvider { - image_cache: ImageCache, -} - -impl LocalCacheMetadataProvider { - pub fn new(image_cache: ImageCache) -> Self { - Self { image_cache } - } -} - -impl MetadataProvider for LocalCacheMetadataProvider { - fn provider_id(&self) -> &'static str { - "local_cache" - } - - fn resolve(&self, candidate: &GameCandidate) -> Result, String> { - if candidate.source == GameSource::Steam { - return self.resolve_steam(candidate); - } - - Ok(None) - } -} - -impl LocalCacheMetadataProvider { - fn resolve_steam(&self, candidate: &GameCandidate) -> Result, String> { - let Some(app_id) = candidate.app_id.as_deref() else { - return Ok(None); - }; - - let store_data = fetch_steam_store_data(app_id)?; - let title = store_data - .as_ref() - .and_then(|value| value.get("name")) - .and_then(Value::as_str) - .map(ToString::to_string) - .or_else(|| Some(candidate.title.clone())); - let description = store_data - .as_ref() - .and_then(|value| value.get("short_description")) - .and_then(Value::as_str) - .map(ToString::to_string); - let genres = store_data - .as_ref() - .and_then(|value| value.get("genres")) - .and_then(Value::as_array) - .map(|items| { - items - .iter() - .filter_map(|item| item.get("description").and_then(Value::as_str)) - .map(ToString::to_string) - .collect::>() - }) - .unwrap_or_default(); - let steam_categories = store_data - .as_ref() - .and_then(|value| value.get("categories")) - .and_then(Value::as_array) - .map(|items| { - items - .iter() - .filter_map(|item| item.get("description").and_then(Value::as_str)) - .map(ToString::to_string) - .collect::>() - }) - .unwrap_or_default(); - let steam_app_type = store_data - .as_ref() - .and_then(|value| value.get("type")) - .and_then(Value::as_str) - .map(ToString::to_string); - let app_kind = infer_library_item_kind( - steam_app_type.as_deref(), - title.as_deref(), - &genres, - &steam_categories, - ); - let release_date = store_data - .as_ref() - .and_then(|value| value.get("release_date")) - .and_then(|value| value.get("date")) - .and_then(Value::as_str) - .map(ToString::to_string); - let developer = joined_string_array(store_data.as_ref(), "developers"); - let publisher = joined_string_array(store_data.as_ref(), "publishers"); - let header_image = store_data - .as_ref() - .and_then(|value| value.get("header_image")) - .and_then(Value::as_str) - .map(ToString::to_string); - - let source = candidate.source.as_str(); - let cover_image = self.image_cache.cache_first_remote_image( - source, - app_id, - "cover", - "jpg", - &[ - format!("https://cdn.cloudflare.steamstatic.com/steam/apps/{app_id}/library_600x900.jpg"), - format!("https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/{app_id}/library_600x900.jpg"), - format!("https://cdn.cloudflare.steamstatic.com/steam/apps/{app_id}/capsule_616x353.jpg"), - ], - )?; - let hero_urls = [ - Some(format!( - "https://cdn.cloudflare.steamstatic.com/steam/apps/{app_id}/library_hero.jpg" - )), - header_image.clone(), - Some(format!( - "https://cdn.cloudflare.steamstatic.com/steam/apps/{app_id}/header.jpg" - )), - ] - .into_iter() - .flatten() - .collect::>(); - let hero_image = self - .image_cache - .cache_first_remote_image(source, app_id, "hero", "jpg", &hero_urls)?; - let icon_image = self.image_cache.cache_first_remote_image( - source, - app_id, - "icon", - "jpg", - &[ - format!( - "https://cdn.cloudflare.steamstatic.com/steam/apps/{app_id}/capsule_184x69.jpg" - ), - format!("https://cdn.cloudflare.steamstatic.com/steam/apps/{app_id}/header.jpg"), - ], - )?; - - Ok(Some(GameMetadata { - title, - description, - app_kind, - steam_app_type, - genres, - steam_categories, - release_date, - developer, - publisher, - cover_image, - hero_image, - icon_image, - status: MetadataStatus::Matched, - })) - } -} - -fn fetch_steam_store_data(app_id: &str) -> Result, String> { - let url = format!("https://store.steampowered.com/api/appdetails?appids={app_id}"); - let response = match ureq::get(&url) - .header("User-Agent", "NebulaOS/0.1 library-scanner") - .call() - { - Ok(response) => response, - Err(_) => return Ok(None), - }; - - if !response.status().is_success() { - return Ok(None); - } - - let mut response = response; - let body = response - .body_mut() - .read_to_string() - .map_err(|err| format!("Failed to read Steam metadata response: {err}"))?; - let value: Value = serde_json::from_str(&body) - .map_err(|err| format!("Failed to parse Steam metadata: {err}"))?; - - Ok(value - .get(app_id) - .filter(|entry| { - entry - .get("success") - .and_then(Value::as_bool) - .unwrap_or(false) - }) - .and_then(|entry| entry.get("data")) - .cloned()) -} - -fn joined_string_array(value: Option<&Value>, key: &str) -> Option { - value - .and_then(|value| value.get(key)) - .and_then(Value::as_array) - .map(|items| { - items - .iter() - .filter_map(Value::as_str) - .collect::>() - .join(", ") - }) - .filter(|value| !value.is_empty()) -} - -#[allow(dead_code)] -pub struct MetadataMatchPlan { - pub exact_app_id: bool, - pub store_specific_lookup: bool, - pub fuzzy_title_matching: bool, - pub manual_user_correction: bool, -} - -impl Default for MetadataMatchPlan { - fn default() -> Self { - Self { - exact_app_id: true, - store_specific_lookup: true, - fuzzy_title_matching: true, - manual_user_correction: true, - } - } -} diff --git a/Bigscreen/src-tauri/src/library/mod.rs b/Bigscreen/src-tauri/src/library/mod.rs deleted file mode 100644 index f0d5e6e..0000000 --- a/Bigscreen/src-tauri/src/library/mod.rs +++ /dev/null @@ -1,142 +0,0 @@ -pub mod commands; -pub mod db; -pub mod images; -pub mod metadata; -pub mod models; -pub mod scanners; - -use crate::storage::AppStorage; -use db::{ensure_library_schema, list_games, upsert_candidate}; -use images::ImageCache; -use metadata::{LocalCacheMetadataProvider, MetadataProvider}; -use models::{GameCandidate, GameSource, LibraryGame, ScanProviderReport, ScanSummary}; -use scanners::{ - epic::EpicScanner, gog::GogScanner, local::LocalAppScanner, steam::SteamScanner, ScanContext, - ScannerProvider, -}; -use std::collections::HashMap; -use std::path::PathBuf; - -pub fn initialize(storage: &AppStorage) -> Result<(), String> { - let conn = storage.connect_library()?; - ensure_library_schema(&conn) -} - -pub fn list_visible_games(storage: &AppStorage) -> Result, String> { - let conn = storage.connect_library()?; - ensure_library_schema(&conn)?; - list_games(&conn, false) -} - -pub fn scan_library( - storage: AppStorage, - local_folders: Vec, -) -> Result { - let conn = storage.connect_library()?; - ensure_library_schema(&conn)?; - - let context = ScanContext { local_folders }; - - let scanners: Vec> = vec![ - Box::new(SteamScanner::new()), - Box::new(EpicScanner::new()), - Box::new(GogScanner::new()), - Box::new(LocalAppScanner::new()), - ]; - - let metadata_provider = - LocalCacheMetadataProvider::new(ImageCache::new(storage.image_cache_dir)); - let mut provider_reports = Vec::new(); - let mut all_candidates = Vec::new(); - - for scanner in scanners { - let source = scanner.source(); - match scanner.scan(&context) { - Ok(mut candidates) => { - let discovered = candidates.len(); - all_candidates.append(&mut candidates); - provider_reports.push(ScanProviderReport { - source, - discovered, - error: None, - }); - } - Err(error) => { - provider_reports.push(ScanProviderReport { - source, - discovered: 0, - error: Some(error), - }); - } - } - } - - let candidates = dedupe_candidates(all_candidates); - let mut inserted_or_updated = 0; - let mut metadata_matched = 0; - let mut unmatched = 0; - - for candidate in &candidates { - let metadata = metadata_provider.resolve(candidate)?; - if metadata.is_some() { - metadata_matched += 1; - } else { - unmatched += 1; - } - upsert_candidate(&conn, candidate, metadata.as_ref())?; - inserted_or_updated += 1; - } - - let games = list_games(&conn, false)?; - - Ok(ScanSummary { - discovered: candidates.len(), - inserted_or_updated, - metadata_matched, - unmatched, - providers: provider_reports, - games, - }) -} - -fn dedupe_candidates(candidates: Vec) -> Vec { - let mut by_identity: HashMap = HashMap::new(); - - for candidate in candidates { - let key = match (&candidate.source, &candidate.app_id) { - (source, Some(app_id)) => format!("source:{}:{app_id}", source.as_str()), - (_, None) => format!( - "path:{}", - candidate.install_path.to_string_lossy().to_lowercase() - ), - }; - - by_identity.entry(key).or_insert(candidate); - } - - let mut unique: Vec = by_identity.into_values().collect(); - unique.sort_by(|left, right| left.title.to_lowercase().cmp(&right.title.to_lowercase())); - unique -} - -#[allow(dead_code)] -pub struct LibraryExpansionHooks; - -#[allow(dead_code)] -impl LibraryExpansionHooks { - pub const ACHIEVEMENTS_DB: &'static str = "databases/achievements.db"; - pub const SAVES_DB: &'static str = "databases/saves.db"; - pub const MODS_DB: &'static str = "databases/mods.db"; - pub const ACCOUNTS_DB: &'static str = "databases/accounts.db"; -} - -#[allow(dead_code)] -pub trait AccountLinkedLibraryProvider: ScannerProvider { - fn account_provider_id(&self) -> &'static str; -} - -#[allow(dead_code)] -pub trait LaunchProvider { - fn source(&self) -> GameSource; - fn launch(&self, game: &LibraryGame) -> Result<(), String>; -} diff --git a/Bigscreen/src-tauri/src/library/models.rs b/Bigscreen/src-tauri/src/library/models.rs deleted file mode 100644 index 262f911..0000000 --- a/Bigscreen/src-tauri/src/library/models.rs +++ /dev/null @@ -1,249 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; - -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum GameSource { - Steam, - Epic, - Gog, - Local, - Unknown, -} - -impl GameSource { - pub fn as_str(&self) -> &'static str { - match self { - Self::Steam => "steam", - Self::Epic => "epic", - Self::Gog => "gog", - Self::Local => "local", - Self::Unknown => "unknown", - } - } - - pub fn from_str(value: &str) -> Self { - match value { - "steam" => Self::Steam, - "epic" => Self::Epic, - "gog" => Self::Gog, - "local" => Self::Local, - _ => Self::Unknown, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum MetadataStatus { - Pending, - Matched, - NeedsReview, - Manual, -} - -impl MetadataStatus { - pub fn as_str(&self) -> &'static str { - match self { - Self::Pending => "pending", - Self::Matched => "matched", - Self::NeedsReview => "needs_review", - Self::Manual => "manual", - } - } - - pub fn from_str(value: &str) -> Self { - match value { - "matched" => Self::Matched, - "needs_review" => Self::NeedsReview, - "manual" => Self::Manual, - _ => Self::Pending, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum LibraryItemKind { - Game, - Tool, - Software, - Unknown, -} - -impl LibraryItemKind { - pub fn as_str(&self) -> &'static str { - match self { - Self::Game => "game", - Self::Tool => "tool", - Self::Software => "software", - Self::Unknown => "unknown", - } - } - - pub fn from_str(value: &str) -> Self { - match value { - "tool" => Self::Tool, - "software" => Self::Software, - "unknown" => Self::Unknown, - _ => Self::Game, - } - } -} - -pub fn infer_library_item_kind( - steam_app_type: Option<&str>, - title: Option<&str>, - genres: &[String], - categories: &[String], -) -> LibraryItemKind { - if steam_app_type - .map(|value| value.eq_ignore_ascii_case("software")) - .unwrap_or(false) - { - return LibraryItemKind::Software; - } - - if genres.iter().any(|genre| is_tool_label(genre)) - || categories.iter().any(|category| is_tool_label(category)) - || title.map(is_tool_title).unwrap_or(false) - { - return LibraryItemKind::Tool; - } - - if steam_app_type - .map(|value| value.eq_ignore_ascii_case("game")) - .unwrap_or(false) - { - LibraryItemKind::Game - } else { - LibraryItemKind::Unknown - } -} - -fn is_tool_label(value: &str) -> bool { - matches!( - normalize_label(value).as_str(), - "animation & modeling" - | "audio production" - | "design & illustration" - | "education" - | "game development" - | "photo editing" - | "software training" - | "utilities" - | "video production" - | "web publishing" - | "includes level editor" - ) -} - -fn is_tool_title(value: &str) -> bool { - let value = normalize_label(value); - value == "blender" - || value.contains("creation kit") - || value.contains("mod tools") - || value.contains("modding tools") - || value.contains("developer tools") - || value.contains("dedicated server") - || value.ends_with(" sdk") - || value.contains(" editor") -} - -fn normalize_label(value: &str) -> String { - value.trim().to_ascii_lowercase() -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct GameCandidate { - pub title: String, - pub source: GameSource, - pub app_id: Option, - pub install_path: PathBuf, - pub executable_path: Option, - pub launch_command: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct GameMetadata { - pub title: Option, - pub description: Option, - pub app_kind: LibraryItemKind, - pub steam_app_type: Option, - pub genres: Vec, - pub steam_categories: Vec, - pub release_date: Option, - pub developer: Option, - pub publisher: Option, - pub cover_image: Option, - pub hero_image: Option, - pub icon_image: Option, - pub status: MetadataStatus, -} - -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct LibraryGame { - pub id: i64, - pub title: String, - pub platform_source: GameSource, - pub source_store: String, - pub source_app_id: Option, - pub install_path: String, - pub executable_path: Option, - pub launch_command: Option, - pub description: Option, - pub app_kind: LibraryItemKind, - pub steam_app_type: Option, - pub genres: Vec, - pub steam_categories: Vec, - pub release_date: Option, - pub developer: Option, - pub publisher: Option, - pub cover_image: Option, - pub hero_image: Option, - pub icon_image: Option, - pub metadata_status: MetadataStatus, - pub last_scanned: i64, - pub user_hidden: bool, - pub user_favourite: bool, - pub user_title: Option, -} - -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct ScanProviderReport { - pub source: GameSource, - pub discovered: usize, - pub error: Option, -} - -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct ScanSummary { - pub discovered: usize, - pub inserted_or_updated: usize, - pub metadata_matched: usize, - pub unmatched: usize, - pub providers: Vec, - pub games: Vec, -} - -#[derive(Debug, Clone, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct LibraryScanRequest { - #[serde(default)] - pub local_folders: Vec, -} - -#[derive(Debug, Clone, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct GameCustomizationRequest { - pub game_id: i64, - pub title: Option, - pub executable_path: Option, - pub hidden: Option, - pub favourite: Option, -} diff --git a/Bigscreen/src-tauri/src/library/scanners/epic.rs b/Bigscreen/src-tauri/src/library/scanners/epic.rs deleted file mode 100644 index 6af014a..0000000 --- a/Bigscreen/src-tauri/src/library/scanners/epic.rs +++ /dev/null @@ -1,118 +0,0 @@ -use super::ScannerProvider; -use crate::library::models::{GameCandidate, GameSource}; -use crate::library::scanners::ScanContext; -use serde_json::Value; -use std::fs; -use std::path::{Path, PathBuf}; - -pub struct EpicScanner; - -impl EpicScanner { - pub fn new() -> Self { - Self - } -} - -impl ScannerProvider for EpicScanner { - fn source(&self) -> GameSource { - GameSource::Epic - } - - fn scan(&self, _context: &ScanContext) -> Result, String> { - let mut candidates = Vec::new(); - - for manifest_dir in discover_manifest_dirs() { - let entries = match fs::read_dir(manifest_dir) { - Ok(entries) => entries, - Err(_) => continue, - }; - - for entry in entries.flatten() { - let path = entry.path(); - if path.extension().and_then(|value| value.to_str()) != Some("item") { - continue; - } - if let Some(candidate) = parse_manifest(&path)? { - candidates.push(candidate); - } - } - } - - Ok(candidates) - } -} - -fn discover_manifest_dirs() -> Vec { - let mut dirs = Vec::new(); - - #[cfg(target_os = "windows")] - if let Some(program_data) = std::env::var_os("PROGRAMDATA") { - dirs.push( - PathBuf::from(program_data) - .join("Epic") - .join("EpicGamesLauncher") - .join("Data") - .join("Manifests"), - ); - } - - #[cfg(target_os = "macos")] - if let Some(home) = super::home_dir() { - dirs.push( - home.join("Library") - .join("Application Support") - .join("Epic") - .join("EpicGamesLauncher") - .join("Data") - .join("Manifests"), - ); - } - - #[cfg(target_os = "linux")] - if let Some(home) = super::home_dir() { - dirs.push( - home.join(".config") - .join("Epic") - .join("EpicGamesLauncher") - .join("Data") - .join("Manifests"), - ); - } - - dirs.into_iter().filter(|path| path.is_dir()).collect() -} - -fn parse_manifest(path: &Path) -> Result, String> { - let contents = fs::read_to_string(path) - .map_err(|err| format!("Failed to read Epic manifest {}: {err}", path.display()))?; - let value: Value = serde_json::from_str(&contents) - .map_err(|err| format!("Failed to parse Epic manifest {}: {err}", path.display()))?; - - let Some(display_name) = value.get("DisplayName").and_then(Value::as_str) else { - return Ok(None); - }; - let Some(install_location) = value.get("InstallLocation").and_then(Value::as_str) else { - return Ok(None); - }; - - let launch_executable = value - .get("LaunchExecutable") - .and_then(Value::as_str) - .filter(|value| !value.is_empty()); - let catalog_item_id = value - .get("CatalogItemId") - .or_else(|| value.get("MainGameCatalogItemId")) - .and_then(Value::as_str) - .map(ToString::to_string); - let install_path = PathBuf::from(install_location); - let executable_path = launch_executable.map(|executable| install_path.join(executable)); - - Ok(Some(GameCandidate { - title: display_name.to_string(), - source: GameSource::Epic, - app_id: catalog_item_id, - install_path, - executable_path, - launch_command: None, - })) -} diff --git a/Bigscreen/src-tauri/src/library/scanners/gog.rs b/Bigscreen/src-tauri/src/library/scanners/gog.rs deleted file mode 100644 index e95876d..0000000 --- a/Bigscreen/src-tauri/src/library/scanners/gog.rs +++ /dev/null @@ -1,153 +0,0 @@ -use super::{is_probably_game_executable, title_from_path, ScannerProvider}; -use crate::library::models::{GameCandidate, GameSource}; -use crate::library::scanners::ScanContext; -use serde_json::Value; -use std::fs; -use std::path::{Path, PathBuf}; - -pub struct GogScanner; - -impl GogScanner { - pub fn new() -> Self { - Self - } -} - -impl ScannerProvider for GogScanner { - fn source(&self) -> GameSource { - GameSource::Gog - } - - fn scan(&self, _context: &ScanContext) -> Result, String> { - let mut candidates = Vec::new(); - - for root in discover_gog_roots() { - let entries = match fs::read_dir(root) { - Ok(entries) => entries, - Err(_) => continue, - }; - - for entry in entries.flatten() { - let install_path = entry.path(); - if !install_path.is_dir() { - continue; - } - if let Some(candidate) = inspect_install_dir(&install_path)? { - candidates.push(candidate); - } - } - } - - Ok(candidates) - } -} - -fn discover_gog_roots() -> Vec { - let mut roots = Vec::new(); - - #[cfg(target_os = "windows")] - { - if let Some(program_files_x86) = std::env::var_os("PROGRAMFILES(X86)") { - let program_files_x86 = PathBuf::from(program_files_x86); - roots.push(program_files_x86.join("GOG Galaxy").join("Games")); - roots.push(program_files_x86.join("GOG.com").join("Games")); - } - if let Some(program_files) = std::env::var_os("PROGRAMFILES") { - let program_files = PathBuf::from(program_files); - roots.push(program_files.join("GOG Galaxy").join("Games")); - roots.push(program_files.join("GOG.com").join("Games")); - } - } - - #[cfg(any(target_os = "linux", target_os = "macos"))] - if let Some(home) = super::home_dir() { - roots.push(home.join("GOG Games")); - roots.push(home.join("Games").join("GOG")); - } - - roots.into_iter().filter(|path| path.is_dir()).collect() -} - -fn inspect_install_dir(install_path: &Path) -> Result, String> { - let metadata = read_gog_metadata(install_path)?; - let title = metadata - .as_ref() - .and_then(|value| value.get("name")) - .and_then(Value::as_str) - .map(ToString::to_string) - .unwrap_or_else(|| title_from_path(install_path)); - let app_id = metadata - .as_ref() - .and_then(|value| value.get("gameId").or_else(|| value.get("game_id"))) - .and_then(|value| { - value - .as_i64() - .map(|id| id.to_string()) - .or_else(|| value.as_str().map(ToString::to_string)) - }); - let executable_path = find_executable(install_path); - - Ok(Some(GameCandidate { - title, - source: GameSource::Gog, - app_id, - install_path: install_path.to_path_buf(), - executable_path, - launch_command: None, - })) -} - -fn read_gog_metadata(install_path: &Path) -> Result, String> { - let entries = match fs::read_dir(install_path) { - Ok(entries) => entries, - Err(_) => return Ok(None), - }; - - for entry in entries.flatten() { - let path = entry.path(); - let Some(file_name) = path.file_name().and_then(|value| value.to_str()) else { - continue; - }; - if !file_name.starts_with("goggame-") || !file_name.ends_with(".info") { - continue; - } - let contents = fs::read_to_string(&path) - .map_err(|err| format!("Failed to read GOG metadata {}: {err}", path.display()))?; - let value = serde_json::from_str(&contents) - .map_err(|err| format!("Failed to parse GOG metadata {}: {err}", path.display()))?; - return Ok(Some(value)); - } - - Ok(None) -} - -fn find_executable(install_path: &Path) -> Option { - let entries = fs::read_dir(install_path).ok()?; - entries - .flatten() - .map(|entry| entry.path()) - .find(|path| path.is_file() && is_launchable(path) && is_probably_game_executable(path)) -} - -fn is_launchable(path: &Path) -> bool { - #[cfg(target_os = "windows")] - { - return path.extension().and_then(|value| value.to_str()) == Some("exe"); - } - - #[cfg(target_os = "macos")] - { - return path.extension().and_then(|value| value.to_str()) == Some("app"); - } - - #[cfg(target_os = "linux")] - { - if path.extension().and_then(|value| value.to_str()) == Some("desktop") { - return true; - } - return true; - } - - #[allow(unreachable_code)] - false -} diff --git a/Bigscreen/src-tauri/src/library/scanners/local.rs b/Bigscreen/src-tauri/src/library/scanners/local.rs deleted file mode 100644 index b7133ef..0000000 --- a/Bigscreen/src-tauri/src/library/scanners/local.rs +++ /dev/null @@ -1,146 +0,0 @@ -use super::{is_probably_game_executable, title_from_path, ScannerProvider}; -use crate::library::models::{GameCandidate, GameSource}; -use crate::library::scanners::ScanContext; -use std::fs; -use std::path::{Path, PathBuf}; - -pub struct LocalAppScanner; - -impl LocalAppScanner { - pub fn new() -> Self { - Self - } -} - -impl ScannerProvider for LocalAppScanner { - fn source(&self) -> GameSource { - GameSource::Local - } - - fn scan(&self, context: &ScanContext) -> Result, String> { - let mut candidates = Vec::new(); - - for folder in &context.local_folders { - if !folder.is_dir() { - continue; - } - scan_folder(folder, 0, &mut candidates)?; - } - - Ok(candidates) - } -} - -fn scan_folder( - folder: &Path, - depth: usize, - candidates: &mut Vec, -) -> Result<(), String> { - if depth > 4 { - return Ok(()); - } - - let entries = match fs::read_dir(folder) { - Ok(entries) => entries, - Err(_) => return Ok(()), - }; - - let mut executables = Vec::new(); - let mut child_dirs = Vec::new(); - - for entry in entries.flatten() { - let path = entry.path(); - if path.is_dir() { - if should_descend(&path) { - child_dirs.push(path); - } - continue; - } - - if is_launchable_file(&path) && is_probably_game_executable(&path) { - executables.push(path); - } - } - - if let Some(executable_path) = pick_best_executable(&executables) { - let title_path = executable_path.parent().unwrap_or(folder); - candidates.push(GameCandidate { - title: title_from_path(title_path), - source: GameSource::Local, - app_id: None, - install_path: title_path.to_path_buf(), - executable_path: Some(executable_path), - launch_command: None, - }); - return Ok(()); - } - - for child in child_dirs { - scan_folder(&child, depth + 1, candidates)?; - } - - Ok(()) -} - -fn should_descend(path: &Path) -> bool { - let name = path - .file_name() - .and_then(|value| value.to_str()) - .unwrap_or_default() - .to_lowercase(); - !matches!( - name.as_str(), - "_commonredist" | "redist" | "redistributables" | "support" | "directx" | "vc_redist" - ) -} - -fn is_launchable_file(path: &Path) -> bool { - #[cfg(target_os = "windows")] - { - return path.extension().and_then(|value| value.to_str()) == Some("exe"); - } - - #[cfg(target_os = "macos")] - { - if path.extension().and_then(|value| value.to_str()) == Some("app") { - return true; - } - } - - #[cfg(target_os = "linux")] - { - if path.extension().and_then(|value| value.to_str()) == Some("desktop") { - return true; - } - return has_executable_bit(path); - } - - #[allow(unreachable_code)] - false -} - -fn pick_best_executable(executables: &[PathBuf]) -> Option { - executables - .iter() - .min_by_key(|path| { - let file_name = path - .file_name() - .and_then(|value| value.to_str()) - .unwrap_or_default() - .to_lowercase(); - if file_name.contains("launcher") { - 1 - } else { - 0 - } - }) - .cloned() -} - -#[cfg(target_os = "linux")] -fn has_executable_bit(path: &Path) -> bool { - use std::os::unix::fs::PermissionsExt; - fs::metadata(path) - .map(|metadata| metadata.permissions().mode() & 0o111 != 0) - .unwrap_or(false) -} diff --git a/Bigscreen/src-tauri/src/library/scanners/mod.rs b/Bigscreen/src-tauri/src/library/scanners/mod.rs deleted file mode 100644 index c72fb8f..0000000 --- a/Bigscreen/src-tauri/src/library/scanners/mod.rs +++ /dev/null @@ -1,69 +0,0 @@ -use super::models::{GameCandidate, GameSource}; -use std::path::PathBuf; - -pub mod epic; -pub mod gog; -pub mod local; -pub mod steam; - -#[derive(Debug, Clone)] -pub struct ScanContext { - pub local_folders: Vec, -} - -pub trait ScannerProvider: Send + Sync { - fn source(&self) -> GameSource; - fn scan(&self, context: &ScanContext) -> Result, String>; -} - -#[cfg(any(target_os = "linux", target_os = "macos"))] -pub fn home_dir() -> Option { - std::env::var_os("HOME") - .map(PathBuf::from) - .or_else(|| std::env::var_os("USERPROFILE").map(PathBuf::from)) -} - -pub fn is_probably_game_executable(path: &std::path::Path) -> bool { - let file_name = path - .file_name() - .and_then(|name| name.to_str()) - .unwrap_or_default() - .to_lowercase(); - - if file_name.is_empty() { - return false; - } - - let ignored_fragments = [ - "unins", - "uninstall", - "setup", - "install", - "redist", - "vcredist", - "crash", - "reporter", - "benchmark", - "launcherhelper", - "unitycrashhandler", - ]; - - !ignored_fragments - .iter() - .any(|fragment| file_name.contains(fragment)) -} - -pub fn title_from_path(path: &std::path::Path) -> String { - path.file_stem() - .or_else(|| path.file_name()) - .and_then(|name| name.to_str()) - .map(|value| { - value - .replace(['_', '-', '.'], " ") - .split_whitespace() - .collect::>() - .join(" ") - }) - .filter(|value| !value.is_empty()) - .unwrap_or_else(|| "Unknown Game".to_string()) -} diff --git a/Bigscreen/src-tauri/src/library/scanners/steam.rs b/Bigscreen/src-tauri/src/library/scanners/steam.rs deleted file mode 100644 index ea69143..0000000 --- a/Bigscreen/src-tauri/src/library/scanners/steam.rs +++ /dev/null @@ -1,227 +0,0 @@ -use super::ScannerProvider; -use crate::library::models::{GameCandidate, GameSource}; -use crate::library::scanners::ScanContext; -use std::collections::HashSet; -use std::fs; -use std::path::{Path, PathBuf}; - -pub struct SteamScanner; - -impl SteamScanner { - pub fn new() -> Self { - Self - } -} - -impl ScannerProvider for SteamScanner { - fn source(&self) -> GameSource { - GameSource::Steam - } - - fn scan(&self, _context: &ScanContext) -> Result, String> { - let mut candidates = Vec::new(); - let mut library_paths = Vec::new(); - - for steam_root in discover_steam_roots() { - library_paths.extend(discover_library_paths(&steam_root)); - } - - library_paths.extend(discover_drive_steam_libraries()); - - for library_path in unique_existing_dirs(library_paths) { - candidates.extend(scan_library_path(&library_path)?); - } - - Ok(candidates) - } -} - -fn discover_steam_roots() -> Vec { - let mut roots = Vec::new(); - - #[cfg(target_os = "windows")] - { - if let Some(program_files_x86) = std::env::var_os("PROGRAMFILES(X86)") { - roots.push(PathBuf::from(program_files_x86).join("Steam")); - } - if let Some(program_files) = std::env::var_os("PROGRAMFILES") { - roots.push(PathBuf::from(program_files).join("Steam")); - } - } - - #[cfg(target_os = "linux")] - if let Some(home) = super::home_dir() { - roots.push(home.join(".steam").join("steam")); - roots.push(home.join(".local").join("share").join("Steam")); - } - - #[cfg(target_os = "macos")] - if let Some(home) = super::home_dir() { - roots.push( - home.join("Library") - .join("Application Support") - .join("Steam"), - ); - } - - unique_existing_dirs(roots) -} - -fn discover_library_paths(steam_root: &Path) -> Vec { - let mut libraries = vec![steam_root.to_path_buf()]; - let library_file = steam_root.join("steamapps").join("libraryfolders.vdf"); - - if let Ok(contents) = fs::read_to_string(library_file) { - for path in extract_vdf_values(&contents, "path") { - libraries.push(PathBuf::from(path.replace("\\\\", "\\"))); - } - - // Older Steam clients used numeric keys directly for library paths. - for (key, value) in extract_vdf_pairs(&contents) { - if key.parse::().is_ok() && value.contains(['\\', '/']) { - libraries.push(PathBuf::from(value.replace("\\\\", "\\"))); - } - } - } - - unique_existing_dirs(libraries) -} - -fn scan_library_path(library_path: &Path) -> Result, String> { - let steamapps = library_path.join("steamapps"); - let manifests = match fs::read_dir(&steamapps) { - Ok(entries) => entries, - Err(_) => return Ok(Vec::new()), - }; - - let mut candidates = Vec::new(); - for entry in manifests.flatten() { - let path = entry.path(); - let file_name = path - .file_name() - .and_then(|name| name.to_str()) - .unwrap_or_default(); - if !file_name.starts_with("appmanifest_") || !file_name.ends_with(".acf") { - continue; - } - - if let Some(candidate) = parse_app_manifest(&steamapps, &path)? { - candidates.push(candidate); - } - } - - Ok(candidates) -} - -fn discover_drive_steam_libraries() -> Vec { - #[cfg(target_os = "windows")] - { - ('A'..='Z') - .map(|letter| PathBuf::from(format!("{letter}:\\SteamLibrary"))) - .filter(|path| path.join("steamapps").is_dir()) - .collect() - } - - #[cfg(not(target_os = "windows"))] - { - Vec::new() - } -} - -fn parse_app_manifest( - steamapps: &Path, - manifest_path: &Path, -) -> Result, String> { - let contents = fs::read_to_string(manifest_path).map_err(|err| { - format!( - "Failed to read Steam manifest {}: {err}", - manifest_path.display() - ) - })?; - let app_id = extract_vdf_values(&contents, "appid").into_iter().next(); - let title = extract_vdf_values(&contents, "name").into_iter().next(); - let install_dir = extract_vdf_values(&contents, "installdir") - .into_iter() - .next(); - - let Some(app_id) = app_id else { - return Ok(None); - }; - let Some(title) = title else { - return Ok(None); - }; - let Some(install_dir) = install_dir else { - return Ok(None); - }; - - Ok(Some(GameCandidate { - title, - source: GameSource::Steam, - app_id: Some(app_id.clone()), - install_path: steamapps.join("common").join(install_dir), - executable_path: None, - launch_command: Some(format!("steam://rungameid/{app_id}")), - })) -} - -fn extract_vdf_values(contents: &str, wanted_key: &str) -> Vec { - extract_vdf_pairs(contents) - .into_iter() - .filter_map(|(key, value)| if key == wanted_key { Some(value) } else { None }) - .collect() -} - -fn extract_vdf_pairs(contents: &str) -> Vec<(String, String)> { - contents - .lines() - .filter_map(|line| { - let parts = quoted_tokens(line); - if parts.len() >= 2 { - Some((parts[0].clone(), parts[1].clone())) - } else { - None - } - }) - .collect() -} - -fn quoted_tokens(line: &str) -> Vec { - let mut tokens = Vec::new(); - let mut current = String::new(); - let mut in_quote = false; - let mut escaped = false; - - for ch in line.chars() { - if escaped { - current.push(ch); - escaped = false; - continue; - } - if ch == '\\' && in_quote { - escaped = true; - continue; - } - if ch == '"' { - if in_quote { - tokens.push(current.clone()); - current.clear(); - } - in_quote = !in_quote; - continue; - } - if in_quote { - current.push(ch); - } - } - - tokens -} - -fn unique_existing_dirs(paths: Vec) -> Vec { - let mut seen = HashSet::new(); - paths - .into_iter() - .filter(|path| path.is_dir()) - .filter(|path| seen.insert(path.to_string_lossy().to_lowercase())) - .collect() -} diff --git a/Bigscreen/src-tauri/src/main.rs b/Bigscreen/src-tauri/src/main.rs deleted file mode 100644 index 795c4ea..0000000 --- a/Bigscreen/src-tauri/src/main.rs +++ /dev/null @@ -1,9 +0,0 @@ -// Prevents additional console window on Windows in release, DO NOT REMOVE!! -#![cfg_attr( - all(target_os = "windows", not(debug_assertions)), - windows_subsystem = "windows" -)] - -fn main() { - nebula_os_lib::run() -} diff --git a/Bigscreen/src-tauri/src/storage.rs b/Bigscreen/src-tauri/src/storage.rs deleted file mode 100644 index 406b06f..0000000 --- a/Bigscreen/src-tauri/src/storage.rs +++ /dev/null @@ -1,48 +0,0 @@ -use rusqlite::Connection; -use std::fs; -use std::path::PathBuf; - -#[derive(Clone)] -pub struct AppStorage { - #[allow(dead_code)] - pub app_data_dir: PathBuf, - #[allow(dead_code)] - pub databases_dir: PathBuf, - #[allow(dead_code)] - pub cache_dir: PathBuf, - pub image_cache_dir: PathBuf, - pub users_db_path: PathBuf, - pub library_db_path: PathBuf, -} - -impl AppStorage { - pub fn new(app_data_dir: PathBuf) -> Result { - let databases_dir = app_data_dir.join("databases"); - let cache_dir = app_data_dir.join("cache"); - let image_cache_dir = cache_dir.join("images"); - - fs::create_dir_all(&databases_dir) - .map_err(|err| format!("Failed to create database directory: {err}"))?; - fs::create_dir_all(&image_cache_dir) - .map_err(|err| format!("Failed to create image cache directory: {err}"))?; - - Ok(Self { - users_db_path: databases_dir.join("users.db"), - library_db_path: databases_dir.join("library.db"), - app_data_dir, - databases_dir, - cache_dir, - image_cache_dir, - }) - } - - pub fn connect_users(&self) -> Result { - Connection::open(&self.users_db_path) - .map_err(|err| format!("Failed to open users database: {err}")) - } - - pub fn connect_library(&self) -> Result { - Connection::open(&self.library_db_path) - .map_err(|err| format!("Failed to open library database: {err}")) - } -} diff --git a/Bigscreen/src-tauri/tauri.conf.json b/Bigscreen/src-tauri/tauri.conf.json deleted file mode 100644 index f91f7f2..0000000 --- a/Bigscreen/src-tauri/tauri.conf.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "https://schema.tauri.app/config/2", - "productName": "nebula-os", - "version": "0.1.0", - "identifier": "com.user.nebula-os", - "build": { - "frontendDist": "../src" - }, - "app": { - "withGlobalTauri": true, - "windows": [ - { - "title": "nebula-os", - "width": 800, - "height": 600, - "fullscreen": true, - "decorations": false - } - ], - "security": { - "csp": null, - "assetProtocol": { - "enable": true, - "scope": ["$APPDATA/**"] - } - } - }, - "bundle": { - "active": true, - "targets": "all", - "icon": [ - "icons/32x32.png", - "icons/128x128.png", - "icons/128x128@2x.png", - "icons/icon.icns", - "icons/icon.ico" - ] - } -} diff --git a/Bigscreen/src/InputRouter.cpp b/Bigscreen/src/InputRouter.cpp new file mode 100644 index 0000000..5446120 --- /dev/null +++ b/Bigscreen/src/InputRouter.cpp @@ -0,0 +1,49 @@ +#include "InputRouter.h" + +#include + +InputRouter::InputRouter(QObject *parent) + : QObject(parent) +{ +} + +bool InputRouter::handleKeyPress(QKeyEvent *event) +{ + if (!event || event->isAutoRepeat()) { + return false; + } + + const Action action = mapKey(event->key()); + if (action == Action::None) { + return false; + } + + emit actionTriggered(action); + event->accept(); + return true; +} + +InputRouter::Action InputRouter::mapKey(int key) +{ + switch (key) { + case Qt::Key_Up: + return Action::Up; + case Qt::Key_Down: + return Action::Down; + case Qt::Key_Left: + return Action::Left; + case Qt::Key_Right: + return Action::Right; + case Qt::Key_Return: + case Qt::Key_Enter: + case Qt::Key_Space: + return Action::Accept; + case Qt::Key_Escape: + case Qt::Key_Backspace: + return Action::Back; + case Qt::Key_Menu: + return Action::Menu; + default: + return Action::None; + } +} diff --git a/Bigscreen/src/InputRouter.h b/Bigscreen/src/InputRouter.h new file mode 100644 index 0000000..64e203a --- /dev/null +++ b/Bigscreen/src/InputRouter.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +class QKeyEvent; + +class InputRouter : public QObject +{ + Q_OBJECT + +public: + enum class Action { + None = 0, + Up, + Down, + Left, + Right, + Accept, + Back, + Menu, + }; + Q_ENUM(Action) + + explicit InputRouter(QObject *parent = nullptr); + + bool handleKeyPress(QKeyEvent *event); + +signals: + void actionTriggered(InputRouter::Action action); + +private: + static Action mapKey(int key); +}; diff --git a/Bigscreen/src/assets/javascript.svg b/Bigscreen/src/assets/javascript.svg deleted file mode 100644 index f9abb2b..0000000 --- a/Bigscreen/src/assets/javascript.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/Bigscreen/src/assets/tauri.svg b/Bigscreen/src/assets/tauri.svg deleted file mode 100644 index 31b62c9..0000000 --- a/Bigscreen/src/assets/tauri.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/Bigscreen/src/core/gamepadProfile.js b/Bigscreen/src/core/gamepadProfile.js deleted file mode 100644 index 839c097..0000000 --- a/Bigscreen/src/core/gamepadProfile.js +++ /dev/null @@ -1,183 +0,0 @@ -const PROFILE_GLYPH_MAP = { - xbox: { - accept: "A", - back: "B", - menu: "☰", - up: "↑", - down: "↓", - left: "←", - right: "→", - l1: "LB", - r1: "RB", - l2: "LT", - r2: "RT", - clear: "X", - y: "Y", - }, - playstation: { - accept: "✕", - back: "○", - menu: "☰", - up: "↑", - down: "↓", - left: "←", - right: "→", - l1: "L1", - r1: "R1", - l2: "L2", - r2: "R2", - clear: "□", - y: "△", - }, - switch: { - accept: "B", - back: "A", - menu: "+", - up: "↑", - down: "↓", - left: "←", - right: "→", - l1: "L", - r1: "R", - l2: "ZL", - r2: "ZR", - clear: "Y", - y: "X", - }, - generic: { - accept: "A", - back: "B", - menu: "☰", - up: "↑", - down: "↓", - left: "←", - right: "→", - l1: "L1", - r1: "R1", - l2: "L2", - r2: "R2", - clear: "X", - y: "Y", - }, -}; - -const GLYPH_ACTIONS = [ - "accept", - "back", - "menu", - "up", - "down", - "left", - "right", - "l1", - "r1", - "l2", - "r2", - "clear", - "y", -]; - -const CORE_GLYPH_IDS = { - accept: "confirm", - back: "back", - menu: "menu", - up: "dpad-up", - down: "dpad-down", - left: "dpad-left", - right: "dpad-right", - l1: "lb", - r1: "rb", - l2: "lt", - r2: "rt", - clear: "x", - y: "y", -}; - -export const CONTROLLER_PROFILES = ["xbox", "playstation", "switch", "generic"]; - -export const PROFILE_LABELS = { - xbox: "Xbox", - playstation: "PlayStation", - switch: "Nintendo Switch", - generic: "controller", -}; - -export const detectGamepadProfile = (gamepad) => { - const id = `${gamepad?.id ?? ""} ${gamepad?.mapping ?? ""}`.toLowerCase(); - - if (/xbox|x-input|microsoft|045e|8bitdo.*xbox|8bitdo.*x/i.test(id)) { - return "xbox"; - } - - if (/playstation|dualsense|dualshock|ps5|ps4|054c|sony|wireless controller/i.test(id)) { - return "playstation"; - } - - if (/nintendo|switch|pro controller|057e|joy-con|joycon/i.test(id)) { - return "switch"; - } - - return "generic"; -}; - -export const getActiveGamepad = () => { - const pads = navigator.getGamepads?.() ?? []; - return pads.find((pad) => pad?.connected) ?? null; -}; - -export const detectActiveProfile = () => { - const pad = getActiveGamepad(); - return pad ? detectGamepadProfile(pad) : "generic"; -}; - -export const getFallbackGlyphs = (profile = "generic") => ({ - ...(PROFILE_GLYPH_MAP[profile] ?? PROFILE_GLYPH_MAP.generic), -}); - -export const resolveGlyphsForProfile = (profile, glyphsModule) => { - const safeProfile = CONTROLLER_PROFILES.includes(profile) ? profile : "generic"; - const platform = safeProfile === "generic" ? "xbox" : safeProfile; - const fallback = getFallbackGlyphs(safeProfile); - - if (typeof glyphsModule?.getGlyph !== "function") { - return fallback; - } - - const resolved = {}; - GLYPH_ACTIONS.forEach((action) => { - const glyphId = CORE_GLYPH_IDS[action]; - resolved[action] = glyphsModule.getGlyph(platform, glyphId) ?? fallback[action]; - }); - return resolved; -}; - -export const watchGamepadProfile = (onChange) => { - let current = detectActiveProfile(); - - const emitIfChanged = () => { - const next = detectActiveProfile(); - if (next === current) { - return; - } - current = next; - onChange(next); - }; - - window.addEventListener("gamepadconnected", emitIfChanged); - window.addEventListener("gamepaddisconnected", emitIfChanged); - - let rafId = 0; - const poll = () => { - emitIfChanged(); - rafId = requestAnimationFrame(poll); - }; - rafId = requestAnimationFrame(poll); - - return () => { - window.removeEventListener("gamepadconnected", emitIfChanged); - window.removeEventListener("gamepaddisconnected", emitIfChanged); - if (rafId) { - cancelAnimationFrame(rafId); - } - }; -}; diff --git a/Bigscreen/src/core/guideSidebar.js b/Bigscreen/src/core/guideSidebar.js deleted file mode 100644 index d8a3768..0000000 --- a/Bigscreen/src/core/guideSidebar.js +++ /dev/null @@ -1,478 +0,0 @@ -import { - NAVIGABLE_VIEWS, - PRIMARY_NAV, - QUICK_ACTIONS, - RECENT_ITEMS, - SOURCE_LABELS, -} from "./sidebarData.js"; - -const escapeHtml = (value) => - String(value) - .replaceAll("&", "&") - .replaceAll("<", "<") - .replaceAll(">", ">") - .replaceAll('"', """); - -const initialsFor = (name) => - name - .split(/\s+/) - .filter(Boolean) - .slice(0, 2) - .map((part) => part[0]?.toUpperCase()) - .join("") || "NU"; - -const renderRailNav = () => - PRIMARY_NAV.map( - (item, index) => ` -

  • - -
  • - `, - ).join(""); - -const renderPanelNav = () => - PRIMARY_NAV.map( - (item, index) => ` -
  • - - ${escapeHtml(item.label)} -
  • - `, - ).join(""); - -const renderQuickActions = () => - QUICK_ACTIONS.map( - (item, index) => ` - - `, - ).join(""); - -const renderRecentItems = () => { - if (!RECENT_ITEMS.length) { - return ` -
    - -

    No recent activity

    -

    Launch a game from your library to see it here.

    -
    - `; - } - - return RECENT_ITEMS.map( - (item, index) => ` - - `, - ).join(""); -}; - -const GUIDE_MARKUP = ` - -`; - -export const createGuideSidebar = ({ - state, - renderView, - openPowerMenu, - openGuidePanel, - onGuideChange, -}) => { - let shellEl = null; - let backdropEl = null; - let panelEl = null; - let expanded = false; - let lastFocusedKey = null; - - const syncProfile = () => { - if (!shellEl) return; - const name = state.profileName || "Nebula User"; - const initials = initialsFor(name); - shellEl.querySelectorAll("[data-profile-name]").forEach((el) => { - el.textContent = name; - }); - shellEl.querySelectorAll("[data-profile-avatar], .guide-rail-avatar, .guide-footer-avatar").forEach((el) => { - el.textContent = initials; - el.setAttribute("aria-label", name); - }); - }; - - const setExpanded = (next) => { - expanded = next; - document.body.classList.toggle("guide-expanded", expanded); - - if (panelEl) { - panelEl.hidden = !expanded; - panelEl.setAttribute("aria-hidden", expanded ? "false" : "true"); - } - - if (backdropEl) { - backdropEl.hidden = !expanded; - } - - shellEl?.querySelectorAll("[data-guide-action='toggle'], .guide-rail-brand").forEach((btn) => { - btn.setAttribute("aria-expanded", expanded ? "true" : "false"); - }); - - onGuideChange?.({ expanded }); - }; - - const open = () => { - if (expanded) return; - const focused = document.querySelector(".is-focused"); - lastFocusedKey = focused?.dataset?.focusKey ?? null; - setExpanded(true); - window.dispatchEvent(new CustomEvent("nebula-guide-open")); - }; - - const close = () => { - if (!expanded) return; - setExpanded(false); - window.dispatchEvent( - new CustomEvent("nebula-guide-close", { - detail: { focusKey: lastFocusedKey }, - }), - ); - }; - - const toggle = () => { - if (expanded) { - close(); - } else { - open(); - } - }; - - const navigateTo = (target) => { - if (!target || !NAVIGABLE_VIEWS.has(target)) { - return false; - } - close(); - renderView(target); - return true; - }; - - const handleGuideAction = (element) => { - const action = element?.dataset?.guideAction; - if (action === "toggle") { - toggle(); - return true; - } - if (action === "power") { - close(); - openPowerMenu?.(); - return true; - } - if (action === "profile") { - console.log("[Guide] Profile panel (placeholder)"); - return true; - } - - const panel = element?.dataset?.panel; - if (panel) { - close(); - openGuidePanel?.(panel); - return true; - } - - return false; - }; - - const handleAccept = (focused) => { - if (!focused) return false; - - const target = focused.dataset.target; - if (target && focused.dataset.navRegion) { - return navigateTo(target); - } - - if (focused.dataset.recentId) { - console.log(`[Guide] Recent item: ${focused.dataset.recentId}`); - return true; - } - - return handleGuideAction(focused); - }; - - const updateActiveView = (viewId) => { - if (!shellEl) return; - shellEl.querySelectorAll("[data-sidebar-nav]").forEach((item) => { - const matches = item.dataset.sidebarNav === viewId; - item.classList.toggle("is-active", matches); - if (matches) { - item.setAttribute("aria-current", "page"); - } else { - item.removeAttribute("aria-current"); - } - }); - }; - - const bindPointer = () => { - shellEl?.addEventListener("click", (event) => { - const item = event.target.closest("[data-focusable='true']"); - if (!item || item.dataset.disabled === "true") return; - - if (item.dataset.guideAction === "toggle" && !expanded) { - open(); - return; - } - - if (item.dataset.target || item.dataset.guideAction || item.dataset.recentId || item.dataset.panel) { - handleAccept(item); - window.dispatchEvent( - new CustomEvent("nebula-navigation-refresh", { - detail: { focusKey: item.dataset.focusKey }, - }), - ); - } - }); - - backdropEl?.addEventListener("click", () => close()); - }; - - const mount = ({ shellRoot, backdropRoot }) => { - shellRoot.innerHTML = GUIDE_MARKUP; - shellEl = shellRoot.querySelector("#guide-sidebar") ?? shellRoot; - panelEl = shellRoot.querySelector("#guide-panel"); - backdropEl = backdropRoot; - syncProfile(); - bindPointer(); - setExpanded(false); - }; - - const getFocusRoots = () => { - if (!shellEl || document.body.classList.contains("body-no-sidebar")) { - return []; - } - if (expanded) { - const root = shellEl.querySelector("[data-guide-focus-root]"); - return root ? [root] : []; - } - const rail = shellEl.querySelector("[data-guide-rail]"); - return rail ? [rail] : []; - }; - - return { - mount, - open, - close, - toggle, - isExpanded: () => expanded, - updateActiveView, - getFocusRoots, - handleAccept, - syncProfile, - }; -}; diff --git a/Bigscreen/src/core/input.js b/Bigscreen/src/core/input.js deleted file mode 100644 index 45c7e70..0000000 --- a/Bigscreen/src/core/input.js +++ /dev/null @@ -1,324 +0,0 @@ -const KEYBOARD_MAP = { - ArrowUp: "up", - ArrowDown: "down", - ArrowLeft: "left", - ArrowRight: "right", - KeyY: "y", - KeyX: "clear", - KeyQ: "l1", - KeyE: "r1", - KeyZ: "l2", - KeyC: "r2", - KeyG: "guide", - Enter: "accept", - Escape: "back", - Backspace: "back", -}; - -const BUTTON_MAP = { - 0: "accept", - 1: "back", - 2: "clear", - 3: "y", - 8: "menu", - 9: "menu", - 4: "l1", - 5: "r1", - 6: "l2", - 7: "r2", - 12: "up", - 13: "down", - 14: "left", - 15: "right", -}; - -const createFallbackEmitter = () => { - const listeners = new Set(); - return { - emit: (payload) => listeners.forEach((listener) => listener(payload)), - on: (listener) => { - listeners.add(listener); - return () => listeners.delete(listener); - }, - }; -}; - -export const createInputManager = ({ onAction, actions }) => { - const heldButtons = new Set(); - const heldVirtual = new Set(); - let rafId = 0; - let mapper = null; - let unsubscribeMapper = null; - let axisLatched = false; - let lastAxisEmitAt = 0; - let active = false; - - const emitter = createFallbackEmitter(); - - const emitAction = (action) => { - if (!actions.includes(action)) { - return; - } - onAction(action); - emitter.emit(action); - }; - - const mapKeyboard = async () => { - try { - const coreInput = await import("@nebulaproject/core/input"); - if (typeof coreInput.createActionMapper !== "function") { - return; - } - - mapper = coreInput.createActionMapper({ - bindings: { - up: [ - { source: "keyboard", control: "ArrowUp" }, - { source: "gamepad", control: "dpad-up" }, - { source: "gamepad", control: "axis-up" }, - ], - down: [ - { source: "keyboard", control: "ArrowDown" }, - { source: "gamepad", control: "dpad-down" }, - { source: "gamepad", control: "axis-down" }, - ], - left: [ - { source: "keyboard", control: "ArrowLeft" }, - { source: "gamepad", control: "dpad-left" }, - { source: "gamepad", control: "axis-left" }, - ], - right: [ - { source: "keyboard", control: "ArrowRight" }, - { source: "gamepad", control: "dpad-right" }, - { source: "gamepad", control: "axis-right" }, - ], - accept: [ - { source: "keyboard", control: "Enter" }, - { source: "gamepad", control: "a" }, - ], - back: [ - { source: "keyboard", control: "Escape" }, - { source: "keyboard", control: "Backspace" }, - { source: "gamepad", control: "b" }, - ], - menu: [ - { source: "keyboard", control: "KeyM" }, - { source: "gamepad", control: "start" }, - ], - guide: [ - { source: "keyboard", control: "KeyG" }, - ], - clear: [ - { source: "keyboard", control: "KeyX" }, - { source: "gamepad", control: "x" }, - ], - y: [ - { source: "keyboard", control: "KeyY" }, - { source: "gamepad", control: "y" }, - ], - l1: [{ source: "gamepad", control: "lb" }], - r1: [{ source: "gamepad", control: "rb" }], - l2: [ - { source: "keyboard", control: "KeyZ" }, - { source: "gamepad", control: "lt" }, - ], - r2: [ - { source: "keyboard", control: "KeyC" }, - { source: "gamepad", control: "rt" }, - ], - }, - }); - - unsubscribeMapper = mapper.onAction((update) => { - if (update?.active && update.action) { - emitAction(update.action); - } - }); - } catch (_error) { - mapper = null; - } - }; - - const mapEventToMapper = (event) => { - mapper?.mapEvent?.(event); - }; - - const onKeyDown = (event) => { - const action = - KEYBOARD_MAP[event.code] ?? - (event.code === "KeyM" ? "menu" : event.code === "KeyG" ? "guide" : null); - if (!action) { - return; - } - event.preventDefault(); - - if (mapper) { - mapEventToMapper({ - source: "keyboard", - control: event.code, - type: "pressed", - value: 1, - }); - } else if (!heldVirtual.has(event.code)) { - heldVirtual.add(event.code); - emitAction(action); - } - }; - - const onKeyUp = (event) => { - if (mapper) { - mapEventToMapper({ - source: "keyboard", - control: event.code, - type: "released", - value: 0, - }); - } - heldVirtual.delete(event.code); - }; - - const processAxis = (axisX, axisY) => { - const threshold = 0.6; - const now = performance.now(); - let axisAction = null; - - if (Math.abs(axisX) > Math.abs(axisY)) { - if (axisX <= -threshold) axisAction = "left"; - if (axisX >= threshold) axisAction = "right"; - } else { - if (axisY <= -threshold) axisAction = "up"; - if (axisY >= threshold) axisAction = "down"; - } - - if (!axisAction) { - axisLatched = false; - return; - } - - if (axisLatched || now - lastAxisEmitAt < 120) { - return; - } - - if (mapper) { - mapEventToMapper({ - source: "gamepad", - control: `axis-${axisAction}`, - type: "axis", - value: 1, - }); - } else { - emitAction(axisAction); - } - - axisLatched = true; - lastAxisEmitAt = now; - }; - - const pollGamepad = () => { - if (!active) { - return; - } - - const [pad] = navigator.getGamepads?.() ?? []; - if (pad) { - pad.buttons.forEach((button, index) => { - const action = BUTTON_MAP[index]; - if (!action) { - return; - } - - if (button.pressed && !heldButtons.has(index)) { - heldButtons.add(index); - - if (mapper) { - const controlMap = { - accept: "a", - back: "b", - clear: "x", - y: "y", - menu: "start", - l1: "lb", - r1: "rb", - l2: "lt", - r2: "rt", - up: "dpad-up", - down: "dpad-down", - left: "dpad-left", - right: "dpad-right", - }; - - mapEventToMapper({ - source: "gamepad", - control: controlMap[action] ?? action, - type: "pressed", - value: 1, - }); - } else { - emitAction(action); - } - } - - if (!button.pressed && heldButtons.has(index)) { - heldButtons.delete(index); - if (mapper) { - const releaseControlMap = { - accept: "a", - back: "b", - clear: "x", - y: "y", - menu: "start", - l1: "lb", - r1: "rb", - l2: "lt", - r2: "rt", - up: "dpad-up", - down: "dpad-down", - left: "dpad-left", - right: "dpad-right", - }; - - mapEventToMapper({ - source: "gamepad", - control: releaseControlMap[action] ?? action, - type: "released", - value: 0, - }); - } - } - }); - - processAxis(pad.axes?.[0] ?? 0, pad.axes?.[1] ?? 0); - } - - rafId = requestAnimationFrame(pollGamepad); - }; - - const start = async () => { - if (active) { - return; - } - active = true; - await mapKeyboard(); - window.addEventListener("keydown", onKeyDown); - window.addEventListener("keyup", onKeyUp); - rafId = requestAnimationFrame(pollGamepad); - }; - - const stop = () => { - active = false; - window.removeEventListener("keydown", onKeyDown); - window.removeEventListener("keyup", onKeyUp); - if (rafId) { - cancelAnimationFrame(rafId); - rafId = 0; - } - unsubscribeMapper?.(); - unsubscribeMapper = null; - }; - - return { - start, - stop, - onAction: emitter.on, - }; -}; diff --git a/Bigscreen/src/core/nav.js b/Bigscreen/src/core/nav.js deleted file mode 100644 index e827000..0000000 --- a/Bigscreen/src/core/nav.js +++ /dev/null @@ -1,451 +0,0 @@ -// Spatial controller navigation. -// -// Given a focused element ("source") and a direction (up/down/left/right) we pick -// the next focusable by physical screen position rather than by an authoring-time -// grid (row/col data attributes). The screen geometry is the source of truth that -// the player sees, so it is also what the controller should follow. -// -// The algorithm: -// 1. Filter candidates to those whose *center* is strictly past the source's -// edge in the requested direction. Center-vs-edge avoids picking elements -// that merely brush the source from the side. -// 2. Prefer candidates that overlap the source on the perpendicular axis. A -// candidate sharing your row when you press right (or your column when you -// press up/down) is almost always the right answer. -// 3. If no overlapping candidate exists, fall back to candidates inside a 60° -// cone from the source center pointing in the requested direction. Bias -// heavily against off-axis drift. -// 4. Remember the "preferred" perpendicular center while the player presses the -// same axis repeatedly so vertical traversal doesn't slide sideways across -// rows of unequal width. -// 5. Sidebar is treated as a separate region. Left-from-content escapes to the -// sidebar only when there is no content target to the left; right-from- -// sidebar always re-enters content; up/down inside the sidebar walks the -// sidebar items. - -const EPSILON = 1; -const MAX_FALLBACK_CONE_RADIANS = Math.PI / 3; // 60° - -const getRect = (element) => { - const rect = element.getBoundingClientRect(); - return { - left: rect.left, - top: rect.top, - right: rect.right, - bottom: rect.bottom, - width: rect.width, - height: rect.height, - centerX: rect.left + rect.width / 2, - centerY: rect.top + rect.height / 2, - }; -}; - -const isInDirection = (sourceRect, targetRect, direction) => { - if (direction === "right") return targetRect.centerX > sourceRect.right - EPSILON; - if (direction === "left") return targetRect.centerX < sourceRect.left + EPSILON; - if (direction === "down") return targetRect.centerY > sourceRect.bottom - EPSILON; - if (direction === "up") return targetRect.centerY < sourceRect.top + EPSILON; - return false; -}; - -const perpendicularOverlap = (sourceRect, targetRect, direction) => { - if (direction === "left" || direction === "right") { - const start = Math.max(sourceRect.top, targetRect.top); - const end = Math.min(sourceRect.bottom, targetRect.bottom); - return Math.max(0, end - start); - } - const start = Math.max(sourceRect.left, targetRect.left); - const end = Math.min(sourceRect.right, targetRect.right); - return Math.max(0, end - start); -}; - -const primaryDistance = (sourceRect, targetRect, direction) => { - if (direction === "right") return Math.max(0, targetRect.left - sourceRect.right); - if (direction === "left") return Math.max(0, sourceRect.left - targetRect.right); - if (direction === "down") return Math.max(0, targetRect.top - sourceRect.bottom); - if (direction === "up") return Math.max(0, sourceRect.top - targetRect.bottom); - return Number.POSITIVE_INFINITY; -}; - -const perpendicularDisplacement = (sourceRect, targetRect, direction) => { - if (direction === "left" || direction === "right") { - return Math.abs(targetRect.centerY - sourceRect.centerY); - } - return Math.abs(targetRect.centerX - sourceRect.centerX); -}; - -const offAxisAngle = (sourceRect, targetRect, direction) => { - const dx = targetRect.centerX - sourceRect.centerX; - const dy = targetRect.centerY - sourceRect.centerY; - if (direction === "right") return Math.atan2(Math.abs(dy), Math.max(1, dx)); - if (direction === "left") return Math.atan2(Math.abs(dy), Math.max(1, -dx)); - if (direction === "down") return Math.atan2(Math.abs(dx), Math.max(1, dy)); - if (direction === "up") return Math.atan2(Math.abs(dx), Math.max(1, -dy)); - return Math.PI; -}; - -const scoreCandidate = (sourceRect, targetRect, direction, anchorPerpCenter) => { - if (!isInDirection(sourceRect, targetRect, direction)) { - return null; - } - - const primary = primaryDistance(sourceRect, targetRect, direction); - const overlap = perpendicularOverlap(sourceRect, targetRect, direction); - const perpDisp = perpendicularDisplacement(sourceRect, targetRect, direction); - - let score; - if (overlap > 0) { - // Strong winner: candidate shares the perpendicular line with the source. - // Primary distance dominates; tiny perpendicular drift breaks ties so the - // most centered candidate wins. - score = primary + perpDisp * 0.15; - } else { - // No overlap — only allow within a 60° cone in the requested direction. - if (offAxisAngle(sourceRect, targetRect, direction) > MAX_FALLBACK_CONE_RADIANS) { - return null; - } - // Heavy off-axis penalty plus a flat bias so an overlapping candidate that - // is further away in the primary axis still beats this one. - score = primary + perpDisp * 2.5 + 400; - } - - if (anchorPerpCenter !== null && Number.isFinite(anchorPerpCenter)) { - const anchorDrift = direction === "left" || direction === "right" - ? Math.abs(targetRect.centerY - anchorPerpCenter) - : Math.abs(targetRect.centerX - anchorPerpCenter); - score += anchorDrift * 0.6; - } - - return score; -}; - -const isElementInteractable = (element) => { - if (element.dataset.disabled === "true") return false; - if (element.getAttribute("aria-disabled") === "true") return false; - if (element.hasAttribute("hidden")) return false; - const rect = element.getBoundingClientRect(); - if (rect.width <= 0 || rect.height <= 0) return false; - return true; -}; - -export const createNavigationManager = () => { - let contract = null; - let focusables = []; - let focusedIndex = -1; - - // Tracks the perpendicular center the player is "anchored" to while they - // repeat moves on the same axis. axis="x" while pressing up/down, axis="y" - // while pressing left/right. Reset whenever the axis flips or focus crosses - // regions (sidebar <-> content). - let anchor = { axis: null, value: null }; - - const decorateFocusable = (element) => { - element.classList.remove("is-focused"); - element.tabIndex = -1; - }; - - const dispatchFocusChange = (element) => { - const col = Number(element.dataset.col ?? 0); - const row = Number(element.dataset.row ?? 0); - document.documentElement.style.setProperty("--nebula-accent-line-x", `${20 + col * 110}px`); - document.documentElement.style.setProperty("--nebula-focus-strength", "1"); - - window.dispatchEvent( - new CustomEvent("nebula-focus-change", { - detail: { - key: element.dataset.focusKey ?? null, - row, - col, - }, - }), - ); - }; - - const applyFocus = (index) => { - if (!focusables.length) { - focusedIndex = -1; - return; - } - - const clamped = Math.max(0, Math.min(index, focusables.length - 1)); - - focusables.forEach((focusable) => { - focusable.element.classList.remove("is-focused"); - focusable.element.setAttribute("aria-selected", "false"); - }); - - focusedIndex = clamped; - const focused = focusables[focusedIndex]?.element; - if (focused) { - focused.classList.add("is-focused"); - focused.setAttribute("aria-selected", "true"); - focused.focus({ preventScroll: true }); - dispatchFocusChange(focused); - } - }; - - const buildFocusables = () => { - if (!contract?.focusRoot) { - focusables = []; - return; - } - - const roots = [contract.focusRoot, ...(contract.extraFocusRoots ?? [])].filter(Boolean); - const nodes = Array.from( - new Set(roots.flatMap((root) => Array.from(root.querySelectorAll("[data-focusable='true']")))), - ); - - focusables = nodes - .filter(isElementInteractable) - .map((element) => { - decorateFocusable(element); - return { - element, - row: Number(element.dataset.row ?? 0), - col: Number(element.dataset.col ?? 0), - key: element.dataset.focusKey ?? "", - region: element.dataset.navRegion ?? "content", - }; - }); - - focusables.forEach((focusable, index) => { - focusable.index = index; - }); - }; - - const resolveDefaultFocus = () => { - if (!focusables.length) return -1; - - if (contract?.defaultFocus) { - const idx = focusables.findIndex((f) => f.element === contract.defaultFocus); - if (idx >= 0) return idx; - } - - const contentIdx = focusables.findIndex((f) => f.region !== "sidebar" && f.region !== "guide"); - return contentIdx >= 0 ? contentIdx : 0; - }; - - const findContentDefaultIndex = () => { - if (contract?.defaultFocus) { - const idx = focusables.findIndex((f) => f.element === contract.defaultFocus); - if (idx >= 0 && focusables[idx].region !== "sidebar" && focusables[idx].region !== "guide") { - return idx; - } - } - return focusables.findIndex((f) => f.region !== "sidebar" && f.region !== "guide"); - }; - - const findSidebarTargetIndex = () => { - const activeIdx = focusables.findIndex( - (f) => f.region === "sidebar" && f.element.classList.contains("is-active"), - ); - if (activeIdx >= 0) return activeIdx; - return focusables.findIndex((f) => f.region === "sidebar"); - }; - - const findNextRegionIndex = (direction, region) => { - const regionItems = focusables - .map((focusable, index) => ({ focusable, index })) - .filter(({ focusable }) => focusable.region === region); - - if (!regionItems.length) return null; - - regionItems.sort((a, b) => { - const ra = a.focusable.element.getBoundingClientRect(); - const rb = b.focusable.element.getBoundingClientRect(); - return ra.top - rb.top; - }); - - const currentSlot = regionItems.findIndex(({ index }) => index === focusedIndex); - if (currentSlot < 0) return null; - - const nextSlot = direction === "up" ? currentSlot - 1 : currentSlot + 1; - return regionItems[nextSlot]?.index ?? null; - }; - - const findNextSidebarIndex = (direction) => findNextRegionIndex(direction, "sidebar"); - - const findNextGuideIndex = (direction) => findNextRegionIndex(direction, "guide"); - - const findBestContentSpatialIndex = (direction) => { - const source = focusables[focusedIndex]; - if (!source) return -1; - - const sourceRect = getRect(source.element); - - const axisOfMove = direction === "up" || direction === "down" ? "x" : "y"; - const anchorPerpCenter = anchor.axis === axisOfMove ? anchor.value : null; - - let bestIndex = -1; - let bestScore = Number.POSITIVE_INFINITY; - - focusables.forEach((candidate, idx) => { - if (idx === focusedIndex) return; - if (candidate.region === "sidebar" || candidate.region === "guide") return; - - const targetRect = getRect(candidate.element); - const score = scoreCandidate(sourceRect, targetRect, direction, anchorPerpCenter); - if (score === null) return; - if (score < bestScore) { - bestScore = score; - bestIndex = idx; - } - }); - - return bestIndex; - }; - - const updateAnchorForMove = (direction, sourceRect) => { - const axisOfMove = direction === "up" || direction === "down" ? "x" : "y"; - if (anchor.axis !== axisOfMove) { - anchor = { - axis: axisOfMove, - value: axisOfMove === "x" ? sourceRect.centerX : sourceRect.centerY, - }; - } - }; - - const moveWithNebula = (direction) => { - // The bundled @nebulaproject/core picker is opt-in. Our local spatial - // navigation is the default everywhere so every view gets the same - // predictable controller behavior unless a view explicitly asks for the - // remote algorithm. - if (contract?.useNebulaNavigation !== true) return null; - - const picker = contract?.nebulaNavigation?.pickBestCandidate; - if (typeof picker !== "function") return null; - - const source = focusables[focusedIndex]; - if (!source) return null; - - const sourceRect = getRect(source.element); - const candidates = focusables - .filter((item) => item.index !== source.index) - .map((item) => { - const rect = getRect(item.element); - return { - id: item.key, - x: rect.left, - y: rect.top, - width: rect.width, - height: rect.height, - }; - }); - - const picked = picker( - { - id: source.key, - x: sourceRect.left, - y: sourceRect.top, - width: sourceRect.width, - height: sourceRect.height, - }, - candidates, - direction, - ); - - if (!picked?.id) return null; - - const nextIndex = focusables.findIndex((item) => item.key === picked.id); - return nextIndex >= 0 ? nextIndex : null; - }; - - const move = (direction) => { - if (!focusables.length || focusedIndex < 0) return; - - const source = focusables[focusedIndex]; - const sourceRect = getRect(source.element); - - // -- Guide panel (expanded shell navigation) --------------------------- - if (source.region === "guide") { - if (direction === "up" || direction === "down") { - const next = findNextGuideIndex(direction); - if (next !== null) applyFocus(next); - return; - } - if (direction === "left" || direction === "right") { - const lateral = focusables - .map((focusable, index) => ({ focusable, index })) - .filter(({ focusable }) => focusable.region === "guide") - .filter(({ focusable }) => { - const row = Number(focusable.element.dataset.row ?? 0); - return row === Number(source.element.dataset.row ?? 0); - }) - .sort((a, b) => { - const ra = a.focusable.element.getBoundingClientRect(); - const rb = b.focusable.element.getBoundingClientRect(); - return ra.left - rb.left; - }); - - const slot = lateral.findIndex(({ index }) => index === focusedIndex); - if (slot < 0) return; - const nextSlot = direction === "left" ? slot - 1 : slot + 1; - const next = lateral[nextSlot]?.index; - if (next !== undefined) applyFocus(next); - } - return; - } - - // -- Collapsed sidebar rail -------------------------------------------- - if (source.region === "sidebar") { - if (direction === "up" || direction === "down") { - const next = findNextSidebarIndex(direction); - if (next !== null) applyFocus(next); - return; - } - if (direction === "right") { - const contentIdx = findContentDefaultIndex(); - if (contentIdx >= 0) { - anchor = { axis: null, value: null }; - applyFocus(contentIdx); - } - return; - } - return; - } - - // -- Optional external navigation hook (Nebula core) ------------------- - const nebulaIndex = moveWithNebula(direction); - if (nebulaIndex !== null) { - updateAnchorForMove(direction, sourceRect); - applyFocus(nebulaIndex); - return; - } - - // -- Spatial search within content ------------------------------------- - const bestContentIdx = findBestContentSpatialIndex(direction); - if (bestContentIdx >= 0) { - updateAnchorForMove(direction, sourceRect); - applyFocus(bestContentIdx); - return; - } - - // -- Edge of content: escape to sidebar when going left ---------------- - if (direction === "left") { - const sidebarIdx = findSidebarTargetIndex(); - if (sidebarIdx >= 0) { - anchor = { axis: null, value: null }; - applyFocus(sidebarIdx); - } - return; - } - - // No candidate in this direction. Stay put — pressing again won't bounce - // the player around to unrelated regions. - }; - - const mount = (nextContract) => { - contract = nextContract; - anchor = { axis: null, value: null }; - buildFocusables(); - applyFocus(resolveDefaultFocus()); - }; - - const getFocusedElement = () => focusables[focusedIndex]?.element ?? null; - - return { - mount, - move, - getFocusedElement, - }; -}; diff --git a/Bigscreen/src/core/passkey.js b/Bigscreen/src/core/passkey.js deleted file mode 100644 index 5ea3c34..0000000 --- a/Bigscreen/src/core/passkey.js +++ /dev/null @@ -1,184 +0,0 @@ -const PASSKEY_STORAGE_KEY = "nebula.passkey.v1"; -const PASSKEY_VERSION = 1; -const PASSKEY_LENGTH = 6; - -const clamp = (value, min, max) => Math.max(min, Math.min(max, value)); - -const FALLBACK_CONFIG = { - enabled: true, - length: PASSKEY_LENGTH, - requireConfirm: false, - keyboardSupport: true, - maxAttempts: 5, - cooldownSeconds: 30, - animationSpeed: "normal", - highContrast: false, - hash: "", - salt: "", -}; - -const arrayToHex = (bytes) => - Array.from(bytes) - .map((value) => value.toString(16).padStart(2, "0")) - .join(""); - -const createSalt = () => { - const bytes = new Uint8Array(16); - crypto.getRandomValues(bytes); - return arrayToHex(bytes); -}; - -const normalizeSequence = (sequence) => sequence.join("|"); - -export const hashPasskeySequence = async (sequence, salt) => { - const source = `${salt}::${normalizeSequence(sequence)}`; - const encoded = new TextEncoder().encode(source); - const digest = await crypto.subtle.digest("SHA-256", encoded); - return arrayToHex(new Uint8Array(digest)); -}; - -export const loadPasskeyConfig = () => { - try { - const raw = window.localStorage.getItem(PASSKEY_STORAGE_KEY); - if (!raw) { - return { - ...FALLBACK_CONFIG, - }; - } - - const parsed = JSON.parse(raw); - return { - ...FALLBACK_CONFIG, - ...parsed, - length: PASSKEY_LENGTH, - maxAttempts: clamp(Number(parsed.maxAttempts ?? FALLBACK_CONFIG.maxAttempts), 1, 10), - cooldownSeconds: clamp(Number(parsed.cooldownSeconds ?? FALLBACK_CONFIG.cooldownSeconds), 5, 120), - requireConfirm: Boolean(parsed.requireConfirm ?? FALLBACK_CONFIG.requireConfirm), - keyboardSupport: Boolean(parsed.keyboardSupport ?? FALLBACK_CONFIG.keyboardSupport), - enabled: Boolean(parsed.enabled ?? FALLBACK_CONFIG.enabled), - highContrast: Boolean(parsed.highContrast ?? FALLBACK_CONFIG.highContrast), - animationSpeed: ["slow", "normal", "fast"].includes(parsed.animationSpeed) - ? parsed.animationSpeed - : FALLBACK_CONFIG.animationSpeed, - hash: typeof parsed.hash === "string" ? parsed.hash : "", - salt: typeof parsed.salt === "string" ? parsed.salt : "", - version: PASSKEY_VERSION, - }; - } catch (_error) { - return { - ...FALLBACK_CONFIG, - version: PASSKEY_VERSION, - }; - } -}; - -export const savePasskeyConfig = (config) => { - const safeConfig = { - ...FALLBACK_CONFIG, - ...config, - version: PASSKEY_VERSION, - length: PASSKEY_LENGTH, - maxAttempts: clamp(Number(config.maxAttempts ?? FALLBACK_CONFIG.maxAttempts), 1, 10), - cooldownSeconds: clamp(Number(config.cooldownSeconds ?? FALLBACK_CONFIG.cooldownSeconds), 5, 120), - }; - - window.localStorage.setItem(PASSKEY_STORAGE_KEY, JSON.stringify(safeConfig)); - return safeConfig; -}; - -export const createPasskeyController = () => { - const config = loadPasskeyConfig(); - let failedAttempts = 0; - let lockoutUntil = 0; - - const persist = () => savePasskeyConfig(config); - - const inLockout = () => Date.now() < lockoutUntil; - - const getLockoutRemainingMs = () => Math.max(0, lockoutUntil - Date.now()); - - const verifySequence = async (sequence) => { - if (!config.enabled) { - return { ok: true, reason: "disabled" }; - } - - if (inLockout()) { - return { - ok: false, - reason: "lockout", - lockoutRemainingMs: getLockoutRemainingMs(), - }; - } - - if (!config.hash || !config.salt) { - return { ok: false, reason: "setup-required" }; - } - - const hash = await hashPasskeySequence(sequence, config.salt); - if (hash === config.hash) { - failedAttempts = 0; - return { ok: true, reason: "match" }; - } - - failedAttempts += 1; - const attemptsLeft = Math.max(0, config.maxAttempts - failedAttempts); - if (attemptsLeft <= 0) { - lockoutUntil = Date.now() + config.cooldownSeconds * 1000; - failedAttempts = 0; - return { - ok: false, - reason: "lockout", - lockoutRemainingMs: getLockoutRemainingMs(), - }; - } - - return { - ok: false, - reason: "mismatch", - attemptsLeft, - }; - }; - - const setSequence = async (sequence) => { - const nextSalt = createSalt(); - const nextHash = await hashPasskeySequence(sequence, nextSalt); - config.salt = nextSalt; - config.hash = nextHash; - failedAttempts = 0; - lockoutUntil = 0; - persist(); - }; - - const updateConfig = (partial) => { - Object.assign(config, partial); - config.length = PASSKEY_LENGTH; - config.maxAttempts = clamp(Number(config.maxAttempts), 1, 10); - config.cooldownSeconds = clamp(Number(config.cooldownSeconds), 5, 120); - persist(); - return { ...config }; - }; - - const resetSequence = () => { - config.hash = ""; - config.salt = ""; - config.length = PASSKEY_LENGTH; - failedAttempts = 0; - lockoutUntil = 0; - persist(); - }; - - const getConfig = () => ({ ...config }); - - return { - getConfig, - updateConfig, - verifySequence, - setSequence, - resetSequence, - inLockout, - getLockoutRemainingMs, - hasPasskey: () => Boolean(config.hash && config.salt), - }; -}; - -export const PASSKEY_ACTIONS = ["up", "down", "left", "right", "l1", "r1", "l2", "r2"]; diff --git a/Bigscreen/src/core/router.js b/Bigscreen/src/core/router.js deleted file mode 100644 index e0cc765..0000000 --- a/Bigscreen/src/core/router.js +++ /dev/null @@ -1,34 +0,0 @@ -export const createRouter = (outlet) => { - const views = new Map(); - let current = null; - - const register = (view) => { - views.set(view.id, view); - }; - - const navigate = (id) => { - const view = views.get(id); - if (!view) { - throw new Error(`Unknown view: ${id}`); - } - - current = id; - outlet.innerHTML = view.render(); - const nextView = outlet.querySelector(".view"); - if (nextView) { - requestAnimationFrame(() => { - nextView.classList.add("view-entered"); - }); - } - view.mount?.(outlet); - return view.getNavigationContract(); - }; - - const getCurrent = () => current; - - return { - register, - navigate, - getCurrent, - }; -}; diff --git a/Bigscreen/src/core/sidebarData.js b/Bigscreen/src/core/sidebarData.js deleted file mode 100644 index 41fc692..0000000 --- a/Bigscreen/src/core/sidebarData.js +++ /dev/null @@ -1,58 +0,0 @@ -/** Primary shell navigation (data-driven). */ -export const PRIMARY_NAV = [ - { id: "home", label: "Home", icon: "⌂", target: "home" }, - { id: "library", label: "Library", icon: "⊟", target: "library" }, - { id: "store", label: "Store", icon: "⊞", target: "store" }, - { id: "mods", label: "Mods", icon: "◇", target: "mods" }, - { id: "settings", label: "Settings", icon: "⚙", target: "settings" }, -]; - -/** Quick actions in the expanded guide. */ -export const QUICK_ACTIONS = [ - { id: "search", label: "Search", icon: "⌕", panel: "search" }, - { id: "notifications", label: "Notifications", icon: "🔔", panel: "notifications" }, - { id: "downloads", label: "Downloads", icon: "↓", panel: "downloads" }, - { id: "controller", label: "Controller Settings", icon: "🎮", panel: "controller" }, - { id: "power", label: "Power Menu", icon: "⏻", action: "power" }, -]; - -/** Mock recently played titles (replace with library bridge later). */ -export const RECENT_ITEMS = [ - { - id: "recent-halo", - title: "Halo Infinite", - source: "steam", - lastPlayed: "2 hours ago", - accent: "#1e4a6e", - }, - { - id: "recent-cyber", - title: "Cyberpunk 2077", - source: "gog", - lastPlayed: "Yesterday", - accent: "#5c1a3a", - }, - { - id: "recent-fortnite", - title: "Fortnite", - source: "epic", - lastPlayed: "3 days ago", - accent: "#2a3a5c", - }, - { - id: "recent-emulator", - title: "Nebula Arcade", - source: "local", - lastPlayed: "Last week", - accent: "#1a3c2e", - }, -]; - -export const SOURCE_LABELS = { - steam: "Steam", - gog: "GOG", - epic: "Epic", - local: "Local", -}; - -export const NAVIGABLE_VIEWS = new Set(PRIMARY_NAV.map((item) => item.target)); diff --git a/Bigscreen/src/core/state.js b/Bigscreen/src/core/state.js deleted file mode 100644 index 48b8e53..0000000 --- a/Bigscreen/src/core/state.js +++ /dev/null @@ -1,149 +0,0 @@ -import { - detectActiveProfile, - getFallbackGlyphs, - resolveGlyphsForProfile, - watchGamepadProfile, -} from "./gamepadProfile.js"; -import { createPasskeyController } from "./passkey.js"; -import { loadExistingUser } from "./users.js"; - -const FALLBACK_THEME = { - colors: { - bg: "#0b1020", - panel: "#141c33", - panelAlt: "#1b2747", - text: "#eef4ff", - muted: "#9eb1d3", - accent: "#50d6ff", - danger: "#ff6b88", - success: "#7dff9e", - focus: "#50d6ff", - overlay: "rgba(5, 8, 18, 0.78)", - }, - spacing: { - xs: 6, - sm: 10, - md: 16, - lg: 24, - xl: 36, - }, - radius: { - sm: 10, - md: 16, - lg: 22, - }, - typography: { - body: 18, - title: 24, - display: 34, - }, -}; - -export const createAppState = () => { - const passkey = createPasskeyController(); - const state = { - passkey, - locked: true, - activeView: "lock", - user: null, - profileName: "Nebula User", - userSetupRequired: true, - nebula: { - coreReady: false, - source: "local-fallback", - navigation: null, - input: null, - glyphs: null, - theme: null, - ui: null, - }, - theme: FALLBACK_THEME, - controllerProfile: "generic", - glyphs: getFallbackGlyphs("generic"), - settingsCategory: "system", - settingsValues: { - network: true, - audio: true, - display: false, - storage: true, - system: false, - }, - passkeySetupRequired: !passkey.hasPasskey(), - }; - - state.refreshControllerGlyphs = (profile = detectActiveProfile()) => { - state.controllerProfile = profile; - state.glyphs = resolveGlyphsForProfile(profile, state.nebula.glyphs); - window.dispatchEvent( - new CustomEvent("nebula-controller-profile", { - detail: { profile }, - }), - ); - }; - - state.startGamepadProfileWatcher = () => { - if (state.stopGamepadProfileWatcher) { - state.stopGamepadProfileWatcher(); - } - state.stopGamepadProfileWatcher = watchGamepadProfile((profile) => { - state.refreshControllerGlyphs(profile); - }); - }; - - const applyThemeToDocument = () => { - const root = document.documentElement; - const { colors, spacing, radius, typography } = state.theme; - - Object.entries(colors).forEach(([key, value]) => root.style.setProperty(`--nebula-color-${key}`, value)); - Object.entries(spacing).forEach(([key, value]) => root.style.setProperty(`--nebula-spacing-${key}`, `${value}px`)); - Object.entries(radius).forEach(([key, value]) => root.style.setProperty(`--nebula-radius-${key}`, `${value}px`)); - Object.entries(typography).forEach(([key, value]) => root.style.setProperty(`--nebula-type-${key}`, `${value}px`)); - }; - - const initializeNebulaCore = async () => { - try { - const [input, navigation, glyphs, theme, ui] = await Promise.all([ - import("@nebulaproject/core/input"), - import("@nebulaproject/core/navigation"), - import("@nebulaproject/core/glyphs"), - import("@nebulaproject/core/theme"), - import("@nebulaproject/core/ui"), - ]); - - state.nebula = { - coreReady: true, - source: "@nebulaproject/core", - input, - navigation, - glyphs, - theme, - ui, - }; - - state.theme = typeof theme.createTheme === "function" ? theme.createTheme({}) : FALLBACK_THEME; - - state.refreshControllerGlyphs(detectActiveProfile()); - } catch (_error) { - state.nebula = { - ...state.nebula, - coreReady: false, - source: "local-fallback", - }; - state.theme = FALLBACK_THEME; - state.refreshControllerGlyphs("generic"); - } - - applyThemeToDocument(); - }; - - const initializeUser = async () => { - const user = await loadExistingUser(); - state.user = user; - state.userSetupRequired = !user; - state.profileName = user?.name ?? "New User"; - }; - - state.initializeNebulaCore = initializeNebulaCore; - state.initializeUser = initializeUser; - return state; -}; diff --git a/Bigscreen/src/core/users.js b/Bigscreen/src/core/users.js deleted file mode 100644 index a5b8149..0000000 --- a/Bigscreen/src/core/users.js +++ /dev/null @@ -1,105 +0,0 @@ -const LEGACY_USER_STORAGE_KEY = "nebula.user.v1"; - -const getInvoke = async () => { - const globalInvoke = window.__TAURI__?.core?.invoke; - if (typeof globalInvoke === "function") { - return globalInvoke; - } - - try { - const tauriCore = await import("@tauri-apps/api/core"); - return typeof tauriCore.invoke === "function" ? tauriCore.invoke : null; - } catch (_error) { - return null; - } -}; - -const normalizeUser = (record) => { - if (!record || typeof record !== "object") { - return null; - } - const id = Number(record.id); - const name = typeof record.name === "string" ? record.name.trim() : ""; - const firstName = typeof record.firstName === "string" ? record.firstName.trim() : ""; - const lastName = typeof record.lastName === "string" ? record.lastName.trim() : ""; - if (!Number.isFinite(id) || !name) { - return null; - } - - return { - id, - name, - firstName: firstName || name, - lastName: lastName || "", - createdAtUnixMs: Number(record.createdAtUnixMs ?? Date.now()), - }; -}; - -export const loadExistingUser = async () => { - const invoke = await getInvoke(); - if (!invoke) { - const legacyRaw = window.localStorage.getItem(LEGACY_USER_STORAGE_KEY); - if (!legacyRaw) { - return null; - } - try { - return normalizeUser(JSON.parse(legacyRaw)); - } catch (_parseError) { - return null; - } - } - - try { - const record = await invoke("get_first_user"); - return normalizeUser(record); - } catch (_error) { - const legacyRaw = window.localStorage.getItem(LEGACY_USER_STORAGE_KEY); - if (!legacyRaw) { - return null; - } - try { - return normalizeUser(JSON.parse(legacyRaw)); - } catch (_parseError) { - return null; - } - } -}; - -export const createUser = async ({ firstName, lastName = "" }) => { - const requestedFirstName = String(firstName ?? "").trim(); - const requestedLastName = String(lastName ?? "").trim(); - if (!requestedFirstName) { - throw new Error("First name is required."); - } - - const invoke = await getInvoke(); - if (!invoke) { - const fallback = { - id: 1, - name: requestedLastName ? `${requestedFirstName} ${requestedLastName}` : requestedFirstName, - firstName: requestedFirstName, - lastName: requestedLastName, - createdAtUnixMs: Date.now(), - }; - window.localStorage.setItem(LEGACY_USER_STORAGE_KEY, JSON.stringify(fallback)); - return fallback; - } - - try { - const record = await invoke("create_user", { - firstName: requestedFirstName, - lastName: requestedLastName || null, - }); - return normalizeUser(record); - } catch (_error) { - const fallback = { - id: 1, - name: requestedLastName ? `${requestedFirstName} ${requestedLastName}` : requestedFirstName, - firstName: requestedFirstName, - lastName: requestedLastName, - createdAtUnixMs: Date.now(), - }; - window.localStorage.setItem(LEGACY_USER_STORAGE_KEY, JSON.stringify(fallback)); - return fallback; - } -}; diff --git a/Bigscreen/src/index.html b/Bigscreen/src/index.html deleted file mode 100644 index 8c9c358..0000000 --- a/Bigscreen/src/index.html +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - - - - - - - - - - - - Nebula Shell - - - - - - -
    -
    - -
    - -
    -
    -
    -
    - -
    -
    - - - - - - - - - - - - - - - - diff --git a/Bigscreen/src/main.cpp b/Bigscreen/src/main.cpp new file mode 100644 index 0000000..9459325 --- /dev/null +++ b/Bigscreen/src/main.cpp @@ -0,0 +1,99 @@ +#include "InputRouter.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { +void logMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message) +{ + QFile logFile(QStringLiteral("nebula-bigscreen.log")); + if (!logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) { + return; + } + + QTextStream stream(&logFile); + stream << QDateTime::currentDateTime().toString(Qt::ISODate) << " "; + switch (type) { + case QtDebugMsg: + stream << "DEBUG"; + break; + case QtInfoMsg: + stream << "INFO"; + break; + case QtWarningMsg: + stream << "WARN"; + break; + case QtCriticalMsg: + stream << "CRITICAL"; + break; + case QtFatalMsg: + stream << "FATAL"; + break; + } + + stream << ": " << message; + if (context.file) { + stream << " (" << context.file << ":" << context.line << ")"; + } + stream << "\n"; +} +} + +class InputFilter : public QObject +{ +public: + explicit InputFilter(InputRouter *router, QObject *parent = nullptr) + : QObject(parent) + , m_router(router) + { + } + +protected: + bool eventFilter(QObject *watched, QEvent *event) override + { + Q_UNUSED(watched); + if (event->type() == QEvent::KeyPress) { + return m_router->handleKeyPress(static_cast(event)); + } + return false; + } + +private: + InputRouter *m_router = nullptr; +}; + +int main(int argc, char *argv[]) +{ + qInstallMessageHandler(logMessageHandler); + + QGuiApplication app(argc, argv); + QGuiApplication::setApplicationName(QStringLiteral("Nebula Bigscreen")); + QGuiApplication::setOrganizationName(QStringLiteral("NebulaOS")); + QGuiApplication::setOrganizationDomain(QStringLiteral("nebulaos.local")); + + InputRouter inputRouter; + InputFilter inputFilter(&inputRouter); + app.installEventFilter(&inputFilter); + + QQmlApplicationEngine engine; + engine.rootContext()->setContextProperty(QStringLiteral("InputRouter"), &inputRouter); + + QObject::connect( + &engine, + &QQmlApplicationEngine::objectCreationFailed, + &app, + []() { QCoreApplication::exit(-1); }, + Qt::QueuedConnection); + + engine.load(QUrl(QStringLiteral("qrc:/qt/qml/Nebula/Bigscreen/qml/Main.qml"))); + + return app.exec(); +} diff --git a/Bigscreen/src/main.js b/Bigscreen/src/main.js deleted file mode 100644 index f04aca1..0000000 --- a/Bigscreen/src/main.js +++ /dev/null @@ -1,388 +0,0 @@ -import { createGuideSidebar } from "./core/guideSidebar.js"; -import { NAVIGABLE_VIEWS } from "./core/sidebarData.js"; -import { createInputManager } from "./core/input.js"; -import { createNavigationManager } from "./core/nav.js"; -import { createRouter } from "./core/router.js"; -import { createAppState } from "./core/state.js"; -import { createHomeView } from "./views/home/home.js"; -import { createLibraryView } from "./views/library/library.js"; -import { createLockView } from "./views/lock/lock.js"; -import { createUserSetupView } from "./views/onboarding/userSetup.js"; -import { createGuidePanelOverlay } from "./views/overlays/guidePanel.js"; -import { createKeyboardOverlay } from "./views/overlays/keyboard.js"; -import { createPowerMenuOverlay } from "./views/overlays/powerMenu.js"; -import { createPlaceholderView } from "./views/placeholder/placeholder.js"; -import { createSettingsView } from "./views/settings/settings.js"; - -const appRoot = document.querySelector("#app"); -const overlayRoot = document.querySelector("#overlay-root"); -const keyboardRoot = document.querySelector("#keyboard-root"); -const footer = document.querySelector("#app-footer"); -const guideShellRoot = document.querySelector("#guide-shell-root"); -const guideBackdrop = document.querySelector("#guide-backdrop"); - -const SIDEBAR_HIDDEN_VIEWS = new Set(["lock", "user-setup"]); - -const state = createAppState(); -const nav = createNavigationManager(); -const router = createRouter(appRoot); -const guidePanels = createGuidePanelOverlay({ mountRoot: overlayRoot }); -const powerMenu = createPowerMenuOverlay({ mountRoot: overlayRoot }); -const keyboard = createKeyboardOverlay({ mountRoot: keyboardRoot }); - -let currentViewContract = null; - -const remountNavigation = (options = {}) => { - if (!currentViewContract) return; - - currentViewContract = { - ...currentViewContract, - extraFocusRoots: SIDEBAR_HIDDEN_VIEWS.has(state.activeView) - ? [] - : guideSidebar.getFocusRoots(), - defaultFocus: options.defaultFocus ?? currentViewContract.defaultFocus, - }; - - nav.mount(currentViewContract); - - if (options.focusKey) { - const target = document.querySelector(`[data-focus-key="${CSS.escape(options.focusKey)}"]`); - if (target) { - currentViewContract.defaultFocus = target; - nav.mount(currentViewContract); - } - } -}; - -const guideSidebar = createGuideSidebar({ - state, - renderView, - openPowerMenu, - openGuidePanel: (panelId) => { - guidePanels.open(panelId, { - onClose: () => remountNavigation(), - }); - setFooterHints("#guide-panel-hints-template", state.glyphs); - }, - onGuideChange: ({ expanded }) => { - if (expanded) { - const active = document.querySelector( - "#guide-panel [data-sidebar-nav].is-active, #guide-panel .guide-nav-item", - ); - remountNavigation({ defaultFocus: active ?? undefined }); - setFooterHints("#guide-hints-template", state.glyphs); - return; - } - remountNavigation(); - setFooterHints(currentViewContract?.hintsTemplate ?? "#global-hints-template", state.glyphs); - }, -}); - -const emitUiHook = (type, payload = {}) => { - window.dispatchEvent( - new CustomEvent("nebula-ui-hook", { - detail: { type, ...payload }, - }), - ); -}; - -const setFooterHints = (templateId, glyphs) => { - const template = document.querySelector(templateId); - if (!template) { - footer.innerHTML = ""; - return; - } - footer.innerHTML = template.innerHTML; - footer.querySelectorAll("[data-glyph]").forEach((element) => { - const action = element.dataset.glyph; - element.textContent = glyphs[action] ?? action; - }); -}; - -const updateClockLabels = () => { - const now = new Date(); - const formattedTime = now.toLocaleTimeString([], { - hour: "2-digit", - minute: "2-digit", - }); - const formattedDate = now.toLocaleDateString([], { - weekday: "short", - month: "short", - day: "numeric", - year: "numeric", - }); - document.querySelectorAll("[data-clock]").forEach((clock) => { - clock.textContent = formattedTime; - }); - document.querySelectorAll("[data-date]").forEach((date) => { - date.textContent = formattedDate; - }); -}; - -const updateSidebar = (viewId) => { - const body = document.body; - - if (SIDEBAR_HIDDEN_VIEWS.has(viewId)) { - body.classList.add("body-no-sidebar"); - guideSidebar.close(); - return; - } - - body.classList.remove("body-no-sidebar"); - guideSidebar.updateActiveView(viewId); - guideSidebar.syncProfile(); -}; - -function renderView(viewId) { - const contract = router.navigate(viewId); - if (!contract) { - return; - } - state.activeView = viewId; - updateSidebar(viewId); - - if (guideSidebar.isExpanded()) { - guideSidebar.close(); - } - - currentViewContract = { - ...contract, - extraFocusRoots: SIDEBAR_HIDDEN_VIEWS.has(viewId) ? [] : guideSidebar.getFocusRoots(), - }; - - nav.mount(currentViewContract); - setFooterHints(currentViewContract.hintsTemplate ?? "#global-hints-template", state.glyphs); - updateClockLabels(); -} - -const refreshNavigation = (event) => { - if (!currentViewContract?.focusRoot) { - return; - } - - const focusKey = event?.detail?.focusKey; - const requestedFocus = focusKey - ? document.querySelector(`[data-focus-key="${CSS.escape(focusKey)}"]`) - : null; - - remountNavigation({ - defaultFocus: requestedFocus ?? currentViewContract.defaultFocus, - }); -}; - -const registerViews = () => { - const context = { state, renderView, powerMenu, keyboard, openPowerMenu }; - router.register(createUserSetupView(context)); - router.register(createLockView(context)); - router.register(createHomeView(context)); - router.register(createSettingsView(context)); - router.register(createLibraryView(context)); - router.register( - createPlaceholderView(context, { - id: "store", - title: "Store", - subtitle: "Discover", - }), - ); - router.register( - createPlaceholderView(context, { - id: "mods", - title: "Mods", - subtitle: "Community", - }), - ); -}; - -function openPowerMenu() { - if (guideSidebar.isExpanded()) { - guideSidebar.close(); - } - powerMenu.open({ - onClose: () => { - remountNavigation(); - setFooterHints(currentViewContract?.hintsTemplate ?? "#global-hints-template", state.glyphs); - }, - }); - setFooterHints("#minimal-hints-template", state.glyphs); -} - -const toggleGuide = () => { - if (SIDEBAR_HIDDEN_VIEWS.has(state.activeView)) { - return; - } - guideSidebar.toggle(); -}; - -const handleGuideAccept = (focused) => { - const handled = guideSidebar.handleAccept(focused); - if (handled) { - remountNavigation({ focusKey: focused?.dataset?.focusKey }); - } -}; - -const handleAction = (action) => { - if (powerMenu.isOpen()) { - powerMenu.handleAction(action); - return; - } - - if (guidePanels.isOpen()) { - if (guidePanels.handleAction(action)) { - return; - } - } - - if (keyboard.isOpen()) { - const consumed = keyboard.handleAction(action); - if (consumed) { - return; - } - } - - if (!currentViewContract) { - return; - } - - if (action === "guide") { - toggleGuide(); - return; - } - - if (action === "menu") { - if (SIDEBAR_HIDDEN_VIEWS.has(state.activeView)) { - currentViewContract.onMenu?.(); - return; - } - - if (guideSidebar.isExpanded()) { - guideSidebar.close(); - return; - } - toggleGuide(); - return; - } - - if (guideSidebar.isExpanded()) { - if (action === "back") { - guideSidebar.close(); - return; - } - - if (action === "up" || action === "down" || action === "left" || action === "right") { - nav.move(action); - emitUiHook("move", { action }); - return; - } - - if (action === "accept") { - const focused = nav.getFocusedElement(); - focused?.classList.add("is-pressed"); - window.setTimeout(() => focused?.classList.remove("is-pressed"), 180); - handleGuideAccept(focused); - return; - } - - return; - } - - if (action === "up" || action === "down" || action === "left" || action === "right") { - if (currentViewContract.captureDirectionalInput) { - const handled = currentViewContract.onAction?.(action, nav.getFocusedElement()); - if (handled) { - return; - } - } - - nav.move(action); - emitUiHook("move", { action }); - return; - } - - if (action === "accept") { - const focused = nav.getFocusedElement(); - focused?.classList.add("is-pressed"); - window.setTimeout(() => focused?.classList.remove("is-pressed"), 180); - emitUiHook("accept", { focusKey: focused?.dataset.focusKey ?? null }); - - if (focused?.dataset.navRegion === "sidebar") { - if (focused.dataset.guideAction === "toggle") { - guideSidebar.open(); - return; - } - if (focused.dataset.guideAction === "power") { - openPowerMenu(); - return; - } - const target = focused.dataset.target; - if (target && NAVIGABLE_VIEWS.has(target)) { - renderView(target); - } - return; - } - - currentViewContract.onAccept?.(focused); - return; - } - - if (action === "back") { - emitUiHook("back"); - currentViewContract.onBack?.(); - return; - } - - currentViewContract.onAction?.(action, nav.getFocusedElement()); -}; - -const initialize = async () => { - guideSidebar.mount({ - shellRoot: guideShellRoot, - backdropRoot: guideBackdrop, - }); - - await state.initializeUser(); - await state.initializeNebulaCore(); - state.startGamepadProfileWatcher?.(); - registerViews(); - renderView(state.userSetupRequired ? "user-setup" : "lock"); - updateClockLabels(); - window.setInterval(updateClockLabels, 1000); - - window.addEventListener("nebula-navigation-refresh", refreshNavigation); - window.addEventListener("nebula-controller-profile", () => { - setFooterHints(currentViewContract?.hintsTemplate ?? "#global-hints-template", state.glyphs); - }); - window.addEventListener("nebula-guide-close", (event) => { - remountNavigation({ focusKey: event.detail?.focusKey }); - }); - - window.addEventListener("keydown", (event) => { - if (event.key === "Escape" && guideSidebar.isExpanded()) { - event.preventDefault(); - guideSidebar.close(); - } - }); - - const input = createInputManager({ - onAction: handleAction, - actions: [ - "up", - "down", - "left", - "right", - "accept", - "back", - "menu", - "guide", - "clear", - "y", - "l1", - "r1", - "l2", - "r2", - ], - }); - - input.start(); -}; - -initialize(); diff --git a/Bigscreen/src/styles.css b/Bigscreen/src/styles.css deleted file mode 100644 index e57b8ac..0000000 --- a/Bigscreen/src/styles.css +++ /dev/null @@ -1,112 +0,0 @@ -.logo.vanilla:hover { - filter: drop-shadow(0 0 2em #ffe21c); -} -:root { - font-family: Inter, Avenir, Helvetica, Arial, sans-serif; - font-size: 16px; - line-height: 24px; - font-weight: 400; - - color: #0f0f0f; - background-color: #f6f6f6; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - -webkit-text-size-adjust: 100%; -} - -.container { - margin: 0; - padding-top: 10vh; - display: flex; - flex-direction: column; - justify-content: center; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: 0.75s; -} - -.logo.tauri:hover { - filter: drop-shadow(0 0 2em #24c8db); -} - -.row { - display: flex; - justify-content: center; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} - -a:hover { - color: #535bf2; -} - -h1 { - text-align: center; -} - -input, -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - color: #0f0f0f; - background-color: #ffffff; - transition: border-color 0.25s; - box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2); -} - -button { - cursor: pointer; -} - -button:hover { - border-color: #396cd8; -} -button:active { - border-color: #396cd8; - background-color: #e8e8e8; -} - -input, -button { - outline: none; -} - -#greet-input { - margin-right: 5px; -} - -@media (prefers-color-scheme: dark) { - :root { - color: #f6f6f6; - background-color: #2f2f2f; - } - - a:hover { - color: #24c8db; - } - - input, - button { - color: #ffffff; - background-color: #0f0f0f98; - } - button:active { - background-color: #0f0f0f69; - } -} diff --git a/Bigscreen/src/styles/base.css b/Bigscreen/src/styles/base.css deleted file mode 100644 index 2423049..0000000 --- a/Bigscreen/src/styles/base.css +++ /dev/null @@ -1,230 +0,0 @@ -* { - box-sizing: border-box; -} - -html, -body { - margin: 0; - width: 100%; - height: 100%; - overflow: hidden; - background: var(--nebula-color-bg); - color: var(--nebula-color-text); - font-family: "Segoe UI", system-ui, -apple-system, BlinkMacSystemFont, sans-serif; -} - -/* ─── Flat background: no blur layers ─── */ -#nebula-background { - position: fixed; - inset: 0; - z-index: -2; - overflow: hidden; -} - -.nebula-layer.gradient { - position: absolute; - inset: 0; - background: - radial-gradient(circle at 15% 10%, rgba(79, 216, 255, 0.06), transparent 38%), - radial-gradient(circle at 85% 80%, rgba(157, 79, 224, 0.07), transparent 38%), - linear-gradient(165deg, #050810 0%, #070a14 40%, #0a0d1c 70%, #0d0a1e 100%); -} - -.nebula-layer.starfield { - position: absolute; - inset: 0; - background-image: - radial-gradient(circle, rgba(255, 255, 255, 0.75) 0.6px, transparent 1.2px), - radial-gradient(circle, rgba(79, 216, 255, 0.4) 0.5px, transparent 1px); - background-size: 200px 200px, 300px 300px; - background-position: 0 0, 140px 110px; - opacity: 0.18; -} - -/* fog and vignette layers: hidden for Zero-Blur Policy */ -.nebula-layer.fog, -.nebula-layer.vignette { - display: none; -} - -/* ─── No shell depth blur ─── */ -.shell-chrome, -.shell-depth-blur { - display: none; -} - -/* ─── Root layout: sidebar + main ─── */ -.app-layout { - display: flex; - width: 100vw; - height: 100vh; - overflow: hidden; - position: relative; - z-index: 0; -} - -/* ─── Guide sidebar mount ─── */ -.guide-mount { - flex-shrink: 0; - height: 100%; -} - -/* ─── Main content area ─── */ -.app-main-area { - position: relative; - flex: 1; - display: flex; - flex-direction: column; - height: 100%; - overflow: hidden; - min-width: 0; -} - -.placeholder-view .placeholder-body { - margin: var(--nebula-spacing-lg); - max-width: 720px; -} - -.placeholder-copy { - margin: var(--nebula-spacing-md) 0 var(--nebula-spacing-lg); - color: var(--nebula-color-muted); - line-height: 1.5; -} - -.placeholder-back { - min-height: 52px; - padding: 0 var(--nebula-spacing-lg); - border: none; - border-radius: var(--nebula-radius-pill); - background: var(--nebula-color-panel-alt); - color: var(--nebula-color-text); - font-weight: 600; - cursor: pointer; -} - -.app-shell { - flex: 1; - min-height: 0; - overflow: hidden; - position: relative; - z-index: 2; -} - -/* ─── View transitions ─── */ -.view { - width: 100%; - height: 100%; - display: flex; - flex-direction: column; - opacity: 0; - transform: translateX(24px); -} - -.view.view-entered { - opacity: 1; - transform: translateX(0); - transition: - transform var(--nebula-duration-slow) var(--nebula-ease-console), - opacity var(--nebula-duration-nav) var(--nebula-ease-standard); -} - -/* ─── Shared shell elements ─── */ -.shell-topbar { - display: flex; - flex-direction: column; - padding: 14px var(--nebula-spacing-xl); - border-bottom: 1px solid var(--nebula-color-border); - flex-shrink: 0; -} - -.shell-brand { - margin: 0; - letter-spacing: 0.12em; - text-transform: uppercase; - font-size: 14px; - font-weight: 700; - color: var(--nebula-color-text); -} - -.shell-time { - letter-spacing: 0.05em; - font-size: 16px; - font-weight: 600; - color: var(--nebula-color-muted); -} - -.shell-avatar { - width: 30px; - height: 30px; - border-radius: var(--nebula-radius-pill); - border: 2px solid rgba(79, 216, 255, 0.3); - background: radial-gradient(circle at 34% 30%, rgba(255, 255, 255, 0.8), rgba(75, 81, 155, 0.7)); -} - -.shell-topbar-content { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; -} - -.shell-accent-line { - display: none; -} - -.shell-status { - display: inline-flex; - align-items: center; - gap: var(--nebula-spacing-md); -} - -.view-header { - display: flex; - align-items: center; - justify-content: space-between; -} - -.view-title { - margin: 0; - font-size: var(--nebula-type-display); - line-height: 1.15; - letter-spacing: 0.01em; -} - -.muted { - color: var(--nebula-color-muted); -} - -/* ─── Footer hints bar ─── */ -.app-footer { - flex-shrink: 0; - min-height: 44px; - padding: 0 var(--nebula-spacing-xl) var(--nebula-spacing-sm); - border-top: 1px solid var(--nebula-color-border); - display: flex; - align-items: center; -} - -.hint-row { - display: flex; - align-items: center; - gap: var(--nebula-spacing-lg); - color: var(--nebula-color-muted); -} - -.hint { - display: inline-flex; - gap: 6px; - align-items: center; - font-size: 14px; -} - -/* ─── Background animations ─── */ -@keyframes starfieldShift { - 0% { transform: translate3d(0, 0, 0); } - 100% { transform: translate3d(-140px, -110px, 0); } -} - -.nebula-layer.starfield { - animation: starfieldShift 60s linear infinite; -} diff --git a/Bigscreen/src/styles/components.css b/Bigscreen/src/styles/components.css deleted file mode 100644 index ee81db2..0000000 --- a/Bigscreen/src/styles/components.css +++ /dev/null @@ -1,159 +0,0 @@ -/* ─── Panel (flat, no blur) ─── */ -.panel { - background: var(--nebula-color-panel); - border: 1px solid var(--nebula-color-border); - border-radius: var(--nebula-radius-md); - padding: var(--nebula-spacing-lg); -} - -/* ─── Focusable — flat neon border, no glow/blur ─── */ -.focusable { - border: 2px solid transparent; - border-radius: var(--nebula-radius-md); - outline: none; - position: relative; - overflow: hidden; - cursor: pointer; - will-change: transform, border-color; - transform: translateZ(0); - transition: - transform var(--nebula-duration-nav) var(--nebula-ease-console), - border-color var(--nebula-duration-nav) var(--nebula-ease-console), - background-color var(--nebula-duration-nav) var(--nebula-ease-standard); -} - -.focusable.is-focused { - border-color: var(--nebula-color-accent); - transform: scale(1.03) translateZ(0); -} - -.sidebar-nav-item.focusable.is-focused, -.guide-rail-item.focusable.is-focused, -.guide-nav-item.focusable.is-focused { - border-top-color: transparent; - border-bottom-color: transparent; - border-left-color: var(--nebula-color-accent); - border-right-color: var(--nebula-color-purple); - transform: none; -} - -.focusable.is-pressed { - animation: uiPressPulse var(--nebula-duration-fast) var(--nebula-ease-snap); -} - -/* ─── Tile ─── */ -.tile { - min-height: 160px; - min-width: 280px; - display: flex; - flex-direction: column; - justify-content: flex-end; - gap: var(--nebula-spacing-xs); - background: var(--nebula-color-panel-alt); - color: var(--nebula-color-text); - border-radius: var(--nebula-radius-md); - padding: var(--nebula-spacing-lg); - transform-origin: center; - position: relative; - overflow: hidden; - border: 2px solid var(--nebula-color-border); -} - -.tile.is-focused { - border-color: var(--nebula-color-accent); - transform: scale(1.04) translateZ(0); -} - -.tile-icon { - font-size: 38px; - line-height: 1; - opacity: 0.9; - transition: transform var(--nebula-duration-nav) var(--nebula-ease-console); -} - -.tile.is-focused .tile-icon { - transform: scale(1.06) translateY(-2px); -} - -.tile-label { - margin: 0; - font-size: clamp(20px, 2vw, 26px); - font-weight: 700; - letter-spacing: -0.01em; - z-index: 1; -} - -.tile-meta { - margin: 0; - font-size: 14px; - color: var(--nebula-color-muted); - z-index: 1; -} - -.tile-accent-bar { - position: absolute; - left: 0; - bottom: 0; - right: 0; - height: 3px; - background: var(--nebula-color-accent); - opacity: 0; - transform: scaleX(0); - transform-origin: left center; - transition: - opacity var(--nebula-duration-nav) var(--nebula-ease-console), - transform var(--nebula-duration-nav) var(--nebula-ease-console); -} - -.dashboard-tile.is-focused .tile-accent-bar { - opacity: 1; - transform: scaleX(1); -} - -/* ─── Controller button prompts ─── */ -.btn-prompt { - display: inline-flex; - align-items: center; - justify-content: center; - width: 20px; - height: 20px; - border-radius: 50%; - font-size: 11px; - font-weight: 800; - flex-shrink: 0; - line-height: 1; -} - -.btn-a { background: var(--btn-a); color: #fff; } -.btn-b { background: var(--btn-b); color: #fff; } -.btn-x { background: var(--btn-x); color: #fff; } -.btn-y { background: var(--btn-y); color: #fff; } - -.btn-glyph { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0 6px; - height: 18px; - border-radius: var(--nebula-radius-sm); - font-size: 11px; - font-weight: 700; - background: rgba(255, 255, 255, 0.1); - border: 1px solid rgba(255, 255, 255, 0.18); - color: var(--nebula-color-muted); -} - -/* ─── Button-like ─── */ -.button-like { - background: var(--nebula-color-panel-alt); - color: var(--nebula-color-text); - padding: var(--nebula-spacing-md) var(--nebula-spacing-lg); - border-radius: var(--nebula-radius-sm); -} - -/* ─── Animations ─── */ -@keyframes uiPressPulse { - 0% { transform: scale(1); } - 40% { transform: scale(0.97); } - 100% { transform: scale(1); } -} diff --git a/Bigscreen/src/styles/guide.css b/Bigscreen/src/styles/guide.css deleted file mode 100644 index 2a6fc50..0000000 --- a/Bigscreen/src/styles/guide.css +++ /dev/null @@ -1,618 +0,0 @@ -/* ─── Nebula guide sidebar (Xbox-inspired shell navigation) ─── */ - -:root { - --guide-rail-width: 88px; - --guide-panel-width: min(400px, 92vw); - --guide-glass: rgba(8, 10, 22, 0.92); - --guide-glass-border: rgba(79, 216, 255, 0.14); - --guide-accent-glow: rgba(79, 216, 255, 0.35); -} - -.guide-shell { - position: relative; - flex-shrink: 0; - width: var(--guide-rail-width); - height: 100%; - z-index: 30; -} - -.body-no-sidebar .guide-shell, -.body-no-sidebar .guide-backdrop { - display: none; -} - -/* ─── Collapsed rail ─── */ -.guide-rail { - width: var(--guide-rail-width); - height: 100%; - display: flex; - flex-direction: column; - align-items: center; - padding: 14px 0 12px; - background: linear-gradient(180deg, rgba(10, 12, 24, 0.98) 0%, rgba(7, 9, 18, 0.98) 100%); - border-right: 1px solid var(--guide-glass-border); - transition: opacity var(--nebula-duration-nav) var(--nebula-ease-standard); -} - -.guide-expanded .guide-rail { - opacity: 0; - pointer-events: none; -} - -.guide-rail-brand { - width: 52px; - height: 52px; - border-radius: 50%; - border: 1.5px solid rgba(79, 216, 255, 0.3); - background: radial-gradient(circle at 38% 32%, rgba(79, 216, 255, 0.55), rgba(157, 79, 224, 0.75) 55%, rgba(30, 16, 60, 1)); - color: var(--nebula-color-text); - display: grid; - place-items: center; - margin-bottom: 18px; - cursor: pointer; - padding: 0; -} - -.guide-brand-mark { - font-size: 20px; - line-height: 1; - text-shadow: 0 0 12px var(--guide-accent-glow); -} - -.guide-rail-nav { - list-style: none; - margin: 0; - padding: 0; - width: 100%; - flex: 1; - display: flex; - flex-direction: column; - gap: 4px; -} - -.guide-rail-item { - display: flex; - align-items: center; - justify-content: center; - min-height: 52px; - margin: 0 8px; - border-radius: var(--nebula-radius-md); - border: 2px solid transparent; - border-left: 3px solid transparent; - color: var(--nebula-color-muted); - cursor: pointer; - transition: - color var(--nebula-duration-fast) var(--nebula-ease-standard), - background var(--nebula-duration-fast) var(--nebula-ease-standard), - border-color var(--nebula-duration-fast) var(--nebula-ease-standard), - box-shadow var(--nebula-duration-fast) var(--nebula-ease-standard); -} - -.guide-rail-item.is-active { - color: var(--nebula-color-text); - background: rgba(79, 216, 255, 0.08); - border-left-color: var(--nebula-color-accent); - box-shadow: inset 3px 0 0 var(--nebula-color-accent); -} - -.guide-rail-item.is-focused { - color: var(--nebula-color-text); - background: rgba(79, 216, 255, 0.14); - border-color: rgba(79, 216, 255, 0.45); - border-left-color: var(--nebula-color-accent); - box-shadow: - 0 0 0 1px rgba(79, 216, 255, 0.25), - 0 0 18px rgba(79, 216, 255, 0.12); - transform: none; -} - -.guide-rail-icon { - font-size: 22px; - line-height: 1; -} - -.guide-rail-footer { - display: flex; - flex-direction: column; - align-items: center; - gap: 10px; - padding-top: 12px; - border-top: 1px solid rgba(255, 255, 255, 0.06); - width: 100%; -} - -.guide-rail-expand, -.guide-rail-profile { - width: 48px; - height: 48px; - border-radius: var(--nebula-radius-md); - border: 2px solid transparent; - background: rgba(255, 255, 255, 0.04); - color: var(--nebula-color-muted); - cursor: pointer; - display: grid; - place-items: center; - padding: 0; -} - -.guide-rail-expand.is-focused, -.guide-rail-profile.is-focused { - border-color: var(--nebula-color-accent); - color: var(--nebula-color-text); - transform: none; -} - -.guide-rail-expand-icon { - font-size: 20px; -} - -.guide-rail-avatar { - width: 34px; - height: 34px; - border-radius: 50%; - display: grid; - place-items: center; - font-size: 12px; - font-weight: 700; - background: linear-gradient(145deg, rgba(79, 216, 255, 0.35), rgba(157, 79, 224, 0.5)); - border: 2px solid rgba(79, 216, 255, 0.35); - color: var(--nebula-color-text); -} - -/* ─── Expanded guide panel (overlay) ─── */ -.guide-panel { - position: fixed; - left: 0; - top: 0; - bottom: 0; - width: var(--guide-panel-width); - z-index: 35; - transform: translateX(-104%); - transition: transform var(--nebula-duration-slow) var(--nebula-ease-console); - pointer-events: none; -} - -.guide-expanded .guide-panel { - transform: translateX(0); - pointer-events: auto; -} - -.guide-panel-inner { - height: 100%; - display: flex; - flex-direction: column; - overflow: hidden; - background: var(--guide-glass); - border-right: 1px solid var(--guide-glass-border); - box-shadow: - 8px 0 40px rgba(0, 0, 0, 0.45), - inset -1px 0 0 rgba(157, 79, 224, 0.12); -} - -.guide-header { - padding: 20px 22px 16px; - display: grid; - gap: 12px; -} - -.guide-profile { - display: flex; - align-items: center; - gap: 14px; -} - -.guide-profile-avatar, -.guide-footer-avatar { - width: 48px; - height: 48px; - border-radius: 50%; - display: grid; - place-items: center; - font-size: 15px; - font-weight: 700; - flex-shrink: 0; - background: linear-gradient(145deg, rgba(79, 216, 255, 0.4), rgba(157, 79, 224, 0.55)); - border: 2px solid rgba(79, 216, 255, 0.4); - color: var(--nebula-color-text); - box-shadow: 0 0 20px rgba(79, 216, 255, 0.15); -} - -.guide-profile-name { - margin: 0; - font-size: 20px; - font-weight: 700; - letter-spacing: -0.02em; -} - -.guide-profile-status { - margin: 4px 0 0; - display: flex; - align-items: center; - gap: 8px; - font-size: 13px; - color: var(--nebula-color-muted); -} - -.guide-status-dot { - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--nebula-color-success); - box-shadow: 0 0 8px rgba(79, 255, 136, 0.6); -} - -.guide-header-meta { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; -} - -.guide-header-time { - margin: 0; - font-size: 22px; - font-weight: 600; - font-variant-numeric: tabular-nums; -} - -.guide-header-date { - margin: 2px 0 0; - font-size: 12px; - color: var(--nebula-color-muted); -} - -.guide-header-indicators { - display: flex; - gap: 10px; - color: var(--nebula-color-accent); - font-size: 14px; - opacity: 0.85; -} - -.guide-header-brand { - margin: 0; - font-size: 11px; - font-weight: 700; - letter-spacing: 0.14em; - text-transform: uppercase; - color: var(--nebula-color-muted); - display: flex; - align-items: center; - gap: 8px; -} - -.guide-header-brand-mark { - color: var(--nebula-color-accent); - text-shadow: 0 0 10px var(--guide-accent-glow); -} - -.guide-divider { - border: none; - height: 1px; - margin: 0 18px; - background: linear-gradient(90deg, transparent, rgba(79, 216, 255, 0.2), transparent); -} - -.guide-section { - padding: 12px 18px; - min-height: 0; -} - -.guide-section-recent { - flex: 1; - overflow: hidden; - display: flex; - flex-direction: column; -} - -.guide-section-title { - margin: 0 0 10px; - font-size: 11px; - font-weight: 700; - letter-spacing: 0.12em; - text-transform: uppercase; - color: var(--nebula-color-muted); -} - -.guide-nav-list { - list-style: none; - margin: 0; - padding: 0; - display: flex; - flex-direction: column; - gap: 6px; -} - -.guide-nav-item { - display: flex; - align-items: center; - gap: 14px; - min-height: 48px; - padding: 0 14px; - border-radius: var(--nebula-radius-md); - border: 2px solid transparent; - border-left: 3px solid transparent; - color: var(--nebula-color-muted); - cursor: pointer; - transition: - background var(--nebula-duration-fast) var(--nebula-ease-standard), - border-color var(--nebula-duration-fast) var(--nebula-ease-standard), - color var(--nebula-duration-fast) var(--nebula-ease-standard); -} - -.guide-nav-item.is-active { - color: var(--nebula-color-text); - background: rgba(79, 216, 255, 0.08); - border-left-color: var(--nebula-color-accent); -} - -.guide-nav-item.is-active::after { - content: ""; - margin-left: auto; - width: 6px; - height: 6px; - border-radius: 50%; - background: var(--nebula-color-accent); - box-shadow: 0 0 8px var(--guide-accent-glow); -} - -.guide-nav-item.is-focused { - color: var(--nebula-color-text); - background: rgba(79, 216, 255, 0.16); - border-color: rgba(79, 216, 255, 0.45); - border-left-color: var(--nebula-color-accent); - box-shadow: 0 0 22px rgba(79, 216, 255, 0.1); - transform: none; -} - -.guide-nav-icon { - font-size: 20px; - width: 28px; - text-align: center; -} - -.guide-nav-label { - font-size: 16px; - font-weight: 600; -} - -.guide-quick-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 8px; -} - -.guide-quick-item { - display: flex; - align-items: center; - gap: 10px; - min-height: 44px; - padding: 8px 12px; - border-radius: var(--nebula-radius-md); - border: 2px solid transparent; - background: rgba(255, 255, 255, 0.03); - color: var(--nebula-color-muted); - cursor: pointer; - text-align: left; - font: inherit; -} - -.guide-quick-item.is-focused { - border-color: var(--nebula-color-accent); - color: var(--nebula-color-text); - background: rgba(79, 216, 255, 0.12); - transform: none; -} - -.guide-quick-icon { - font-size: 16px; - opacity: 0.9; -} - -.guide-quick-label { - font-size: 13px; - font-weight: 600; -} - -.guide-recent-list { - display: flex; - flex-direction: column; - gap: 6px; - overflow-y: auto; - padding-right: 4px; - max-height: min(220px, 28vh); -} - -.guide-recent-item { - display: flex; - align-items: center; - gap: 12px; - width: 100%; - min-height: 56px; - padding: 8px 10px; - border-radius: var(--nebula-radius-md); - border: 2px solid transparent; - background: rgba(255, 255, 255, 0.02); - color: var(--nebula-color-text); - cursor: pointer; - text-align: left; - font: inherit; -} - -.guide-recent-item.is-focused { - border-color: var(--nebula-color-accent); - background: rgba(79, 216, 255, 0.1); - transform: none; -} - -.guide-recent-cover { - width: 40px; - height: 40px; - border-radius: var(--nebula-radius-sm); - flex-shrink: 0; - background: linear-gradient(135deg, var(--recent-accent, #1a2a44), rgba(0, 0, 0, 0.5)); - border: 1px solid rgba(255, 255, 255, 0.1); -} - -.guide-recent-meta { - display: flex; - flex-direction: column; - gap: 4px; - min-width: 0; -} - -.guide-recent-title { - font-size: 14px; - font-weight: 600; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.guide-recent-sub { - display: flex; - align-items: center; - gap: 8px; - font-size: 11px; - color: var(--nebula-color-muted); -} - -.guide-source-badge { - padding: 2px 7px; - border-radius: var(--nebula-radius-pill); - font-size: 10px; - font-weight: 700; - letter-spacing: 0.04em; - text-transform: uppercase; - background: rgba(79, 216, 255, 0.12); - color: var(--nebula-color-accent); -} - -.guide-source-badge[data-source="steam"] { - background: rgba(27, 40, 56, 0.9); - color: #66c0f4; -} - -.guide-source-badge[data-source="gog"] { - background: rgba(60, 20, 90, 0.5); - color: #c9a0ff; -} - -.guide-source-badge[data-source="epic"] { - background: rgba(30, 30, 30, 0.8); - color: #f0f0f0; -} - -.guide-recent-empty { - padding: 20px 12px; - text-align: center; - border-radius: var(--nebula-radius-md); - border: 1px dashed rgba(255, 255, 255, 0.1); - background: rgba(0, 0, 0, 0.2); -} - -.guide-recent-empty-icon { - font-size: 28px; - opacity: 0.4; - display: block; - margin-bottom: 8px; -} - -.guide-recent-empty-title { - margin: 0 0 6px; - font-weight: 600; -} - -.guide-recent-empty-copy { - margin: 0; - font-size: 13px; - color: var(--nebula-color-muted); -} - -.guide-footer { - margin-top: auto; - padding: 14px 18px 16px; - border-top: 1px solid rgba(255, 255, 255, 0.06); - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 8px; -} - -.guide-footer-btn { - display: inline-flex; - align-items: center; - gap: 8px; - min-height: 40px; - padding: 0 12px; - border-radius: var(--nebula-radius-pill); - border: 2px solid transparent; - background: rgba(255, 255, 255, 0.04); - color: var(--nebula-color-muted); - font-size: 13px; - font-weight: 600; - cursor: pointer; - font: inherit; -} - -.guide-footer-btn.is-focused { - border-color: var(--nebula-color-accent); - color: var(--nebula-color-text); - transform: none; -} - -.guide-footer-avatar { - width: 28px; - height: 28px; - font-size: 10px; -} - -.guide-version { - flex: 1 1 100%; - margin: 6px 0 0; - font-size: 11px; - color: var(--nebula-color-muted); - opacity: 0.75; -} - -/* Dim layer over main content when guide is open (scoped to .app-main-area) */ -.guide-backdrop { - position: absolute; - inset: 0; - z-index: 10; - background: rgba(3, 5, 14, 0.62); - opacity: 0; - pointer-events: none; - transition: opacity var(--nebula-duration-slow) var(--nebula-ease-standard); -} - -body.guide-expanded .guide-backdrop, -.guide-backdrop:not([hidden]) { - opacity: 1; - pointer-events: auto; -} - -.guide-backdrop[hidden] { - display: block !important; - opacity: 0; - pointer-events: none; -} - -/* Focus overrides for guide items */ -.guide-rail-item.focusable.is-focused, -.guide-nav-item.focusable.is-focused, -.guide-quick-item.focusable.is-focused, -.guide-recent-item.focusable.is-focused, -.guide-footer-btn.focusable.is-focused, -.guide-rail-brand.focusable.is-focused, -.guide-rail-expand.focusable.is-focused, -.guide-rail-profile.focusable.is-focused { - transform: none; -} - -@media (max-width: 720px) { - :root { - --guide-rail-width: 72px; - --guide-panel-width: min(100vw, 100%); - } -} diff --git a/Bigscreen/src/styles/shell-guidelines.md b/Bigscreen/src/styles/shell-guidelines.md deleted file mode 100644 index 384ac58..0000000 --- a/Bigscreen/src/styles/shell-guidelines.md +++ /dev/null @@ -1,53 +0,0 @@ -# Nebula Shell UI Guidelines (Dashboard Refresh) - -## Layout Structure -- Home uses a left-aligned horizontal tile rail (`.tile-rail`) with one primary row of large app tiles. -- Top bar is shared across shell views with brand left and time/profile right. -- Settings uses a horizontal category bar at top and card panel content below. -- Lock screen keeps immersive centered time/date and reveals PIN panel on first input. - -## Animation Guidelines -- Use cubic-bezier curves only: - - `--nebula-ease-console`: focus travel and panel transitions. - - `--nebula-ease-standard`: opacity and ambient motion. - - `--nebula-ease-snap`: press pulse feedback. -- Focus transitions target `120ms–180ms` (`--nebula-duration-fast` / `--nebula-duration-nav`). -- Use transform/opacity for movement and avoid layout-triggering transitions. -- Page changes use `.view` to `.view-entered` slide-in transitions. -- Press feedback uses `.is-pressed` and `uiPressPulse` keyframes. - -## Component Breakdown - -### TopBar -- Class roots: `.shell-topbar`, `.shell-brand`, `.shell-status`, `.shell-time`, `.shell-avatar`, `.shell-accent-line`. -- Purpose: persistent identity + status + navigation-reactive accent line. -- Reactive token: `--nebula-accent-line-x` updates from focus manager. - -### TileRow -- Class root: `.tile-rail`. -- Purpose: horizontal app rail with controller-first left/right travel and smooth scroll. -- Behavior: focus auto-centers via `scrollTo` and updates parallax variables. - -### Tile -- Class roots: `.tile`, `.dashboard-tile`, `.tile-icon`, `.tile-label`, `.tile-meta`. -- Focus state: `.is-focused` scales tile and adds cyan glow outline. -- Press state: `.is-pressed` triggers pulse animation and hook events. - -### BackgroundLayer -- DOM root: `#nebula-background` with `.nebula-layer` children (`gradient`, `starfield`, `fog`, `vignette`). -- Purpose: animated nebula depth stack with subtle star motion and fog drift. -- Parallax token: `--bg-parallax-x` supports focus-driven depth shift. - -### FocusManager -- Implementation root: `src/core/nav.js`. -- Responsibilities: - - Maintain focused element and directional navigation. - - Apply `.is-focused` state and `aria-selected`. - - Publish focus telemetry events (`nebula-focus-change`). - - Update CSS vars (`--nebula-accent-line-x`, `--nebula-focus-strength`). - -## UI Hook Contract (No Audio Implementation) -- `window` `CustomEvent("nebula-ui-hook")` details: - - `type: "focus" | "move" | "accept" | "back"` - - Optional metadata (`target`, `action`, `focusKey`) -- Intended use: external UI audio/haptics bridge without coupling shell visuals to playback. diff --git a/Bigscreen/src/styles/theme.css b/Bigscreen/src/styles/theme.css deleted file mode 100644 index e90c6ba..0000000 --- a/Bigscreen/src/styles/theme.css +++ /dev/null @@ -1,58 +0,0 @@ -:root { - /* Core palette — solid, no heavy transparency */ - --nebula-color-bg: #070a14; - --nebula-color-bg-deep: #050810; - --nebula-color-sidebar: #0a0c18; - --nebula-color-panel: #0d1020; - --nebula-color-panel-alt: #111425; - --nebula-color-border: rgba(255, 255, 255, 0.08); - --nebula-color-border-mid: rgba(255, 255, 255, 0.14); - - --nebula-color-text: #f2f7ff; - --nebula-color-muted: #7a8fa8; - - /* Neon accents */ - --nebula-color-accent: #4fd8ff; - --nebula-color-purple: #9d4fe0; - --nebula-color-danger: #ff4f6b; - --nebula-color-success: #4fff88; - --nebula-color-focus: #4fd8ff; - - /* Controller button colours (flat solid) */ - --btn-a: #4caf50; - --btn-b: #f44336; - --btn-x: #2196f3; - --btn-y: #ff9800; - - --nebula-spacing-xs: 6px; - --nebula-spacing-sm: 10px; - --nebula-spacing-md: 16px; - --nebula-spacing-lg: 24px; - --nebula-spacing-xl: 36px; - - --nebula-radius-sm: 6px; - --nebula-radius-md: 10px; - --nebula-radius-lg: 16px; - --nebula-radius-pill: 999px; - - --nebula-type-body: 18px; - --nebula-type-title: 24px; - --nebula-type-display: 34px; - - --nebula-ease-standard: cubic-bezier(0.25, 0.1, 0.25, 1); - --nebula-ease-console: cubic-bezier(0.19, 0.82, 0.18, 1); - --nebula-ease-snap: cubic-bezier(0.32, 0.94, 0.18, 1); - - --nebula-duration-fast: 100ms; - --nebula-duration-nav: 160ms; - --nebula-duration-slow: 300ms; - - /* No glow/shadow — flat */ - --nebula-depth-shadow: 0 4px 16px rgba(0, 0, 0, 0.5); - - /* Overlay backdrop (power menu, etc.) — kept solid/dark */ - --nebula-color-overlay: rgba(5, 7, 18, 0.88); - - /* Backward-compat aliases */ - --nebula-color-panelAlt: #111425; -} diff --git a/Bigscreen/src/views/home/home.css b/Bigscreen/src/views/home/home.css deleted file mode 100644 index 4611b87..0000000 --- a/Bigscreen/src/views/home/home.css +++ /dev/null @@ -1,594 +0,0 @@ -/* ════════════════════════════════════════════════════════ - HOME VIEW — Sci-fi dashboard layout - ════════════════════════════════════════════════════════ */ - -.home-view { - display: flex; - flex-direction: column; - height: 100%; - padding: 0; - gap: 0; - overflow: hidden; -} - -/* ── Top status bar ──────────────────────────────────── */ -.home-topbar { - display: flex; - align-items: center; - justify-content: space-between; - padding: 16px 24px 12px; - flex-shrink: 0; - border-bottom: 1px solid var(--nebula-color-border); -} - -.home-time { - font-size: clamp(36px, 4vw, 52px); - font-weight: 800; - letter-spacing: -0.03em; - color: var(--nebula-color-text); - line-height: 1; -} - -.home-status-icons { - display: flex; - align-items: center; - gap: 14px; - color: var(--nebula-color-muted); -} - -.home-status-icon { - display: flex; - align-items: center; - justify-content: center; -} - -/* ── Body: two columns ────────────────────────────────── */ -.home-body { - flex: 1; - display: grid; - grid-template-columns: 1fr 310px; - gap: 16px; - padding: 14px 20px 0; - min-height: 0; - overflow: hidden; -} - -/* ── Center column ───────────────────────────────────── */ -.home-center { - display: flex; - flex-direction: column; - gap: 12px; - min-height: 0; - overflow: hidden; -} - -/* ── Category tabs ───────────────────────────────────── */ -.home-tabs { - display: flex; - align-items: center; - gap: 20px; - flex-shrink: 0; - border-bottom: 1px solid var(--nebula-color-border); - padding-bottom: 10px; -} - -.home-tab { - background: none; - color: var(--nebula-color-muted); - font-size: 16px; - font-weight: 700; - padding: 0 2px 8px; - border: none; - border-bottom: 2px solid transparent; - border-radius: 0; - cursor: pointer; - transition: - color var(--nebula-duration-fast) var(--nebula-ease-standard), - border-bottom-color var(--nebula-duration-fast) var(--nebula-ease-standard); - transform: none; -} - -.home-tab.is-active { - color: var(--nebula-color-text); - border-bottom-color: var(--nebula-color-accent); -} - -.home-tab.is-focused { - color: var(--nebula-color-accent); - border-bottom-color: var(--nebula-color-accent); - transform: none; - border-radius: 0; -} - -.tab-hint { - margin-left: auto; - display: inline-flex; - align-items: center; - gap: 4px; - font-size: 13px; - color: var(--nebula-color-muted); -} - -/* ── Hero card ───────────────────────────────────────── */ -.hero-card { - flex: 1; - position: relative; - border-radius: var(--nebula-radius-lg); - overflow: hidden; - min-height: 0; - border: 2px solid var(--nebula-color-border); - transition: border-color var(--nebula-duration-nav) var(--nebula-ease-console); - transform: none; -} - -.hero-card.is-focused { - border-color: var(--nebula-color-accent); - transform: none; -} - -/* Game art layers */ -.hero-art { - position: absolute; - inset: 0; - overflow: hidden; -} - -.hero-art-bg { - position: absolute; - inset: 0; - background: - linear-gradient(165deg, - #0d0e00 0%, - #1a1600 20%, - #2a2200 40%, - #3a3000 60%, - rgba(180, 150, 0, 0.25) 80%, - rgba(220, 180, 0, 0.15) 100% - ); -} - -.hero-art.has-image .hero-art-bg { - background: - linear-gradient(165deg, rgba(7, 10, 20, 0.36), rgba(7, 10, 20, 0.86)), - var(--hero-image) center / cover no-repeat; -} - -.hero-art-mid { - position: absolute; - inset: 0; - background: - radial-gradient(ellipse at 65% 50%, rgba(255, 200, 0, 0.18) 0%, transparent 55%), - radial-gradient(ellipse at 30% 80%, rgba(0, 80, 180, 0.2) 0%, transparent 45%); -} - -/* Stylised "character" area */ -.hero-art-character { - position: absolute; - bottom: 0; - right: 10%; - width: 42%; - height: 90%; - background: - linear-gradient(180deg, - transparent 0%, - rgba(60, 50, 0, 0.4) 30%, - rgba(100, 90, 0, 0.6) 60%, - rgba(30, 25, 0, 0.9) 100% - ); - clip-path: polygon(15% 0%, 85% 0%, 100% 100%, 0% 100%); -} - -.hero-title-watermark { - position: absolute; - bottom: 110px; - right: 5%; - font-size: clamp(32px, 5vw, 64px); - font-weight: 900; - color: rgba(255, 220, 0, 0.85); - letter-spacing: -0.03em; - line-height: 0.95; - text-align: right; - text-transform: uppercase; - pointer-events: none; - font-style: italic; -} - -.hero-art.has-image .hero-art-mid, -.hero-art.has-image .hero-art-character { - display: none; -} - -/* Controller overlay */ -.hero-ctrl-overlay { - position: absolute; - top: 20px; - left: 20px; - display: flex; - gap: 14px; - align-items: center; - opacity: 0.55; -} - -.ctrl-glyph { - display: block; -} - -.hero-l-badge { - width: 22px; - height: 22px; - border-radius: var(--nebula-radius-sm); - background: rgba(255, 255, 255, 0.15); - border: 1px solid rgba(255, 255, 255, 0.25); - display: flex; - align-items: center; - justify-content: center; - font-size: 11px; - font-weight: 800; - color: rgba(255, 255, 255, 0.7); -} - -/* Gradient overlay + info */ -.hero-overlay { - position: absolute; - inset: 0; - background: linear-gradient( - to top, - rgba(7, 10, 20, 0.98) 0%, - rgba(7, 10, 20, 0.7) 36%, - transparent 65% - ); - display: flex; - flex-direction: column; - justify-content: flex-end; - padding: 20px 24px; -} - -.hero-info { - display: flex; - flex-direction: column; - gap: 14px; -} - -.hero-game-title { - margin: 0; - font-size: clamp(26px, 3.2vw, 42px); - font-weight: 800; - letter-spacing: -0.02em; - color: #fff; - line-height: 1.1; -} - -.hero-game-meta { - margin: 0; - color: var(--nebula-color-muted); - font-size: 14px; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.08em; -} - -.hero-actions { - display: flex; - gap: 10px; - flex-wrap: wrap; -} - -.hero-btn { - display: inline-flex; - align-items: center; - gap: 8px; - padding: 9px 18px; - border-radius: var(--nebula-radius-sm); - font-size: 14px; - font-weight: 700; - cursor: pointer; - background: rgba(255, 255, 255, 0.08); - border: 1.5px solid rgba(255, 255, 255, 0.2); - color: #fff; - transition: - background var(--nebula-duration-fast) var(--nebula-ease-standard), - border-color var(--nebula-duration-fast) var(--nebula-ease-standard), - transform var(--nebula-duration-fast) var(--nebula-ease-console); - transform: none; -} - -.hero-btn-primary { - background: var(--nebula-color-accent); - border-color: transparent; - color: #070a14; -} - -.hero-btn.is-focused { - border-color: var(--nebula-color-accent); - transform: translateY(-1px); -} - -.hero-btn-primary.is-focused { - border-color: #fff; - transform: translateY(-1px); -} - -.hero-dots { - display: flex; - align-items: center; - gap: 6px; - margin-top: 10px; -} - -.hero-dot { - width: 6px; - height: 6px; - border-radius: 50%; - background: rgba(255, 255, 255, 0.28); - transition: all var(--nebula-duration-fast) var(--nebula-ease-standard); -} - -.hero-dot.is-active { - width: 22px; - border-radius: 3px; - background: var(--nebula-color-accent); -} - -/* ── Featured strip ──────────────────────────────────── */ -.featured-strip { - flex-shrink: 0; - padding-bottom: 12px; -} - -.section-label { - font-size: 12px; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.12em; - color: var(--nebula-color-muted); - margin: 0 0 10px; -} - -.featured-row { - display: flex; - gap: 10px; -} - -.featured-thumb { - flex: 1; - height: 68px; - border-radius: var(--nebula-radius-md); - background: - linear-gradient(135deg, rgba(79, 216, 255, 0.18), rgba(125, 89, 255, 0.18)), - linear-gradient(135deg, var(--thumb-a, #1a1a2e), var(--thumb-b, #2a1050)); - border: 2px solid var(--nebula-color-border); - cursor: pointer; - transition: border-color var(--nebula-duration-fast); - overflow: hidden; - padding: 0; -} - -.featured-thumb.has-image { - background: - linear-gradient(180deg, rgba(7, 10, 20, 0), rgba(7, 10, 20, 0.64)), - var(--featured-image) center / cover no-repeat; -} - -.featured-thumb.is-focused { - border-color: var(--nebula-color-accent); - transform: translateY(-2px); -} - -/* ── Right panel ─────────────────────────────────────── */ -.home-right-panel { - display: flex; - flex-direction: column; - gap: 12px; - overflow: hidden; - min-height: 0; - padding-bottom: 12px; -} - -.panel-header-row { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 10px; -} - -.panel-heading { - font-size: 15px; - font-weight: 700; - color: var(--nebula-color-text); - margin: 0; - text-transform: uppercase; - letter-spacing: 0.06em; -} - -.panel-heading-sm { - font-size: 13px; - font-weight: 700; - color: var(--nebula-color-text); - margin: 0 0 8px; - text-transform: uppercase; - letter-spacing: 0.06em; -} - -/* ── Quick launch grid ───────────────────────────────── */ -.quick-launch { - flex-shrink: 0; -} - -.quick-grid { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 8px; -} - -.quick-tile { - position: relative; - border-radius: var(--nebula-radius-md); - overflow: hidden; - background: var(--nebula-color-panel); - border: 2px solid var(--nebula-color-border); - cursor: pointer; - padding: 0; - text-align: left; - color: var(--nebula-color-text); - transition: - border-color var(--nebula-duration-fast) var(--nebula-ease-console), - transform var(--nebula-duration-fast) var(--nebula-ease-console); - transform: translateZ(0); -} - -.quick-tile.is-focused { - border-color: var(--nebula-color-accent); - transform: scale(1.04) translateZ(0); -} - -.quick-tile-art { - height: 70px; - display: grid; - place-items: center; - background: - radial-gradient(circle at top left, rgba(79, 216, 255, 0.22), transparent 46%), - linear-gradient(135deg, var(--ta, #1a1a2e), var(--tb, #2a2050)); - position: relative; - overflow: hidden; -} - -.quick-tile-art.has-image { - background: - linear-gradient(180deg, rgba(7, 10, 20, 0), rgba(7, 10, 20, 0.48)), - var(--quick-image) center / cover no-repeat; -} - -.quick-tile-initials { - display: grid; - place-items: center; - width: 36px; - height: 36px; - border-radius: 12px; - background: rgba(0, 0, 0, 0.24); - color: var(--nebula-color-text); - font-size: 13px; - font-weight: 900; - letter-spacing: 0.04em; -} - -.quick-tile-art.has-image .quick-tile-initials { - display: none; -} - -.tile-badge { - position: absolute; - bottom: 4px; - right: 4px; - font-size: 12px; - line-height: 1; -} - -.quick-tile-footer { - padding: 6px 8px 7px; -} - -.quick-tile-name { - margin: 0; - font-size: 11px; - font-weight: 700; - color: var(--nebula-color-text); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - line-height: 1.3; -} - -.quick-tile-meta { - margin: 3px 0 0; - font-size: 10px; - color: var(--nebula-color-muted); - line-height: 1.3; -} - -/* Solid progress bar */ -.quick-tile-bar { - position: relative; - padding-left: 0; -} - -.quick-tile-bar::before { - content: ""; - display: block; - height: 2px; - background: var(--nebula-color-border); - border-radius: 1px; - margin-bottom: 3px; -} - -.quick-tile-bar::after { - content: ""; - display: block; - position: absolute; - top: 0; - left: 0; - width: var(--pct, 0%); - height: 2px; - background: var(--nebula-color-accent); - border-radius: 1px; -} - -/* ── Bottom side panels ──────────────────────────────── */ -.side-bottom-panels { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 10px; - flex-shrink: 0; -} - -.friends-panel, -.activity-panel { - background: var(--nebula-color-panel); - border: 1px solid var(--nebula-color-border); - border-radius: var(--nebula-radius-md); - padding: 12px; -} - -.friends-avatars { - display: flex; - gap: 6px; - flex-wrap: wrap; -} - -.home-placeholder-copy { - margin: 0; - color: var(--nebula-color-muted); - font-size: 11px; - line-height: 1.4; -} - -.friend-avatar { - width: 28px; - height: 28px; - border-radius: 50%; - background: var(--fc, var(--nebula-color-accent)); - opacity: 0.85; - border: 1.5px solid rgba(255, 255, 255, 0.12); -} - -.activity-row { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 4px; -} - -.activity-label { - font-size: 17px; - font-weight: 800; - color: var(--nebula-color-text); - line-height: 1; -} - -.activity-hint { - margin: 0; - font-size: 11px; - color: var(--nebula-color-muted); - display: inline-flex; - align-items: center; - gap: 4px; -} diff --git a/Bigscreen/src/views/home/home.html b/Bigscreen/src/views/home/home.html deleted file mode 100644 index ad2deec..0000000 --- a/Bigscreen/src/views/home/home.html +++ /dev/null @@ -1,60 +0,0 @@ -
    -
    -
    -

    Nebula OS

    -
    - -

    --:--

    -
    -
    -
    -
    - -
    -

    Dashboard

    -

    Jump back in

    -
    - -
    - - - - -
    -
    diff --git a/Bigscreen/src/views/home/home.js b/Bigscreen/src/views/home/home.js deleted file mode 100644 index 7acdfa4..0000000 --- a/Bigscreen/src/views/home/home.js +++ /dev/null @@ -1,456 +0,0 @@ -const getTauriCore = async () => { - const globalCore = window.__TAURI__?.core; - if (typeof globalCore?.invoke === "function") { - return { - invoke: globalCore.invoke, - convertFileSrc: typeof globalCore.convertFileSrc === "function" ? globalCore.convertFileSrc : null, - }; - } - - try { - const tauriCore = await import("@tauri-apps/api/core"); - return { - invoke: typeof tauriCore.invoke === "function" ? tauriCore.invoke : null, - convertFileSrc: typeof tauriCore.convertFileSrc === "function" ? tauriCore.convertFileSrc : null, - }; - } catch (_error) { - return { invoke: null, convertFileSrc: null }; - } -}; - -const sourceLabel = (source) => { - const labels = { - steam: "Steam", - epic: "Epic Games", - gog: "GOG", - local: "Local", - unknown: "Unknown", - }; - return labels[source] ?? "Library"; -}; - -const gameTitle = (game) => game?.userTitle || game?.title || "No games scanned yet"; - -const gameArtUrl = (game, convertFileSrc, key = "heroImage") => { - const path = game?.[key] || game?.coverImage || game?.iconImage; - if (!path || !convertFileSrc) return ""; - return convertFileSrc(path); -}; - -const initialsForTitle = (title) => - title - .split(/\s+/) - .filter(Boolean) - .slice(0, 2) - .map((part) => part[0]?.toUpperCase()) - .join("") || "OS"; - -const HOME_TEMPLATE = ` -
    - - -
    - --:-- -
    - - - - - - - - - -
    -
    - - -
    - - -
    - - - - - -
    - - - -
    -
    -

    Scan your device to populate Home

    -

    No games scanned yet

    -
    - - - -
    -
    - -
    -
    - - - -
    - - - -
    -
    -`; - -export const createHomeView = ({ state, renderView, openPowerMenu }) => ({ - id: "home", - render: () => HOME_TEMPLATE, - mount: async () => { - const view = document.querySelector("[data-view='home']"); - if (!view) return; - - view.querySelectorAll(".home-tab").forEach((tab) => { - tab.addEventListener("click", () => { - view.querySelectorAll(".home-tab").forEach((t) => { - t.classList.remove("is-active"); - t.setAttribute("aria-selected", "false"); - }); - tab.classList.add("is-active"); - tab.setAttribute("aria-selected", "true"); - }); - }); - - await hydrateHomeLibrary(view); - }, - getNavigationContract: () => { - const root = document.querySelector("[data-view='home']"); - return { - focusRoot: root, - defaultFocus: root?.querySelector("[data-focus-key='btn-continue']") ?? null, - layout: { type: "grid", cols: 6, rows: 4 }, - hintsTemplate: "#global-hints-template", - nebulaNavigation: state.nebula.navigation, - useNebulaNavigation: false, - onAccept: (element) => { - if (!element) return; - const target = element.dataset.target; - const focusKey = element.dataset.focusKey; - - if (target === "power") { - openPowerMenu(); - return; - } - if (target) { - state.activeView = target; - renderView(target); - return; - } - - if (element.dataset.gameId || element.dataset.quickSlot || element.dataset.featuredSlot) { - state.activeView = "library"; - renderView("library"); - return; - } - - if (focusKey === "tab-now-playing" || focusKey === "tab-featured") { - element.click(); - } - }, - onBack: () => { - state.locked = true; - state.activeView = "lock"; - renderView("lock"); - }, - onMenu: () => {}, - }; - }, -}); - -const hydrateHomeLibrary = async (view) => { - const { invoke, convertFileSrc } = await getTauriCore(); - if (!invoke) { - setEmptyHomeState(view, "Run NebulaOS with Tauri to load your library."); - return; - } - - try { - const games = await invoke("list_library_games"); - if (!Array.isArray(games) || games.length === 0) { - setEmptyHomeState(view, "Open Library and scan your device to populate Home."); - return; - } - - const sortedGames = [...games].sort((left, right) => { - if (left.userFavourite !== right.userFavourite) { - return left.userFavourite ? -1 : 1; - } - return gameTitle(left).localeCompare(gameTitle(right)); - }); - - renderHeroGame(view, sortedGames[0], convertFileSrc); - renderQuickLaunch(view, sortedGames.slice(0, 6), convertFileSrc); - renderFeatured(view, sortedGames.slice(0, 4), convertFileSrc); - renderActivity(view, sortedGames); - } catch (error) { - setEmptyHomeState(view, String(error)); - } -}; - -const setEmptyHomeState = (view, message) => { - view.querySelector("[data-hero-title]").textContent = "No games scanned yet"; - view.querySelector("[data-hero-meta]").textContent = message; - view.querySelector("[data-home-library-count]").textContent = "0 Games"; - view.querySelector("[data-home-activity-hint]").textContent = "Scan from Library to populate Home."; -}; - -const renderHeroGame = (view, game, convertFileSrc) => { - const title = gameTitle(game); - const hero = view.querySelector("[data-home-hero]"); - const heroArt = view.querySelector("[data-hero-art]"); - const titleNode = view.querySelector("[data-hero-title]"); - const metaNode = view.querySelector("[data-hero-meta]"); - const watermark = view.querySelector("[data-hero-watermark]"); - const imageUrl = gameArtUrl(game, convertFileSrc, "heroImage"); - - hero?.setAttribute("aria-label", `Featured game: ${title}`); - titleNode.textContent = title; - metaNode.textContent = `${sourceLabel(game.platformSource)} · ${game.metadataStatus === "needs_review" ? "Needs review" : "Ready"}`; - watermark.textContent = title.toUpperCase(); - - if (imageUrl) { - heroArt.style.setProperty("--hero-image", `url("${imageUrl}")`); - heroArt.classList.add("has-image"); - } else { - heroArt.style.removeProperty("--hero-image"); - heroArt.classList.remove("has-image"); - } -}; - -const renderQuickLaunch = (view, games, convertFileSrc) => { - view.querySelectorAll("[data-quick-slot]").forEach((tile, index) => { - const game = games[index]; - const art = tile.querySelector(".quick-tile-art"); - const initials = tile.querySelector(".quick-tile-initials"); - const name = tile.querySelector(".quick-tile-name"); - const meta = tile.querySelector(".quick-tile-meta"); - - if (!game) { - tile.removeAttribute("data-game-id"); - tile.setAttribute("aria-label", "Empty library slot"); - art.style.removeProperty("--quick-image"); - art.classList.remove("has-image"); - initials.textContent = "OS"; - name.textContent = "Empty Slot"; - meta.textContent = "Scan for more games"; - return; - } - - const title = gameTitle(game); - const imageUrl = gameArtUrl(game, convertFileSrc, "coverImage"); - tile.dataset.gameId = String(game.id); - tile.setAttribute("aria-label", title); - name.textContent = title; - meta.textContent = sourceLabel(game.platformSource); - initials.textContent = initialsForTitle(title); - - if (imageUrl) { - art.style.setProperty("--quick-image", `url("${imageUrl}")`); - art.classList.add("has-image"); - } else { - art.style.removeProperty("--quick-image"); - art.classList.remove("has-image"); - } - }); -}; - -const renderFeatured = (view, games, convertFileSrc) => { - view.querySelectorAll("[data-featured-slot]").forEach((tile, index) => { - const game = games[index]; - if (!game) { - tile.removeAttribute("data-game-id"); - tile.setAttribute("aria-label", "Empty featured slot"); - tile.style.removeProperty("--featured-image"); - tile.classList.remove("has-image"); - return; - } - - const title = gameTitle(game); - const imageUrl = gameArtUrl(game, convertFileSrc, "heroImage"); - tile.dataset.gameId = String(game.id); - tile.setAttribute("aria-label", title); - if (imageUrl) { - tile.style.setProperty("--featured-image", `url("${imageUrl}")`); - tile.classList.add("has-image"); - } else { - tile.style.removeProperty("--featured-image"); - tile.classList.remove("has-image"); - } - }); -}; - -const renderActivity = (view, games) => { - const count = games.length; - view.querySelector("[data-home-library-count]").textContent = `${count} ${count === 1 ? "Game" : "Games"}`; - view.querySelector("[data-home-activity-hint]").textContent = "Home is showing your scanned local library."; -}; diff --git a/Bigscreen/src/views/library/library.css b/Bigscreen/src/views/library/library.css deleted file mode 100644 index 277aaf4..0000000 --- a/Bigscreen/src/views/library/library.css +++ /dev/null @@ -1,901 +0,0 @@ -.library-view { - --library-glow: rgba(79, 216, 255, 0.42); - height: 100%; - overflow: hidden; - background: - radial-gradient(circle at 18% 12%, rgba(79, 216, 255, 0.12), transparent 34%), - radial-gradient(circle at 78% 0%, rgba(157, 79, 224, 0.14), transparent 36%); -} - -.library-topbar { - display: flex; - align-items: center; - justify-content: space-between; - flex-shrink: 0; - padding: 18px 26px 12px; - border-bottom: 1px solid rgba(79, 216, 255, 0.14); -} - -.library-eyebrow, -.library-section-kicker { - margin: 0; - color: var(--nebula-color-accent); - font-size: 12px; - font-weight: 800; - letter-spacing: 0.16em; - text-transform: uppercase; -} - -.library-title { - margin: 4px 0 0; - font-size: clamp(38px, 4.5vw, 62px); - line-height: 1; - letter-spacing: -0.04em; -} - -.library-topbar-right, -.library-actions, -.library-system-status { - display: flex; - align-items: center; - gap: 12px; -} - -.library-system-status { - padding: 10px 14px; - border: 1px solid rgba(255, 255, 255, 0.11); - border-radius: var(--nebula-radius-pill); - background: rgba(10, 16, 34, 0.74); - color: var(--nebula-color-muted); - font-size: 13px; -} - -.library-system-status strong { - color: var(--nebula-color-text); - font-size: 16px; -} - -.library-status-dot { - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--nebula-color-success); - box-shadow: 0 0 14px rgba(79, 255, 136, 0.55); -} - -.library-action, -.library-category-tab, -.library-genre-tab, -.library-filter-chip, -.library-sort-option, -.library-detail-button { - color: var(--nebula-color-text); - border: 2px solid rgba(255, 255, 255, 0.09); - background: rgba(15, 23, 46, 0.82); - box-shadow: none; -} - -.library-action { - min-width: 132px; - padding: 12px 18px; - border-radius: var(--nebula-radius-pill); - font-weight: 800; -} - -.library-console { - flex: 1; - min-height: 0; - padding: 16px 20px 0; -} - -.library-main { - display: flex; - flex-direction: column; - gap: 14px; - height: 100%; - min-height: 0; -} - -.library-category-tabs, -.library-genre-tabs { - display: flex; - gap: 12px; - flex-shrink: 0; -} - -.library-category-tab { - min-width: 210px; - padding: 18px 24px; - border-radius: var(--nebula-radius-lg); - font-size: clamp(20px, 2vw, 28px); - font-weight: 900; - text-align: left; -} - -.library-category-tab.is-active, -.library-genre-tab.is-active, -.library-filter-chip.is-active, -.library-sort-option.is-active { - color: #04101d; - background: linear-gradient(135deg, var(--nebula-color-accent), #78f0ff); - border-color: rgba(255, 255, 255, 0.7); -} - -.library-genre-tab { - padding: 12px 18px; - border-radius: var(--nebula-radius-pill); - font-size: 15px; - font-weight: 800; -} - -.library-content-row { - flex: 1; - min-height: 0; - display: grid; - grid-template-columns: minmax(250px, 300px) 1fr; - gap: 16px; -} - -.library-sidebar { - display: flex; - flex-direction: column; - gap: 12px; - min-height: 0; -} - -.library-summary-card, -.library-filter-card, -.library-grid-region, -.library-details-card, -.library-sort-card, -.library-empty-card { - border: 1px solid rgba(79, 216, 255, 0.13); - background: - linear-gradient(180deg, rgba(17, 24, 48, 0.88), rgba(8, 12, 27, 0.92)), - rgba(10, 16, 34, 0.86); - border-radius: var(--nebula-radius-lg); - box-shadow: 0 18px 50px rgba(0, 0, 0, 0.36); -} - -.library-summary-card, -.library-filter-card { - padding: 16px; -} - -.library-summary-total { - display: flex; - align-items: end; - gap: 10px; - margin: 12px 0; -} - -.library-summary-total strong { - font-size: 48px; - line-height: 0.9; -} - -.library-summary-total span, -.library-sync-status p, -.library-status-copy { - margin: 0; - color: var(--nebula-color-muted); -} - -.library-stat-grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 8px; -} - -.library-stat-grid span, -.library-sync-status { - padding: 10px; - border-radius: var(--nebula-radius-md); - background: rgba(255, 255, 255, 0.045); -} - -.library-stat-grid strong { - color: var(--nebula-color-text); -} - -.library-sync-status { - display: grid; - grid-template-columns: auto 1fr; - gap: 10px; - margin-top: 12px; -} - -.library-filter-group { - display: flex; - flex-direction: column; - gap: 8px; - margin-top: 12px; -} - -.library-filter-label { - color: var(--nebula-color-muted); - font-size: 12px; - font-weight: 800; - text-transform: uppercase; - letter-spacing: 0.12em; -} - -.library-filter-chip { - width: 100%; - padding: 11px 12px; - border-radius: var(--nebula-radius-md); - text-align: left; - font-weight: 800; -} - -.library-grid-region { - min-width: 0; - min-height: 0; - padding: 16px; - display: flex; - flex-direction: column; -} - -.library-grid-header { - display: flex; - align-items: end; - justify-content: space-between; - gap: 16px; - flex-shrink: 0; - margin-bottom: 14px; -} - -.library-grid-header h2 { - margin: 4px 0 0; - font-size: 28px; -} - -.library-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); - gap: 16px; - overflow: auto; - padding: 4px 8px 24px; - scrollbar-width: thin; -} - -.library-card { - min-height: 330px; - padding: 0; - display: flex; - flex-direction: column; - border-radius: 22px; - border: 2px solid rgba(255, 255, 255, 0.08); - background: rgba(8, 12, 27, 0.94); - color: var(--nebula-color-text); - text-align: left; - box-shadow: 0 16px 34px rgba(0, 0, 0, 0.34); - transform-origin: center; -} - -.library-card.is-focused { - border-color: var(--nebula-color-accent); - box-shadow: - 0 0 0 2px rgba(79, 216, 255, 0.16), - 0 0 34px var(--library-glow), - 0 22px 50px rgba(0, 0, 0, 0.54); - transform: scale(1.045) translateZ(0); - z-index: 3; -} - -.library-card-art { - position: relative; - min-height: 178px; - display: grid; - place-items: center; - overflow: hidden; - border-radius: 20px 20px 0 0; - background: - radial-gradient(circle at 18% 16%, color-mix(in srgb, var(--library-accent, #4fd8ff), transparent 24%), transparent 42%), - linear-gradient(135deg, rgba(79, 216, 255, 0.16), rgba(157, 79, 224, 0.18)), - #10182f; -} - -.library-card-art.has-image { - background: - linear-gradient(180deg, rgba(7, 10, 20, 0), rgba(7, 10, 20, 0.58)), - var(--library-art) center / cover no-repeat; -} - -.library-card-art span, -.library-details-art span { - display: grid; - place-items: center; - width: 78px; - height: 78px; - border-radius: 24px; - background: rgba(0, 0, 0, 0.24); - border: 1px solid rgba(255, 255, 255, 0.16); - font-size: 26px; - font-weight: 950; - letter-spacing: 0.08em; -} - -.library-card-art.has-image span, -.library-details-art.has-image span { - display: none; -} - -.library-verified-badge { - position: absolute; - right: 10px; - top: 10px; - padding: 5px 8px; - border-radius: var(--nebula-radius-pill); - background: rgba(79, 255, 136, 0.16); - border: 1px solid rgba(79, 255, 136, 0.42); - color: #9effbc; - font-size: 11px; - font-style: normal; - font-weight: 900; - text-transform: uppercase; -} - -.library-card-body { - display: flex; - flex-direction: column; - gap: 8px; - padding: 14px; -} - -.library-card-title-row { - display: flex; - align-items: start; - justify-content: space-between; - gap: 10px; -} - -.library-card-title-row h3 { - margin: 0; - font-size: 20px; - line-height: 1.1; -} - -.library-card-title-row span { - flex-shrink: 0; - color: var(--nebula-color-accent); - font-size: 11px; - font-weight: 900; - text-transform: uppercase; -} - -.library-card-body p, -.library-card-meta-row, -.library-card-achievements { - margin: 0; - color: var(--nebula-color-muted); - font-size: 13px; -} - -.library-card-meta-row { - display: flex; - justify-content: space-between; - gap: 10px; -} - -.library-card-focus-copy, -.library-card-achievements { - display: none; -} - -.library-card.is-focused .library-card-focus-copy, -.library-card.is-focused .library-card-achievements { - display: block; -} - -.library-empty-card { - grid-column: 1 / -1; - padding: 28px; -} - -.library-empty-card h3 { - margin: 8px 0; - font-size: 28px; -} - -.library-details-panel, -.library-sort-panel { - display: none; -} - -.library-details-panel.is-open, -.library-sort-panel.is-open { - position: fixed; - inset: 0; - z-index: 30; - display: block; -} - -.library-details-backdrop, -.library-sort-backdrop { - position: absolute; - inset: 0; - background: rgba(3, 6, 14, 0.72); -} - -.library-details-card { - position: absolute; - top: 30px; - right: 30px; - bottom: 30px; - width: min(520px, calc(100vw - 140px)); - overflow: hidden; - display: flex; - flex-direction: column; -} - -.library-details-art { - min-height: 210px; - display: grid; - place-items: center; - background: - radial-gradient(circle at 20% 20%, color-mix(in srgb, var(--library-accent, #4fd8ff), transparent 24%), transparent 44%), - linear-gradient(135deg, rgba(79, 216, 255, 0.18), rgba(157, 79, 224, 0.2)); -} - -.library-details-art.has-image { - background: - linear-gradient(180deg, rgba(7, 10, 20, 0), rgba(7, 10, 20, 0.78)), - var(--library-detail-art) center / cover no-repeat; -} - -.library-details-body { - padding: 22px; - overflow: auto; -} - -.library-details-body h2 { - margin: 8px 0; - font-size: 34px; - line-height: 1; -} - -.library-details-body p { - color: var(--nebula-color-muted); - line-height: 1.5; -} - -.library-details-list { - display: grid; - gap: 10px; - margin: 16px 0; -} - -.library-details-list div { - padding: 10px; - border-radius: var(--nebula-radius-md); - background: rgba(255, 255, 255, 0.05); -} - -.library-details-list dt { - color: var(--nebula-color-muted); - font-size: 11px; - font-weight: 900; - text-transform: uppercase; -} - -.library-details-list dd { - margin: 3px 0 0; -} - -.library-details-actions, -.library-sort-options { - display: grid; - gap: 10px; -} - -.library-detail-button, -.library-sort-option { - padding: 14px 16px; - border-radius: var(--nebula-radius-md); - text-align: left; - font-weight: 900; -} - -.library-sort-card { - position: absolute; - right: 34px; - bottom: 58px; - width: min(360px, calc(100vw - 130px)); - padding: 20px; -} - -.library-sort-card h2 { - margin: 8px 0; - font-size: 30px; -} - -.library-sort-card p { - color: var(--nebula-color-muted); -} - -.library-action.is-focused, -.library-category-tab.is-focused, -.library-genre-tab.is-focused, -.library-filter-chip.is-focused, -.library-sort-option.is-focused, -.library-detail-button.is-focused { - border-color: var(--nebula-color-accent); - box-shadow: - 0 0 0 2px rgba(79, 216, 255, 0.14), - 0 0 26px rgba(79, 216, 255, 0.34); - transform: scale(1.035) translateZ(0); -} - -@media (max-width: 1180px) { - .library-content-row { - grid-template-columns: 240px 1fr; - } - - .library-category-tab { - min-width: 170px; - } - - .library-grid { - grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); - } -} - -/* Game Detail View Styles */ -.game-detail-view { - position: absolute; - inset: 0; - display: flex; - flex-direction: column; - overflow: hidden; - background: linear-gradient(180deg, rgba(17, 24, 48, 0.95), rgba(8, 12, 27, 0.98)); -} - -.game-detail-header-image { - position: absolute; - top: 0; - left: 0; - right: 0; - height: 40%; - background-size: cover; - background-position: center; - background-color: rgba(79, 216, 255, 0.08); - z-index: 1; -} - -.game-detail-header-overlay { - position: absolute; - inset: 0; - background: linear-gradient(180deg, rgba(7, 10, 20, 0.3), rgba(7, 10, 20, 0.95)); - z-index: 1; -} - -.game-detail-content { - position: relative; - z-index: 2; - flex: 1; - overflow-y: auto; - display: flex; - flex-direction: column; -} - -.game-detail-header { - padding: 28px 40px 20px; - display: grid; - grid-template-columns: 1fr auto; - gap: 40px; - align-items: start; -} - -.game-detail-title-section h1 { - margin: 0; - font-size: 48px; - line-height: 1.1; - letter-spacing: -0.02em; - max-width: 600px; -} - -.game-detail-title-section .library-section-kicker { - display: block; - margin-bottom: 12px; -} - -.game-detail-meta { - display: flex; - gap: 24px; - margin-top: 12px; - font-size: 14px; - color: var(--nebula-color-muted); - flex-wrap: wrap; -} - -.game-detail-play-button-container { - display: flex; - gap: 12px; -} - -.game-detail-play-button { - min-width: 160px; - padding: 16px 32px; - font-size: 18px; - font-weight: 900; - text-transform: uppercase; - letter-spacing: 0.08em; - border: none; - border-radius: 12px; - background: linear-gradient(135deg, var(--nebula-color-accent), #78f0ff); - color: #04101d; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - box-shadow: 0 12px 32px rgba(79, 216, 255, 0.34); - transition: all 0.2s ease; - white-space: nowrap; -} - -.game-detail-play-button .play-icon { - font-size: 20px; -} - -.game-detail-play-button.is-focused { - transform: scale(1.08); - box-shadow: - 0 0 0 2px rgba(79, 216, 255, 0.3), - 0 16px 40px rgba(79, 216, 255, 0.42); -} - -.game-detail-main { - display: grid; - grid-template-columns: 1fr 320px; - gap: 40px; - padding: 0 40px 40px; -} - -.game-detail-left-column { - display: flex; - flex-direction: column; - gap: 32px; - max-width: 700px; -} - -.game-detail-right-column { - display: flex; - flex-direction: column; - gap: 24px; -} - -.game-detail-section-title { - margin: 0 0 16px 0; - font-size: 18px; - font-weight: 900; - text-transform: uppercase; - letter-spacing: 0.08em; - color: var(--nebula-color-accent); -} - -.game-detail-description { - display: flex; - flex-direction: column; - gap: 12px; -} - -.game-detail-description p { - margin: 0; - color: var(--nebula-color-muted); - line-height: 1.6; -} - -.game-detail-description .long-description { - color: var(--nebula-color-muted); - opacity: 0.85; -} - -.game-detail-info { - display: flex; - flex-direction: column; - gap: 12px; -} - -.game-detail-info-list { - display: grid; - gap: 12px; -} - -.game-detail-info-list div { - display: grid; - grid-template-columns: 120px 1fr; - gap: 12px; - padding: 12px; - border-radius: var(--nebula-radius-md); - background: rgba(79, 216, 255, 0.05); - border: 1px solid rgba(79, 216, 255, 0.08); -} - -.game-detail-info-list dt { - font-size: 12px; - font-weight: 900; - text-transform: uppercase; - letter-spacing: 0.08em; - color: var(--nebula-color-accent); -} - -.game-detail-info-list dd { - margin: 0; - color: var(--nebula-color-muted); - word-break: break-word; -} - -/* Screenshots Section */ -.game-detail-screenshots { - display: flex; - flex-direction: column; - gap: 16px; -} - -.screenshots-carousel { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 12px; -} - -.screenshot-item { - aspect-ratio: 16 / 9; - border-radius: 12px; - overflow: hidden; - border: 1px solid rgba(79, 216, 255, 0.12); - background: rgba(10, 16, 34, 0.6); -} - -.screenshot-item img { - width: 100%; - height: 100%; - object-fit: cover; -} - -/* Achievements Section */ -.game-detail-achievements { - display: flex; - flex-direction: column; - gap: 16px; - padding: 16px; - border-radius: var(--nebula-radius-lg); - background: linear-gradient(135deg, rgba(79, 216, 255, 0.08), rgba(157, 79, 224, 0.08)); - border: 1px solid rgba(79, 216, 255, 0.12); -} - -.achievements-container { - display: flex; - flex-direction: column; - gap: 12px; -} - -.achievements-progress { - display: grid; - grid-template-columns: auto 1fr auto; - gap: 12px; - align-items: center; -} - -.achievement-stat { - display: flex; - flex-direction: column; - align-items: center; - text-align: center; -} - -.achievement-number { - font-size: 20px; - font-weight: 900; - color: var(--nebula-color-accent); -} - -.achievement-label { - font-size: 11px; - font-weight: 800; - text-transform: uppercase; - color: var(--nebula-color-muted); - margin-top: 4px; -} - -.achievement-bar { - height: 8px; - border-radius: 4px; - background: rgba(255, 255, 255, 0.08); - overflow: hidden; -} - -.achievement-bar-fill { - height: 100%; - background: linear-gradient(90deg, var(--nebula-color-accent), #78f0ff); - border-radius: 4px; -} - -/* Features and Badges */ -.game-detail-features { - display: flex; - flex-direction: column; - gap: 12px; - padding: 16px; - border-radius: var(--nebula-radius-lg); - background: rgba(255, 255, 255, 0.04); - border: 1px solid rgba(255, 255, 255, 0.07); -} - -.features-grid { - display: grid; - gap: 10px; -} - -.feature-badge { - display: inline-block; - padding: 8px 12px; - border-radius: 8px; - font-size: 12px; - font-weight: 800; - text-transform: uppercase; - letter-spacing: 0.04em; - background: rgba(79, 216, 255, 0.14); - color: var(--nebula-color-accent); - border: 1px solid rgba(79, 216, 255, 0.22); -} - -.feature-badge.verified { - background: rgba(79, 255, 136, 0.12); - color: #9effbc; - border-color: rgba(79, 255, 136, 0.24); -} - -/* Actions */ -.game-detail-actions { - display: grid; - gap: 10px; - padding: 16px; - border-radius: var(--nebula-radius-lg); - background: rgba(255, 255, 255, 0.02); - border: 1px solid rgba(255, 255, 255, 0.05); -} - -.library-detail-button { - padding: 12px 16px; - border-radius: var(--nebula-radius-md); - font-weight: 900; - font-size: 14px; - text-align: left; -} - -.library-detail-button.is-focused { - border-color: var(--nebula-color-accent); - box-shadow: - 0 0 0 2px rgba(79, 216, 255, 0.14), - 0 0 26px rgba(79, 216, 255, 0.34); - transform: scale(1.03) translateZ(0); -} - -/* Scrollbar styling for content area */ -.game-detail-content::-webkit-scrollbar { - width: 8px; -} - -.game-detail-content::-webkit-scrollbar-track { - background: transparent; -} - -.game-detail-content::-webkit-scrollbar-thumb { - background: rgba(79, 216, 255, 0.24); - border-radius: 4px; -} - -.game-detail-content::-webkit-scrollbar-thumb:hover { - background: rgba(79, 216, 255, 0.36); -} - -/* Update the library-details-panel for full-screen mode */ -.library-details-panel.is-open { - display: flex; -} - -.game-detail-view { - z-index: 31; -} diff --git a/Bigscreen/src/views/library/library.html b/Bigscreen/src/views/library/library.html deleted file mode 100644 index 53d1747..0000000 --- a/Bigscreen/src/views/library/library.html +++ /dev/null @@ -1,22 +0,0 @@ -
    -
    -
    -

    Nebula OS

    -
    - -

    --:--

    -
    -
    -
    -
    -
    -
    -

    Nebula App

    -

    Library

    -
    -
    -
    - -

    Library integration stub for v0 shell navigation.

    -
    -
    diff --git a/Bigscreen/src/views/library/library.js b/Bigscreen/src/views/library/library.js deleted file mode 100644 index 5871f75..0000000 --- a/Bigscreen/src/views/library/library.js +++ /dev/null @@ -1,292 +0,0 @@ -import { filterLibraryItems } from "./libraryFilters.js"; -import { - GENRE_FILTERS, - LIBRARY_CATEGORIES, - createDefaultLibraryQuery, - createMockLibraryItems, -} from "./libraryModel.js"; -import { hideLibraryItem, launchLibraryItem, loadLibraryItems, scanLibraryItems } from "./libraryBridge.js"; -import { - renderCategoryTabs, - renderDetailsPanel, - renderFilters, - renderGenreTabs, - renderGrid, - renderLibraryShell, - renderSortPanel, - renderSummary, -} from "./libraryComponents.js"; -import { cycleOption, requestNavigationRefresh } from "./libraryController.js"; - -const PLAY_STATES = ["all", "played", "unplayed"]; - -const findItem = (runtime, id = runtime.focusedId) => runtime.items.find((item) => item.id === id) ?? null; - -const focusKeyForRuntime = (runtime) => { - if (runtime.detailsOpen) return "library-detail-0"; - if (runtime.sortOpen) return `library-sort-${runtime.query.sortBy}`; - return runtime.focusedId ? `library-card-${runtime.focusedId}` : "library-category-games"; -}; - -export const createLibraryView = ({ state, renderView }) => { - const runtime = { - items: createMockLibraryItems(), - visibleItems: [], - query: createDefaultLibraryQuery(), - status: "Mock data ready", - message: "Mock apps are shown until Start Scan imports real apps into library.db.", - providers: [], - focusedId: null, - detailsOpen: false, - sortOpen: false, - mounted: false, - focusListenerAttached: false, - }; - - const renderLibrary = (focusKey = null) => { - const root = document.querySelector("[data-view='library']"); - if (!root) return; - - runtime.visibleItems = filterLibraryItems(runtime.items, runtime.query); - if (!runtime.visibleItems.some((item) => item.id === runtime.focusedId)) { - runtime.focusedId = runtime.visibleItems[0]?.id ?? null; - } - - root.querySelector("[data-library-categories]").innerHTML = renderCategoryTabs(runtime.query); - root.querySelector("[data-library-genres]").innerHTML = renderGenreTabs(runtime.query); - root.querySelector("[data-library-summary]").innerHTML = renderSummary( - runtime.items, - runtime.status, - runtime.message, - ); - root.querySelector("[data-library-filters]").innerHTML = renderFilters(runtime.query); - root.querySelector("[data-library-grid]").innerHTML = renderGrid(runtime.visibleItems, runtime.focusedId); - - const resultTitle = root.querySelector("[data-library-result-title]"); - const resultKicker = root.querySelector("[data-library-result-kicker]"); - const status = root.querySelector("[data-library-status]"); - if (resultTitle) resultTitle.textContent = `${runtime.visibleItems.length} Visible`; - if (resultKicker) resultKicker.textContent = `${runtime.query.genre} · ${runtime.query.sortBy}`; - if (status) status.textContent = runtime.message; - - const details = root.querySelector("[data-library-details]"); - details.innerHTML = runtime.detailsOpen ? renderDetailsPanel(findItem(runtime)) : ""; - details.setAttribute("aria-hidden", runtime.detailsOpen ? "false" : "true"); - details.classList.toggle("is-open", runtime.detailsOpen); - - const sortPanel = root.querySelector("[data-library-sort-panel]"); - sortPanel.innerHTML = runtime.sortOpen ? renderSortPanel(runtime.query) : ""; - sortPanel.setAttribute("aria-hidden", runtime.sortOpen ? "false" : "true"); - sortPanel.classList.toggle("is-open", runtime.sortOpen); - - requestNavigationRefresh(focusKey ?? focusKeyForRuntime(runtime)); - }; - - const setLoadedState = (result) => { - runtime.items = result.items; - runtime.status = result.status; - runtime.message = result.message; - runtime.providers = result.providers; - renderLibrary(); - }; - - const refreshLibrary = async () => { - runtime.status = "Refreshing"; - runtime.message = "Reloading library cards from library.db or mock fallback data."; - renderLibrary("library-refresh"); - setLoadedState(await loadLibraryItems()); - }; - - const scanLibrary = async () => { - runtime.status = "Scanning device"; - runtime.message = "Checking Steam, GOG, Epic, emulator folders, and local apps for future integrations."; - renderLibrary("library-scan"); - try { - setLoadedState(await scanLibraryItems()); - } catch (error) { - runtime.status = "Scan failed"; - runtime.message = String(error); - renderLibrary("library-scan"); - } - }; - - const launchFocusedItem = async () => { - const item = findItem(runtime); - if (!item) return; - const result = await launchLibraryItem(item); - runtime.status = result?.launched ? "Launch requested" : "Details ready"; - runtime.message = result?.message ?? `${item.title} is ready.`; - if (!item.installed) { - runtime.detailsOpen = true; - } - renderLibrary(); - }; - - const openDetails = () => { - if (!runtime.focusedId) return; - runtime.detailsOpen = true; - runtime.sortOpen = false; - renderLibrary("library-detail-0"); - }; - - const closePanels = () => { - runtime.detailsOpen = false; - runtime.sortOpen = false; - renderLibrary(); - }; - - const toggleSortPanel = () => { - runtime.sortOpen = !runtime.sortOpen; - runtime.detailsOpen = false; - renderLibrary(runtime.sortOpen ? `library-sort-${runtime.query.sortBy}` : focusKeyForRuntime(runtime)); - }; - - const applyElementAction = async (element) => { - if (!element) return; - const { action } = element.dataset; - - if (action === "scan") return scanLibrary(); - if (action === "refresh") return refreshLibrary(); - if (action === "category") runtime.query.category = element.dataset.category; - if (action === "genre") runtime.query.genre = element.dataset.genre; - if (action === "platform") runtime.query.platform = element.dataset.platform; - if (action === "toggle-installed") runtime.query.installedOnly = !runtime.query.installedOnly; - if (action === "cycle-play-state") runtime.query.playState = cycleOption(PLAY_STATES, runtime.query.playState, 1); - if (action === "toggle-coop") runtime.query.coOpOnly = !runtime.query.coOpOnly; - if (action === "toggle-achievements") runtime.query.achievementsOnly = !runtime.query.achievementsOnly; - if (action === "sort") runtime.query.sortBy = element.dataset.sort; - - if (action === "card") { - runtime.focusedId = element.dataset.itemId; - return openDetails(); - } - - if (action === "launch") return launchFocusedItem(); - - if (action === "install") { - runtime.status = "Install queued"; - runtime.message = "Store/provider install flows will connect here when integrations are added."; - } - - if (action === "uninstall") { - runtime.status = "Uninstall placeholder"; - runtime.message = "Uninstall will be routed through source-specific providers later."; - } - - if (action === "hide") { - const item = findItem(runtime); - if (item) { - item.hidden = true; - await hideLibraryItem(item); - runtime.detailsOpen = false; - runtime.status = "Hidden"; - runtime.message = `${item.title} is hidden from the visible library.`; - } - } - - if (action === "open-folder") { - runtime.status = "Open folder placeholder"; - runtime.message = "Folder opening will use the desktop bridge for installed apps."; - } - - if (action === "close-details" || action === "close-sort") { - return closePanels(); - } - - renderLibrary(element.dataset.focusKey); - }; - - const cycleCategory = (direction) => { - runtime.query.category = cycleOption( - LIBRARY_CATEGORIES.map((category) => category.id), - runtime.query.category, - direction, - ); - renderLibrary(`library-category-${runtime.query.category}`); - }; - - const cycleGenre = (direction) => { - runtime.query.genre = cycleOption(GENRE_FILTERS, runtime.query.genre, direction); - const genreIndex = GENRE_FILTERS.findIndex((genre) => genre === runtime.query.genre); - renderLibrary(`library-genre-${genreIndex}`); - }; - - return { - id: "library", - render: () => renderLibraryShell(), - mount: async () => { - const root = document.querySelector("[data-view='library']"); - if (!root) return; - runtime.mounted = true; - root.addEventListener("click", (event) => { - const actionElement = event.target.closest("[data-action]"); - if (actionElement) { - applyElementAction(actionElement); - } - }); - if (!runtime.focusListenerAttached) { - window.addEventListener("nebula-focus-change", (event) => { - const key = event.detail?.key ?? ""; - if (key.startsWith("library-card-")) { - runtime.focusedId = key.replace("library-card-", ""); - } - }); - runtime.focusListenerAttached = true; - } - renderLibrary("library-category-games"); - setLoadedState(await loadLibraryItems()); - }, - getNavigationContract: () => { - const root = document.querySelector("[data-view='library']"); - return { - focusRoot: root, - defaultFocus: root?.querySelector("[data-focus-key='library-category-games']") ?? null, - layout: { type: "grid", cols: 9, rows: 24 }, - hintsTemplate: "#library-hints-template", - nebulaNavigation: state.nebula.navigation, - useNebulaNavigation: false, - onAccept: applyElementAction, - onBack: () => { - if (runtime.detailsOpen || runtime.sortOpen) { - closePanels(); - return; - } - state.activeView = "home"; - renderView("home"); - }, - onMenu: () => {}, - onAction: (action, element) => { - if (action === "y") { - toggleSortPanel(); - return true; - } - if (action === "clear") { - openDetails(); - return true; - } - if (action === "l1") { - cycleCategory(-1); - return true; - } - if (action === "r1") { - cycleCategory(1); - return true; - } - if (action === "l2") { - cycleGenre(-1); - return true; - } - if (action === "r2") { - cycleGenre(1); - return true; - } - if (element?.dataset.itemId) { - runtime.focusedId = element.dataset.itemId; - renderLibrary(element.dataset.focusKey); - } - return false; - }, - }; - }, - }; -}; diff --git a/Bigscreen/src/views/library/libraryBridge.js b/Bigscreen/src/views/library/libraryBridge.js deleted file mode 100644 index fc28b64..0000000 --- a/Bigscreen/src/views/library/libraryBridge.js +++ /dev/null @@ -1,124 +0,0 @@ -import { createMockLibraryItems, normalizeLibraryItem } from "./libraryModel.js"; - -export const getTauriCore = async () => { - const globalCore = window.__TAURI__?.core; - if (typeof globalCore?.invoke === "function") { - return { - invoke: globalCore.invoke, - convertFileSrc: typeof globalCore.convertFileSrc === "function" ? globalCore.convertFileSrc : null, - }; - } - - try { - const tauriCore = await import("@tauri-apps/api/core"); - return { - invoke: typeof tauriCore.invoke === "function" ? tauriCore.invoke : null, - convertFileSrc: typeof tauriCore.convertFileSrc === "function" ? tauriCore.convertFileSrc : null, - }; - } catch (_error) { - return { invoke: null, convertFileSrc: null }; - } -}; - -const defaultLocalFolders = () => ["C:/Games", "D:/Games"]; - -export const loadLibraryItems = async () => { - const { invoke, convertFileSrc } = await getTauriCore(); - if (!invoke) { - return { - items: createMockLibraryItems(), - status: "Mock data", - message: "Run inside Tauri to scan real installed apps into library.db.", - providers: [], - }; - } - - try { - const games = await invoke("list_library_games"); - const scannedItems = Array.isArray(games) - ? games.map((game, index) => normalizeLibraryItem(game, index, convertFileSrc)) - : []; - - return { - items: scannedItems.length ? scannedItems : createMockLibraryItems(), - status: scannedItems.length ? "Synced with library.db" : "Mock data", - message: scannedItems.length - ? "Showing installed apps scanned into the separate NebulaOS library database." - : "No scanned apps yet. Mock cards are shown until Start Scan finds installed apps.", - providers: [], - }; - } catch (error) { - return { - items: createMockLibraryItems(), - status: "Library bridge fallback", - message: String(error), - providers: [], - }; - } -}; - -export const scanLibraryItems = async () => { - const { invoke, convertFileSrc } = await getTauriCore(); - if (!invoke) { - return { - items: createMockLibraryItems(), - status: "Desktop bridge unavailable", - message: "Start Scan will call Tauri when NebulaOS is running as a desktop app.", - providers: [], - }; - } - - const summary = await invoke("scan_library_command", { - request: { localFolders: defaultLocalFolders() }, - }); - - const items = Array.isArray(summary?.games) - ? summary.games.map((game, index) => normalizeLibraryItem(game, index, convertFileSrc)) - : []; - - return { - items: items.length ? items : createMockLibraryItems(), - status: `${summary?.discovered ?? 0} discovered`, - message: `${summary?.insertedOrUpdated ?? 0} saved · ${summary?.metadataMatched ?? 0} metadata matches · ${ - summary?.unmatched ?? 0 - } need review`, - providers: summary?.providers ?? [], - }; -}; - -export const launchLibraryItem = async (item) => { - if (!item?.installed) { - console.info("[NebulaOS] Install/details state requested", item); - return { launched: false, action: "details", message: "This app is not installed yet." }; - } - - const { invoke } = await getTauriCore(); - const numericId = Number(item.backendId ?? item.id); - if (invoke && Number.isFinite(numericId)) { - return invoke("launch_library_game", { gameId: numericId }); - } - - console.info("[NebulaOS] Launch placeholder", { - id: item.id, - title: item.title, - executablePath: item.executablePath, - installPath: item.installPath, - }); - return { launched: true, action: "mock", message: `Launch queued for ${item.title}.` }; -}; - -export const hideLibraryItem = async (item) => { - const { invoke } = await getTauriCore(); - const numericId = Number(item?.backendId ?? item?.id); - if (invoke && Number.isFinite(numericId)) { - await invoke("update_library_game", { - request: { - gameId: numericId, - title: null, - executablePath: null, - hidden: true, - favourite: null, - }, - }); - } -}; diff --git a/Bigscreen/src/views/library/libraryComponents.js b/Bigscreen/src/views/library/libraryComponents.js deleted file mode 100644 index 3f7ccf1..0000000 --- a/Bigscreen/src/views/library/libraryComponents.js +++ /dev/null @@ -1,440 +0,0 @@ -import { - GENRE_FILTERS, - LIBRARY_CATEGORIES, - PLATFORM_FILTERS, - PLAY_STATE_LABELS, - SORT_OPTIONS, - SOURCE_LABELS, - TYPE_LABELS, - formatPlaytime, - formatShortDate, - initialsForTitle, - summarizeLibrary, -} from "./libraryModel.js"; - -const escapeHtml = (value) => - String(value ?? "") - .replaceAll("&", "&") - .replaceAll("<", "<") - .replaceAll(">", ">") - .replaceAll('"', """) - .replaceAll("'", "'"); - -const providerLabel = (source) => SOURCE_LABELS[source] ?? source; -const typeLabel = (type) => TYPE_LABELS[type] ?? type; - -const navButton = ({ className, row, col, key, action, label, active = false, extra = "" }) => ` - -`; - -export const renderLibraryShell = () => ` -
    -
    -
    -

    Unified Library

    -

    Library

    -
    -
    -
    - - --- - --:-- -
    -
    - ${navButton({ className: "library-action", row: 0, col: 7, key: "library-scan", action: "scan", label: "Start Scan" })} - ${navButton({ className: "library-action", row: 0, col: 8, key: "library-refresh", action: "refresh", label: "Refresh" })} -
    -
    -
    - -
    -
    - - -
    - -
    -
    -
    -

    Ready

    -

    Controller Library

    -
    -

    Loading library...

    -
    -
    -
    -
    -
    -
    - - - -
    -`; - -export const renderCategoryTabs = (query) => - LIBRARY_CATEGORIES.map((category, index) => - navButton({ - className: "library-category-tab", - row: 1, - col: index, - key: `library-category-${category.id}`, - action: "category", - label: escapeHtml(category.label), - active: query.category === category.id, - extra: `data-category="${category.id}"`, - }), - ).join(""); - -export const renderGenreTabs = (query) => - GENRE_FILTERS.map((genre, index) => - navButton({ - className: "library-genre-tab", - row: 2, - col: index, - key: `library-genre-${index}`, - action: "genre", - label: escapeHtml(genre), - active: query.genre === genre, - extra: `data-genre="${escapeHtml(genre)}"`, - }), - ).join(""); - -export const renderSummary = (items, status, message) => { - const summary = summarizeLibrary(items); - return ` -

    Library Summary

    -
    - ${summary.total} - Total apps -
    -
    - ${summary.games} Games - ${summary.verified} Verified - ${summary.hidden} Hidden - ${summary.installed} Installed -
    -
    - -
    - ${escapeHtml(status)} -

    ${escapeHtml(message)}

    -
    -
    - `; -}; - -export const renderFilters = (query) => { - const platformButtons = PLATFORM_FILTERS.map((platform, index) => - navButton({ - className: "library-filter-chip", - row: 4 + index, - col: 0, - key: `library-platform-${platform}`, - action: "platform", - label: platform === "all" ? "All Platforms" : providerLabel(platform), - active: query.platform === platform, - extra: `data-platform="${platform}"`, - }), - ).join(""); - - return ` -

    Filter Toggles

    -
    - By Platform - ${platformButtons} -
    -
    - ${navButton({ - className: "library-filter-chip", - row: 11, - col: 0, - key: "library-installed-only", - action: "toggle-installed", - label: "Installed Only", - active: query.installedOnly, - })} - ${navButton({ - className: "library-filter-chip", - row: 12, - col: 0, - key: "library-play-state", - action: "cycle-play-state", - label: `Play State: ${PLAY_STATE_LABELS[query.playState]}`, - active: query.playState !== "all", - })} - ${navButton({ - className: "library-filter-chip", - row: 13, - col: 0, - key: "library-coop", - action: "toggle-coop", - label: "Co-op", - active: query.coOpOnly, - })} - ${navButton({ - className: "library-filter-chip", - row: 14, - col: 0, - key: "library-achievements", - action: "toggle-achievements", - label: "Achievements", - active: query.achievementsOnly, - })} -
    - `; -}; - -export const renderGrid = (items, focusedId) => { - if (!items.length) { - return ` -
    -

    No Matches

    -

    No apps match these filters.

    -

    Press Y to open sorting and filters, or clear toggles from the left panel.

    -
    - `; - } - - return items - .map((item, index) => { - const row = 4 + Math.floor(index / 4); - const col = 2 + (index % 4); - const artStyle = item.coverImage - ? `style="--library-art: url('${escapeHtml(item.coverImage)}'); --library-accent: ${escapeHtml(item.accent)};"` - : `style="--library-accent: ${escapeHtml(item.accent)};"`; - const achievementCopy = item.achievementsSupported - ? `${item.achievementsUnlocked ?? 0}/${item.achievementsTotal ?? "?"} achievements` - : "No achievements"; - - return ` - - `; - }) - .join(""); -}; - -export const renderDetailsPanel = (item) => { - if (!item) return ""; - - const achievements = item.achievementsSupported - ? `${item.achievementsUnlocked ?? 0} / ${item.achievementsTotal ?? "?"}` - : "Not supported"; - - const screenshotsHtml = item.screenshots && item.screenshots.length > 0 - ? ` -
    -

    Screenshots

    - -
    - ` - : ''; - - const achievementsHtml = item.achievementsSupported - ? ` -
    -

    Achievements

    -
    -
    -
    - ${item.achievementsUnlocked ?? 0} - Unlocked -
    -
    -
    -
    -
    - ${item.achievementsTotal ?? "?"} - Total -
    -
    -
    -
    - ` - : ''; - - const featuresHtml = ` -
    -

    Features

    -
    - ${item.supportsController ? 'Controller Support' : ''} - ${item.steamDeckVerified ? 'Steam Deck Verified' : ''} - ${item.multiplayer ? 'Multiplayer' : ''} - ${item.coOp ? 'Co-op' : ''} -
    -
    - `; - - return ` -
    -
    -
    -
    -
    - -
    -
    -
    -

    ${escapeHtml(providerLabel(item.source))} · ${escapeHtml(typeLabel(item.type))}

    -

    ${escapeHtml(item.title)}

    -
    - ${item.installed ? '✓ Installed' : 'Not installed'} - ${item.lastPlayed ? `Last played: ${escapeHtml(formatShortDate(item.lastPlayed))}` : ''} - ${item.playtimeMinutes > 0 ? `${escapeHtml(formatPlaytime(item.playtimeMinutes))}` : ''} -
    -
    - -
    - -
    -
    - -
    -
    -
    -

    About

    -

    ${escapeHtml(item.description)}

    - ${item.longDescription ? `

    ${escapeHtml(item.longDescription)}

    ` : ''} -
    - -
    -

    Details

    -
    -
    -
    Genres
    -
    ${escapeHtml(item.genre.join(', '))}
    -
    -
    -
    Source
    -
    ${escapeHtml(providerLabel(item.source))}
    -
    - ${item.installed ? `
    -
    Install Location
    -
    ${escapeHtml(item.installPath)}
    -
    ` : ''} - ${item.installedAt ? `
    -
    Installed
    -
    ${escapeHtml(formatShortDate(item.installedAt))}
    -
    ` : ''} - ${item.playtimeMinutes > 0 ? `
    -
    Playtime
    -
    ${escapeHtml(formatPlaytime(item.playtimeMinutes))}
    -
    ` : ''} -
    -
    - - ${screenshotsHtml} -
    - -
    - ${featuresHtml} - ${achievementsHtml} - -
    - ${["Hide", "Open Folder"] - .filter(label => { - if (label === "Open Folder" && !item.installed) return false; - return true; - }) - .map( - (label, index) => ` - - `, - ) - .join("")} -
    -
    -
    -
    -
    - `; -}; - -export const renderSortPanel = (query) => ` -
    -
    -

    Sort Library

    -

    Filter / Sort

    -

    Use Y to close. These options immediately reorder the visible grid.

    -
    - ${SORT_OPTIONS.map((option, index) => - navButton({ - className: "library-sort-option", - row: 18 + index, - col: 6, - key: `library-sort-${option.id}`, - action: "sort", - label: escapeHtml(option.label), - active: query.sortBy === option.id, - extra: `data-sort="${option.id}"`, - }), - ).join("")} -
    -
    -`; diff --git a/Bigscreen/src/views/library/libraryController.js b/Bigscreen/src/views/library/libraryController.js deleted file mode 100644 index e624c1a..0000000 --- a/Bigscreen/src/views/library/libraryController.js +++ /dev/null @@ -1,14 +0,0 @@ -export const requestNavigationRefresh = (focusKey = null) => { - window.dispatchEvent( - new CustomEvent("nebula-navigation-refresh", { - detail: { focusKey }, - }), - ); -}; - -export const cycleOption = (options, current, direction) => { - const index = options.findIndex((option) => option === current); - const safeIndex = index >= 0 ? index : 0; - const nextIndex = (safeIndex + direction + options.length) % options.length; - return options[nextIndex]; -}; diff --git a/Bigscreen/src/views/library/libraryFilters.js b/Bigscreen/src/views/library/libraryFilters.js deleted file mode 100644 index 80614da..0000000 --- a/Bigscreen/src/views/library/libraryFilters.js +++ /dev/null @@ -1,83 +0,0 @@ -const lower = (value) => String(value ?? "").toLowerCase(); - -const byDateDesc = (field) => (left, right) => { - const leftTime = left[field] ? new Date(left[field]).getTime() : 0; - const rightTime = right[field] ? new Date(right[field]).getTime() : 0; - return rightTime - leftTime; -}; - -export const filterLibraryItems = (items, query) => { - let next = [...items]; - - if (!query.includeHidden) { - next = next.filter((item) => !item.hidden); - } - - if (query.category === "games") { - next = next.filter((item) => item.type === "game"); - } - - if (query.category === "software") { - next = next.filter((item) => item.type !== "game"); - } - - if (query.genre === "Latest Installed") { - next = next.filter((item) => item.installedAt); - } else if (query.genre === "Recently Played") { - next = next.filter((item) => item.lastPlayed); - } else if (query.genre !== "All Genres") { - next = next.filter((item) => item.genre.some((genre) => lower(genre) === lower(query.genre))); - } - - if (query.platform !== "all") { - next = next.filter((item) => item.source === query.platform); - } - - if (query.installedOnly) { - next = next.filter((item) => item.installed); - } - - if (query.playState === "played") { - next = next.filter((item) => item.playtimeMinutes > 0 || item.lastPlayed); - } - - if (query.playState === "unplayed") { - next = next.filter((item) => item.playtimeMinutes === 0 && !item.lastPlayed); - } - - if (query.coOpOnly) { - next = next.filter((item) => item.coOp); - } - - if (query.achievementsOnly) { - next = next.filter((item) => item.achievementsSupported); - } - - return sortLibraryItems(next, query.sortBy, query.genre); -}; - -export const sortLibraryItems = (items, sortBy, genre) => { - const sorted = [...items]; - const resolvedSort = - genre === "Latest Installed" ? "recentlyInstalled" : genre === "Recently Played" ? "recentlyPlayed" : sortBy; - - if (resolvedSort === "recentlyPlayed") { - return sorted.sort(byDateDesc("lastPlayed")); - } - - if (resolvedSort === "recentlyInstalled") { - return sorted.sort(byDateDesc("installedAt")); - } - - if (resolvedSort === "playtime") { - return sorted.sort((left, right) => right.playtimeMinutes - left.playtimeMinutes); - } - - if (resolvedSort === "platform") { - return sorted.sort( - (left, right) => left.source.localeCompare(right.source) || left.title.localeCompare(right.title), - ); - } - - return sorted.sort((left, right) => left.title.localeCompare(right.title)); -}; diff --git a/Bigscreen/src/views/library/libraryModel.js b/Bigscreen/src/views/library/libraryModel.js deleted file mode 100644 index c0a138c..0000000 --- a/Bigscreen/src/views/library/libraryModel.js +++ /dev/null @@ -1,309 +0,0 @@ -export const LIBRARY_CATEGORIES = [ - { id: "games", label: "Games" }, - { id: "software", label: "Software" }, - { id: "unified", label: "Unified Library" }, -]; - -export const GENRE_FILTERS = [ - "All Genres", - "Action", - "RPG", - "Strategy", - "Indie", - "Latest Installed", - "Recently Played", -]; - -export const PLATFORM_FILTERS = ["all", "steam", "gog", "epic", "native", "emulated", "other"]; - -export const SORT_OPTIONS = [ - { id: "recentlyPlayed", label: "Recently played" }, - { id: "recentlyInstalled", label: "Recently installed" }, - { id: "alphabetical", label: "Alphabetical" }, - { id: "playtime", label: "Playtime" }, - { id: "platform", label: "Platform" }, -]; - -export const SOURCE_LABELS = { - steam: "Steam", - gog: "GOG", - epic: "Epic", - native: "Native", - emulated: "Emulated", - other: "Other", -}; - -export const TYPE_LABELS = { - game: "Game", - software: "Software", - tool: "Tool", - launcher: "Launcher", -}; - -export const PLAY_STATE_LABELS = { - all: "All play states", - played: "Played", - unplayed: "Unplayed", -}; - -export const createDefaultLibraryQuery = () => ({ - category: "games", - genre: "All Genres", - platform: "all", - installedOnly: false, - playState: "all", - coOpOnly: false, - achievementsOnly: false, - sortBy: "recentlyPlayed", - includeHidden: false, -}); - -const sourceFromBackend = (source) => { - const normalized = String(source ?? "").toLowerCase(); - if (normalized === "local") return "native"; - if (["steam", "gog", "epic", "native", "emulated"].includes(normalized)) return normalized; - return "other"; -}; - -const typeFromBackend = (kind) => { - const normalized = String(kind ?? "").toLowerCase(); - if (["game", "software", "tool", "launcher"].includes(normalized)) return normalized; - return "game"; -}; - -export const initialsForTitle = (title) => - String(title ?? "Nebula") - .split(/\s+/) - .filter(Boolean) - .slice(0, 2) - .map((part) => part[0]?.toUpperCase()) - .join("") || "OS"; - -export const formatPlaytime = (minutes = 0) => { - if (!minutes) return "Not played"; - if (minutes < 60) return `${minutes}m`; - const hours = Math.floor(minutes / 60); - const remainder = minutes % 60; - return remainder ? `${hours}h ${remainder}m` : `${hours}h`; -}; - -export const formatShortDate = (value) => { - if (!value) return "Never"; - const date = new Date(value); - if (Number.isNaN(date.getTime())) return "Never"; - return date.toLocaleDateString([], { month: "short", day: "numeric" }); -}; - -export const normalizeLibraryItem = (raw, index = 0, convertFileSrc = null) => { - const title = raw?.userTitle || raw?.title || "Unknown App"; - const source = sourceFromBackend(raw?.platformSource ?? raw?.source); - const type = typeFromBackend(raw?.appKind ?? raw?.type); - const convert = (path) => (path && convertFileSrc ? convertFileSrc(path) : path || null); - const convertArray = (paths) => (Array.isArray(paths) ? paths.map(convert).filter(Boolean) : []); - const installed = raw?.installed ?? Boolean(raw?.installPath || raw?.install_path); - - return { - id: String(raw?.id ?? `backend-${index}`), - backendId: raw?.id ?? null, - title, - type, - source, - genre: Array.isArray(raw?.genre) - ? raw.genre - : Array.isArray(raw?.genres) - ? raw.genres - : type === "game" - ? ["Action"] - : ["Utilities"], - installed, - installPath: raw?.installPath ?? raw?.install_path ?? null, - executablePath: raw?.executablePath ?? raw?.executable_path ?? null, - coverImage: convert(raw?.coverImage ?? raw?.cover_image), - bannerImage: convert(raw?.bannerImage ?? raw?.heroImage ?? raw?.hero_image), - iconImage: convert(raw?.iconImage ?? raw?.icon_image), - screenshots: convertArray(raw?.screenshots ?? []), - description: - raw?.description || - "Scanned from your local library. Metadata can be enriched later by Steam, GOG, Epic, emulator, and local metadata providers.", - longDescription: raw?.longDescription ?? null, - lastPlayed: raw?.lastPlayed ?? null, - installedAt: raw?.installedAt ?? raw?.createdAt ?? null, - playtimeMinutes: Number(raw?.playtimeMinutes ?? 0), - supportsController: Boolean(raw?.supportsController ?? type === "game"), - steamDeckVerified: Boolean(raw?.steamDeckVerified ?? source === "steam"), - achievementsSupported: Boolean(raw?.achievementsSupported ?? false), - achievementsUnlocked: raw?.achievementsUnlocked ?? null, - achievementsTotal: raw?.achievementsTotal ?? null, - hidden: Boolean(raw?.hidden ?? raw?.userHidden ?? false), - multiplayer: Boolean(raw?.multiplayer ?? false), - coOp: Boolean(raw?.coOp ?? false), - accent: raw?.accent ?? ["#4fd8ff", "#9d4fe0", "#1f7aff", "#39ffd2"][index % 4], - }; -}; - -export const createMockLibraryItems = () => - [ - { - id: "mock-starfall", - title: "Starfall Protocol", - type: "game", - source: "steam", - genre: ["Action", "RPG"], - installed: true, - installPath: "C:/Games/Starfall Protocol", - executablePath: "C:/Games/Starfall Protocol/starfall.exe", - description: "Pilot a relic fighter through collapsing gates in a neon campaign built for controller play.", - longDescription: "Experience a fast-paced action-RPG where you pilot experimental fighter craft through a collapsing interdimensional gateway. Built from the ground up with controller support, Starfall Protocol features real-time combat, intricate level design, and a gripping sci-fi narrative. Perfect for couch co-op sessions or solo playthroughs.", - lastPlayed: "2026-05-15T21:15:00Z", - installedAt: "2026-05-01T08:30:00Z", - playtimeMinutes: 1874, - supportsController: true, - steamDeckVerified: true, - achievementsSupported: true, - achievementsUnlocked: 28, - achievementsTotal: 54, - multiplayer: true, - coOp: true, - screenshots: [ - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%234fd8ff' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3EStarfall Protocol - Screenshot 1%3C/text%3E%3C/svg%3E", - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%239d4fe0' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3EStarfall Protocol - Screenshot 2%3C/text%3E%3C/svg%3E", - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%231f7aff' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3EStarfall Protocol - Screenshot 3%3C/text%3E%3C/svg%3E", - ], - accent: "#4fd8ff", - }, - { - id: "mock-iron-vault", - title: "Iron Vault Tactics", - type: "game", - source: "gog", - genre: ["Strategy", "Indie"], - installed: true, - installPath: "D:/Games/Iron Vault Tactics", - executablePath: "D:/Games/Iron Vault Tactics/ivt.exe", - description: "A turn-based tactics sandbox with long-form campaigns, mod support, and couch co-op skirmishes.", - longDescription: "Command your squad through dynamic turn-based tactical battles in Iron Vault Tactics. Features mod support, deep unit customization, engaging campaign narratives, and intense couch co-op multiplayer. Each decision matters in this tactical masterpiece.", - lastPlayed: "2026-05-10T11:00:00Z", - installedAt: "2026-04-18T18:00:00Z", - playtimeMinutes: 942, - supportsController: true, - achievementsSupported: false, - multiplayer: true, - coOp: true, - screenshots: [ - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%2339ffd2' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3EIron Vault Tactics - Screenshot 1%3C/text%3E%3C/svg%3E", - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%234fd8ff' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3EIron Vault Tactics - Screenshot 2%3C/text%3E%3C/svg%3E", - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%239d4fe0' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3EIron Vault Tactics - Screenshot 3%3C/text%3E%3C/svg%3E", - ], - accent: "#39ffd2", - }, - { - id: "mock-nebula-paint", - title: "Nebula Paint Studio", - type: "software", - source: "native", - genre: ["Creative", "Utilities"], - installed: true, - installPath: "C:/Program Files/Nebula Paint", - executablePath: "C:/Program Files/Nebula Paint/paint.exe", - description: "A TV-friendly concept art tool for quick capture, markup, and launcher artwork editing.", - longDescription: "Nebula Paint Studio is a professional-grade digital painting application optimized for controller input and TV display. Create stunning concept art, edit game launcher artwork, and collaborate seamlessly with frame-perfect precision.", - lastPlayed: "2026-05-11T06:20:00Z", - installedAt: "2026-03-12T09:00:00Z", - playtimeMinutes: 223, - supportsController: true, - achievementsSupported: false, - multiplayer: false, - coOp: false, - screenshots: [ - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%239d4fe0' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3ENebula Paint Studio - Screenshot 1%3C/text%3E%3C/svg%3E", - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%234fd8ff' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3ENebula Paint Studio - Screenshot 2%3C/text%3E%3C/svg%3E", - ], - accent: "#9d4fe0", - }, - { - id: "mock-ember", - title: "Emberline", - type: "game", - source: "epic", - genre: ["Action", "Indie"], - installed: false, - installPath: null, - executablePath: null, - description: "Wishlist entry from a linked store. Install support will be routed through the Epic integration later.", - longDescription: "Emberline is an indie action game with stunning visuals and engaging gameplay. Add it to your library to stay updated on new releases, sales, and updates.", - lastPlayed: null, - installedAt: null, - playtimeMinutes: 0, - supportsController: true, - steamDeckVerified: false, - achievementsSupported: true, - achievementsUnlocked: 0, - achievementsTotal: 32, - multiplayer: false, - coOp: false, - screenshots: [ - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%231f7aff' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3EEmberline - Screenshot 1%3C/text%3E%3C/svg%3E", - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%239d4fe0' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3EEmberline - Screenshot 2%3C/text%3E%3C/svg%3E", - ], - accent: "#1f7aff", - }, - { - id: "mock-retro-core", - title: "RetroCore Station", - type: "launcher", - source: "emulated", - genre: ["Launcher", "Retro"], - installed: true, - installPath: "D:/Emulation/RetroCore", - executablePath: "D:/Emulation/RetroCore/retrocore.exe", - description: "A controller-native emulator hub prepared for future ROM library scanning and save sync.", - longDescription: "RetroCore Station is your gateway to classic gaming. Play thousands of retro games with full controller support, save synchronization, and achievement tracking across multiple emulation systems.", - lastPlayed: "2026-05-14T04:10:00Z", - installedAt: "2026-05-03T15:10:00Z", - playtimeMinutes: 517, - supportsController: true, - achievementsSupported: true, - achievementsUnlocked: 88, - achievementsTotal: 210, - multiplayer: true, - coOp: true, - screenshots: [ - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%23ffb84f' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3ERetroCore Station - Screenshot 1%3C/text%3E%3C/svg%3E", - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%234fd8ff' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3ERetroCore Station - Screenshot 2%3C/text%3E%3C/svg%3E", - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%239d4fe0' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3ERetroCore Station - Screenshot 3%3C/text%3E%3C/svg%3E", - ], - accent: "#ffb84f", - }, - { - id: "mock-orbit-tools", - title: "Orbit Mod Tools", - type: "tool", - source: "other", - genre: ["Utilities", "Game Development"], - installed: false, - installPath: null, - executablePath: null, - description: "Tooling placeholder for future mod SDK detection, dependency checks, and per-game utilities.", - longDescription: "Orbit Mod Tools provides a comprehensive suite of utilities for game modding, including SDK management, dependency resolution, and per-game optimization tools. Essential for serious mod creators.", - lastPlayed: null, - installedAt: null, - playtimeMinutes: 0, - supportsController: false, - achievementsSupported: false, - multiplayer: false, - coOp: false, - screenshots: [], - accent: "#ff6b9a", - }, - ].map((item, index) => normalizeLibraryItem(item, index)); - -export const summarizeLibrary = (items) => { - const visible = items.filter((item) => !item.hidden); - return { - total: visible.length, - games: visible.filter((item) => item.type === "game").length, - verified: visible.filter((item) => item.steamDeckVerified).length, - hidden: items.filter((item) => item.hidden).length, - installed: visible.filter((item) => item.installed).length, - }; -}; diff --git a/Bigscreen/src/views/lock/lock.css b/Bigscreen/src/views/lock/lock.css deleted file mode 100644 index 23ececf..0000000 --- a/Bigscreen/src/views/lock/lock.css +++ /dev/null @@ -1,347 +0,0 @@ -.lock-view { - justify-content: center; - align-items: center; - background: radial-gradient(circle at 55% 48%, rgba(79, 216, 255, 0.1), transparent 62%); -} - -.lock-layout { - width: min(1240px, 96vw); - min-height: min(720px, 82vh); - display: grid; - grid-template-columns: minmax(320px, 1fr) minmax(360px, 520px); - gap: clamp(24px, 4vw, 58px); - background: - linear-gradient(165deg, rgba(56, 82, 128, 0.18), rgba(19, 30, 56, 0.74)), - radial-gradient(circle at 65% 35%, rgba(79, 216, 255, 0.08), transparent 52%), - rgba(13, 18, 31, 0.78); - border-radius: 20px; - border: 1px solid rgba(255, 255, 255, 0.08); - box-shadow: - 0 24px 62px rgba(0, 0, 0, 0.38), - inset 0 1px 0 rgba(255, 255, 255, 0.12); - backdrop-filter: blur(14px); - padding: clamp(28px, 4vw, 48px); -} - -.lock-view.is-success .lock-layout { - box-shadow: - 0 0 0 1px rgba(79, 216, 255, 0.3), - 0 24px 62px rgba(0, 0, 0, 0.38), - 0 0 48px rgba(79, 216, 255, 0.24); -} - -.lock-left { - display: flex; - flex-direction: column; - justify-content: center; - gap: var(--nebula-spacing-lg); -} - -.lock-user { - display: inline-flex; - align-items: center; - gap: 12px; -} - -.lock-avatar { - width: 42px; - height: 42px; - border-radius: 999px; - border: 2px solid rgba(79, 216, 255, 0.44); - background: - radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 0.85), transparent 45%), - linear-gradient(145deg, rgba(112, 189, 255, 0.66), rgba(66, 78, 142, 0.8)); -} - -.lock-username { - margin: 0; - font-weight: 640; - font-size: 18px; -} - -.lock-title-row { - display: flex; - align-items: center; - gap: 14px; - flex-wrap: wrap; -} - -.lock-title { - margin: 0; - font-size: clamp(36px, 5vw, 52px); - font-weight: 540; - letter-spacing: -0.01em; -} - -.lock-controller-badge { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 34px; - height: 34px; - padding: 0 10px; - border-radius: 999px; - font-size: 14px; - font-weight: 700; - letter-spacing: 0.02em; - border: 1px solid rgba(255, 255, 255, 0.2); - background: rgba(255, 255, 255, 0.08); - color: var(--nebula-color-text); - box-shadow: 0 0 18px rgba(79, 216, 255, 0.16); -} - -.lock-controller-badge--xbox { - border-color: rgba(107, 190, 70, 0.55); - background: rgba(107, 190, 70, 0.18); -} - -.lock-controller-badge--playstation { - border-color: rgba(70, 130, 220, 0.55); - background: rgba(70, 130, 220, 0.18); -} - -.lock-controller-badge--switch { - border-color: rgba(230, 70, 70, 0.55); - background: rgba(230, 70, 70, 0.16); -} - -.lock-glyph { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 28px; - height: 28px; - margin: 0 4px; - padding: 0 8px; - border-radius: var(--nebula-radius-sm); - font-size: 14px; - font-weight: 700; - vertical-align: middle; - background: rgba(255, 255, 255, 0.1); - border: 1px solid rgba(255, 255, 255, 0.2); - color: var(--nebula-color-text); -} - -.lock-glyph-menu { - border-color: rgba(79, 216, 255, 0.45); - box-shadow: 0 0 14px rgba(79, 216, 255, 0.22); -} - -.lock-status .lock-glyph-menu { - transform: translateY(-1px); -} - -.lock-copy { - margin: 0; - color: var(--nebula-color-muted); - font-size: 20px; - line-height: 1.45; - max-width: 540px; -} - -.lock-user-setup { - display: grid; - gap: 12px; - max-width: 420px; -} - -.lock-field { - display: grid; - gap: 6px; -} - -.lock-field span { - font-size: 13px; - color: var(--nebula-color-muted); -} - -.lock-field input { - height: 44px; - border-radius: 12px; - border: 1px solid rgba(255, 255, 255, 0.18); - background: rgba(9, 15, 30, 0.65); - color: var(--nebula-color-text); - padding: 0 12px; - font-size: 16px; -} - -.lock-field input:focus { - outline: none; - border-color: rgba(79, 216, 255, 0.72); - box-shadow: 0 0 0 2px rgba(79, 216, 255, 0.2); -} - -.lock-create-user { - height: 46px; - border: 1px solid rgba(79, 216, 255, 0.4); - border-radius: 12px; - background: linear-gradient(160deg, rgba(51, 87, 135, 0.55), rgba(18, 33, 58, 0.8)); - color: var(--nebula-color-text); - font-weight: 600; - font-size: 15px; - letter-spacing: 0.01em; -} - -.lock-create-user.is-focused { - box-shadow: - 0 0 0 2px rgba(79, 216, 255, 0.55), - 0 0 24px rgba(79, 216, 255, 0.3); -} - -.lock-dots { - display: flex; - align-items: center; - gap: clamp(14px, 1.6vw, 22px); - min-height: 30px; - transition: transform var(--nebula-duration-fast) var(--nebula-ease-console); -} - -.lock-dot { - width: 22px; - height: 22px; - border-radius: 999px; - border: 2px solid rgba(191, 206, 230, 0.38); - background: rgba(255, 255, 255, 0.05); - opacity: 0.75; - transform: scale(0.92); - transition: - transform var(--nebula-duration-fast) var(--nebula-ease-console), - border-color var(--nebula-duration-fast) var(--nebula-ease-console), - background-color var(--nebula-duration-fast) var(--nebula-ease-console), - box-shadow var(--nebula-duration-fast) var(--nebula-ease-console); -} - -.lock-dot.active { - border-color: rgba(79, 216, 255, 0.72); - box-shadow: 0 0 16px rgba(79, 216, 255, 0.34); -} - -.lock-dot.filled { - background: rgba(215, 230, 255, 0.9); - border-color: rgba(215, 230, 255, 0.95); - transform: scale(1); - animation: dotFill var(--nebula-duration-fast) var(--nebula-ease-console); -} - -.lock-dots.is-success .lock-dot.filled { - border-color: rgba(79, 216, 255, 0.95); - background: rgba(79, 216, 255, 0.9); - box-shadow: 0 0 20px rgba(79, 216, 255, 0.58); -} - -.lock-dots.is-error .lock-dot { - border-color: color-mix(in srgb, var(--nebula-color-danger) 72%, rgba(255, 255, 255, 0.3)); - box-shadow: 0 0 20px color-mix(in srgb, var(--nebula-color-danger) 38%, transparent); -} - -.lock-dots.is-shaking { - animation: dotsShake 420ms var(--nebula-ease-console); -} - -.lock-status { - margin: 0; - min-height: 24px; - font-size: 16px; - color: var(--nebula-color-muted); -} - -.lock-status.is-danger { - color: var(--nebula-color-danger); -} - -.lock-right { - align-self: center; - display: grid; - grid-template-columns: repeat(3, minmax(96px, 1fr)); - gap: clamp(10px, 1.4vw, 18px); - justify-items: center; - background: rgba(15, 24, 45, 0.6); - border: 1px solid rgba(255, 255, 255, 0.08); -} - -.lock-num { - width: clamp(82px, 7.3vw, 112px); - height: clamp(82px, 7.3vw, 112px); - border: none; - border-radius: 18px; - font-size: clamp(42px, 3.8vw, 68px); - font-weight: 330; - line-height: 1; - color: color-mix(in srgb, var(--nebula-color-text) 95%, rgba(215, 230, 255, 0.94)); - background: - radial-gradient(circle at 35% 30%, rgba(255, 255, 255, 0.12), transparent 55%), - linear-gradient(162deg, rgba(61, 90, 140, 0.18), rgba(15, 23, 43, 0.6)); - box-shadow: - 0 10px 22px rgba(0, 0, 0, 0.24), - inset 0 1px 0 rgba(255, 255, 255, 0.08); - transition: - transform var(--nebula-duration-nav) var(--nebula-ease-console), - box-shadow var(--nebula-duration-nav) var(--nebula-ease-console), - border-color var(--nebula-duration-nav) var(--nebula-ease-console); - display: inline-flex; - align-items: baseline; - justify-content: center; - gap: 8px; -} - -.lock-num.is-zero { - grid-column: 2; -} - -.lock-num.is-focused { - transform: scale(1.1); - box-shadow: - 0 0 0 2px rgba(79, 216, 255, 0.5), - 0 0 28px rgba(79, 216, 255, 0.36), - 0 12px 30px rgba(0, 0, 0, 0.28); -} - -.lock-map { - font-size: 13px; - font-weight: 650; - letter-spacing: 0.02em; - color: color-mix(in srgb, var(--nebula-color-muted) 80%, #fff); - transform: translateY(-8px); -} - -.lock-num.is-focused .lock-map { - color: color-mix(in srgb, var(--nebula-color-accent) 85%, #fff); -} - -@keyframes dotFill { - 0% { - opacity: 0.35; - transform: scale(0.62); - } - - 100% { - opacity: 1; - transform: scale(1); - } -} - -@keyframes dotsShake { - 0% { - transform: translateX(0); - } - - 20% { - transform: translateX(-9px); - } - - 40% { - transform: translateX(8px); - } - - 60% { - transform: translateX(-6px); - } - - 80% { - transform: translateX(4px); - } - - 100% { - transform: translateX(0); - } -} diff --git a/Bigscreen/src/views/lock/lock.html b/Bigscreen/src/views/lock/lock.html deleted file mode 100644 index 0db89fc..0000000 --- a/Bigscreen/src/views/lock/lock.html +++ /dev/null @@ -1,39 +0,0 @@ -
    -
    -
    -

    Nebula OS

    -
    - -

    --:--

    -
    -
    -
    -
    - -
    -

    --:--

    -

    -- --- ----

    -

    Press any key to unlock

    -
    - -
    -

    Enter Security PIN

    -
    -
    - - - - - - - - - - - - -
    -

    Default v0 PIN: 1234

    -

    -
    -
    diff --git a/Bigscreen/src/views/lock/lock.js b/Bigscreen/src/views/lock/lock.js deleted file mode 100644 index 294665d..0000000 --- a/Bigscreen/src/views/lock/lock.js +++ /dev/null @@ -1,473 +0,0 @@ -import { PROFILE_LABELS } from "../../core/gamepadProfile.js"; - -const PROFILE_BADGE_LABELS = { - xbox: "Xbox", - playstation: "PS", - switch: "Switch", -}; - -const LOCK_TEMPLATE = ` -
    -
    -
    -
    - -

    Nebula User

    -
    -
    -

    Enter your passkey

    - -
    -

    Using your controller, enter your 6-digit passkey.

    -
    -

    -
    - -
    - - - - - - - - - - - - - -
    -
    -
    -`; - -export const createLockView = ({ state, renderView, keyboard }) => { - const ENTRY_DEBOUNCE_MS = 120; - const FAIL_CLEAR_MS = 600; - - let digits = []; - let setupDigits = []; - let setupSeed = ""; - let setupPhase = "create"; - let busy = false; - let lastEntryAt = 0; - let keyboardListener = null; - let profileListener = null; - let awaitingConfirm = false; - - const config = () => state.passkey.getConfig(); - - const controllerLabel = () => PROFILE_LABELS[state.controllerProfile] ?? PROFILE_LABELS.generic; - - const menuGlyphMarkup = () => { - const glyph = state.glyphs.menu ?? "Menu"; - return ``; - }; - - const menuConfirmMarkup = (prefix = "Press ", suffix = " to confirm.") => - `${prefix}${menuGlyphMarkup()}${suffix}`; - - const ACTION_TO_DIGIT = { - up: "1", - left: "2", - down: "3", - right: "4", - l2: "5", - r2: "6", - l1: "7", - r1: "8", - y: "9", - clear: "0", - }; - - const playTone = (frequency = 860, durationMs = 34, gainValue = 0.03) => { - const AudioContextClass = window.AudioContext ?? window.webkitAudioContext; - if (!AudioContextClass) { - return; - } - - try { - const ctx = new AudioContextClass(); - const oscillator = ctx.createOscillator(); - const gain = ctx.createGain(); - oscillator.type = "triangle"; - oscillator.frequency.value = frequency; - gain.gain.value = gainValue; - oscillator.connect(gain); - gain.connect(ctx.destination); - oscillator.start(); - - window.setTimeout(() => { - oscillator.stop(); - ctx.close(); - }, durationMs); - } catch (_error) { - // no-op - } - }; - - const pulseHaptic = (ms = 10) => { - navigator.vibrate?.(ms); - }; - - const setStatus = (text = "", danger = false, { html = false } = {}) => { - const status = document.querySelector("[data-status]"); - if (!status) { - return; - } - - if (html) { - status.innerHTML = text; - } else { - status.textContent = text; - } - status.classList.toggle("is-danger", danger); - }; - - const setConfirmStatus = () => { - awaitingConfirm = true; - setStatus(menuConfirmMarkup(), false, { html: true }); - }; - - const updateControllerBadge = () => { - const badge = document.querySelector("[data-controller-badge]"); - if (!badge) { - return; - } - - const profile = state.controllerProfile ?? "generic"; - badge.hidden = profile === "generic"; - badge.className = `lock-controller-badge lock-controller-badge--${profile}`; - badge.textContent = PROFILE_BADGE_LABELS[profile] ?? state.glyphs.menu ?? ""; - badge.title = `${controllerLabel()} controller detected`; - }; - - const updateCopy = () => { - const copy = document.querySelector("[data-copy]"); - if (!copy) { - return; - } - - const length = config().length; - const label = controllerLabel(); - - if (state.passkeySetupRequired) { - if (setupPhase === "confirm") { - copy.innerHTML = `Re-enter the same ${length}-digit passkey, then ${menuConfirmMarkup("press ", ".")}`; - return; - } - copy.textContent = `Use your ${label} controller buttons to enter your ${length}-digit passkey.`; - return; - } - - if (config().requireConfirm) { - copy.innerHTML = `Use your ${label} controller buttons to enter ${length} digits, then ${menuConfirmMarkup("press ", ".")}`; - return; - } - - copy.textContent = `Use your ${label} controller buttons to enter your ${length}-digit passkey.`; - }; - - const updateMapLabels = () => { - document.querySelectorAll("[data-map]").forEach((element) => { - const action = element.dataset.map; - element.textContent = state.glyphs[action] ?? action?.toUpperCase?.() ?? ""; - }); - }; - - const renderDots = (stateClass = "") => { - const dots = document.querySelector("[data-passkey-dots]"); - if (!dots) { - return; - } - - dots.className = `lock-dots ${stateClass}`.trim(); - dots.innerHTML = ""; - - for (let index = 0; index < config().length; index += 1) { - const dot = document.createElement("span"); - dot.className = "lock-dot"; - - if (index < digits.length) { - dot.classList.add("filled"); - } - if (index === digits.length && digits.length < config().length) { - dot.classList.add("active"); - } - - dots.append(dot); - } - }; - - const clearDigits = () => { - digits = []; - awaitingConfirm = false; - renderDots(); - }; - - const lockoutText = (remainingMs) => `Too many attempts. Retry in ${Math.ceil(remainingMs / 1000)}s.`; - - const applyFailure = (text) => { - setStatus(text, true); - renderDots("is-error is-shaking"); - playTone(220, 84, 0.05); - - window.setTimeout(() => { - clearDigits(); - }, FAIL_CLEAR_MS); - }; - - const applySuccess = () => { - const view = document.querySelector(".lock-view"); - setStatus("", false); - renderDots("is-success"); - view?.classList.add("is-success"); - playTone(1200, 66, 0.04); - pulseHaptic(24); - - window.setTimeout(() => { - state.locked = false; - state.activeView = "home"; - renderView("home"); - }, 240); - }; - - const submitDigits = async () => { - if (busy) { - return; - } - - if (digits.length !== config().length) { - setStatus(`Enter ${config().length} digits.`, true); - return; - } - - busy = true; - - if (!config().enabled) { - applySuccess(); - busy = false; - return; - } - - if (state.passkeySetupRequired) { - if (setupPhase === "create") { - setupDigits = [...digits]; - setupSeed = setupDigits.join("|"); - clearDigits(); - setupPhase = "confirm"; - updateCopy(); - setConfirmStatus(); - busy = false; - return; - } - - const confirmSeed = digits.join("|"); - const same = setupSeed.length > 0 - && setupDigits.length === digits.length - && setupDigits.every((digit, index) => digit === digits[index]) - && confirmSeed === setupSeed; - if (!same) { - setupPhase = "create"; - setupDigits = []; - setupSeed = ""; - updateCopy(); - applyFailure("Passkeys did not match. Try again."); - busy = false; - return; - } - - await state.passkey.setSequence(setupDigits); - state.passkeySetupRequired = false; - applySuccess(); - busy = false; - return; - } - - const result = await state.passkey.verifySequence(digits); - if (result.ok) { - applySuccess(); - busy = false; - return; - } - - if (result.reason === "lockout") { - applyFailure(lockoutText(result.lockoutRemainingMs ?? state.passkey.getLockoutRemainingMs())); - busy = false; - return; - } - - if (result.reason === "setup-required") { - state.passkeySetupRequired = true; - setupPhase = "create"; - setupDigits = []; - updateCopy(); - applyFailure("Passkey setup required."); - busy = false; - return; - } - - applyFailure(`Incorrect passkey. ${result.attemptsLeft ?? 0} attempts left.`); - busy = false; - }; - - const pushDigit = (digit) => { - if (busy || state.passkey.inLockout()) { - if (state.passkey.inLockout()) { - setStatus(lockoutText(state.passkey.getLockoutRemainingMs()), true); - } - return; - } - - const now = performance.now(); - if (now - lastEntryAt < ENTRY_DEBOUNCE_MS) { - return; - } - lastEntryAt = now; - - if (digits.length >= config().length) { - return; - } - - digits.push(String(digit)); - renderDots(); - setStatus(""); - playTone(); - pulseHaptic(8); - - if (digits.length === config().length && !config().requireConfirm) { - if (state.passkeySetupRequired) { - setConfirmStatus(); - } else { - submitDigits(); - } - } else if (digits.length === config().length && config().requireConfirm) { - setConfirmStatus(); - } - }; - - const deleteLast = () => { - if (!digits.length || busy) { - return; - } - digits = digits.slice(0, -1); - awaitingConfirm = false; - renderDots(); - setStatus(""); - }; - - const clearAll = () => { - if (!digits.length || busy) { - return; - } - clearDigits(); - setStatus(""); - }; - - const handleNumberKey = (event) => { - if (!config().keyboardSupport || !state.locked) { - return; - } - - const digitMatch = event.code.match(/^(Digit|Numpad)(\d)$/); - if (!digitMatch) { - return; - } - - event.preventDefault(); - pushDigit(digitMatch[2]); - }; - - return { - id: "lock", - render: () => LOCK_TEMPLATE, - mount: () => { - keyboard?.close?.(); - if (state.userSetupRequired) { - renderView("user-setup"); - return; - } - - setupPhase = "create"; - setupDigits = []; - setupSeed = ""; - clearDigits(); - updateCopy(); - setStatus(""); - - const username = document.querySelector("[data-username]"); - if (username) { - username.textContent = state.profileName ?? "Nebula User"; - } - - document.documentElement.style.setProperty("--nebula-focus-strength", "1"); - document.documentElement.dataset.animSpeed = config().animationSpeed; - document.documentElement.classList.toggle("high-contrast", config().highContrast); - updateMapLabels(); - updateControllerBadge(); - state.refreshControllerGlyphs?.(); - - if (keyboardListener) { - window.removeEventListener("keydown", keyboardListener); - } - keyboardListener = handleNumberKey; - window.addEventListener("keydown", keyboardListener); - - if (profileListener) { - window.removeEventListener("nebula-controller-profile", profileListener); - } - profileListener = () => { - updateMapLabels(); - updateCopy(); - updateControllerBadge(); - if (awaitingConfirm) { - setConfirmStatus(); - } - if (state.activeView === "lock") { - window.dispatchEvent(new CustomEvent("nebula-navigation-refresh")); - } - }; - window.addEventListener("nebula-controller-profile", profileListener); - }, - getNavigationContract: () => { - const root = document.querySelector("[data-focus-root]"); - const defaultFocus = root?.querySelector("[data-digit='5']") ?? root?.querySelector("[data-digit='1']") ?? null; - - return { - focusRoot: root, - defaultFocus, - layout: { type: "grid", cols: 3, rows: 4 }, - hintsTemplate: "#lock-hints-template", - nebulaNavigation: state.nebula.navigation, - captureDirectionalInput: true, - onAccept: (element) => { - const digit = element?.dataset.digit; - if (!digit) { - return; - } - pushDigit(digit); - }, - onBack: () => { - deleteLast(); - }, - onMenu: () => { - if (digits.length === config().length) { - submitDigits(); - return; - } - setStatus(`Enter ${config().length} digits first.`, true); - }, - onAction: (action) => { - const mappedDigit = ACTION_TO_DIGIT[action]; - if (mappedDigit) { - pushDigit(mappedDigit); - return true; - } - - return false; - }, - }; - }, - }; -}; diff --git a/Bigscreen/src/views/onboarding/userSetup.css b/Bigscreen/src/views/onboarding/userSetup.css deleted file mode 100644 index 50184de..0000000 --- a/Bigscreen/src/views/onboarding/userSetup.css +++ /dev/null @@ -1,84 +0,0 @@ -.user-setup-view { - justify-content: center; - align-items: center; -} - -.user-setup-layout { - width: min(760px, 94vw); - min-height: min(520px, 66vh); - display: flex; - align-items: center; - justify-content: center; - padding: clamp(24px, 3vw, 38px); -} - -.user-setup-main { - width: min(560px, 100%); - display: flex; - flex-direction: column; - gap: 16px; -} - -.user-setup-eyebrow { - margin: 0; - font-size: 13px; - letter-spacing: 0.08em; - text-transform: uppercase; - color: var(--nebula-color-muted); -} - -.user-setup-title { - margin: 0; - font-size: clamp(34px, 4vw, 48px); - font-weight: 560; -} - -.user-setup-copy { - margin: 0; - color: var(--nebula-color-muted); - font-size: 18px; - line-height: 1.45; -} - -.user-setup-fields { - display: grid; - gap: 10px; - margin-top: 10px; -} - -.user-setup-field { - border: 1px solid rgba(255, 255, 255, 0.12); - border-radius: 12px; - background: rgba(11, 19, 36, 0.58); - padding: 12px 14px; -} - -.user-setup-field.is-active { - border-color: rgba(79, 216, 255, 0.7); - box-shadow: 0 0 0 1px rgba(79, 216, 255, 0.35); -} - -.user-setup-label { - display: block; - font-size: 12px; - color: var(--nebula-color-muted); - margin-bottom: 6px; -} - -.user-setup-value { - margin: 0; - min-height: 24px; - font-size: 20px; -} - -.user-setup-status { - margin: 8px 0 0; - min-height: 22px; - font-size: 15px; - color: var(--nebula-color-muted); -} - -.user-setup-status.is-danger { - color: var(--nebula-color-danger); -} - diff --git a/Bigscreen/src/views/onboarding/userSetup.js b/Bigscreen/src/views/onboarding/userSetup.js deleted file mode 100644 index 3b5a131..0000000 --- a/Bigscreen/src/views/onboarding/userSetup.js +++ /dev/null @@ -1,203 +0,0 @@ -import { createUser } from "../../core/users.js"; - -const USER_SETUP_TEMPLATE = ` -
    -
    -
    -

    First Time Setup

    -

    Create Your Profile

    -

    Use your controller to enter your first and last name, then press Done.

    - -
    -
    - First Name -

    -

    -
    -
    - Last Name (Optional) -

    -

    -
    -
    - -

    -
    -
    -
    -`; - -export const createUserSetupView = ({ state, renderView, keyboard }) => { - const model = { - activeField: "firstName", - firstName: "", - lastName: "", - busy: false, - }; - - const setStatus = (text = "", danger = false) => { - const status = document.querySelector("[data-user-setup-status]"); - if (!status) { - return; - } - status.textContent = text; - status.classList.toggle("is-danger", danger); - }; - - const renderFields = () => { - const firstValue = document.querySelector("[data-field-value='firstName']"); - const lastValue = document.querySelector("[data-field-value='lastName']"); - if (firstValue) { - firstValue.textContent = model.firstName || "-"; - } - if (lastValue) { - lastValue.textContent = model.lastName || "-"; - } - - const firstCard = document.querySelector("[data-field-card='firstName']"); - const lastCard = document.querySelector("[data-field-card='lastName']"); - firstCard?.classList.toggle("is-active", model.activeField === "firstName"); - lastCard?.classList.toggle("is-active", model.activeField === "lastName"); - }; - - const appendCharacter = (character) => { - const field = model.activeField; - const maxLength = 20; - if (character === " ") { - if (!model[field]) { - return; - } - if (model[field].length >= maxLength) { - return; - } - model[field] += " "; - return; - } - if (model[field].length >= maxLength) { - return; - } - model[field] += character; - }; - - const backspaceCharacter = () => { - const field = model.activeField; - if (!model[field]) { - return; - } - model[field] = model[field].slice(0, -1); - }; - - const clearField = () => { - model[model.activeField] = ""; - }; - - const saveUser = async () => { - if (model.busy) { - return; - } - const firstName = model.firstName.trim(); - const lastName = model.lastName.trim(); - if (!firstName) { - setStatus("First name is required.", true); - return; - } - - model.busy = true; - setStatus("Creating profile..."); - try { - const user = await createUser({ firstName, lastName }); - if (!user) { - throw new Error("Could not create user."); - } - state.user = user; - state.profileName = user.name; - state.userSetupRequired = false; - // New profile creation should always start with a fresh passkey setup. - // This avoids inheriting stale passkey state from previous local data. - state.passkey.resetSequence(); - state.passkeySetupRequired = true; - state.activeView = "lock"; - keyboard.close(); - renderView("lock"); - } catch (error) { - setStatus(error?.message ?? "Failed to create user.", true); - } finally { - model.busy = false; - } - }; - - const selectField = (field) => { - model.activeField = field; - renderFields(); - }; - - const nextField = () => { - selectField(model.activeField === "firstName" ? "lastName" : "firstName"); - setStatus(`Editing ${model.activeField === "firstName" ? "First Name" : "Last Name"}.`); - }; - - const prevField = () => { - nextField(); - }; - - return { - id: "user-setup", - render: () => USER_SETUP_TEMPLATE, - mount: () => { - if (!state.userSetupRequired) { - keyboard.close(); - renderView("lock"); - return; - } - model.activeField = "firstName"; - model.firstName = ""; - model.lastName = ""; - model.busy = false; - renderFields(); - keyboard.open({ - onKey: (key) => { - appendCharacter(key); - renderFields(); - setStatus(""); - }, - onBackspace: () => { - backspaceCharacter(); - renderFields(); - setStatus(""); - }, - onClear: () => { - clearField(); - renderFields(); - setStatus(""); - }, - onSubmit: () => { - saveUser(); - }, - onPrevField: () => { - prevField(); - }, - onNextField: () => { - nextField(); - }, - }); - setStatus("Enter first name, then press Enter or Menu."); - }, - getNavigationContract: () => { - return { - focusRoot: null, - defaultFocus: null, - layout: { type: "grid", cols: 1, rows: 1 }, - hintsTemplate: "#keyboard-hints-template", - nebulaNavigation: state.nebula.navigation, - captureDirectionalInput: false, - onAccept: () => {}, - onBack: () => { - keyboard.handleAction("back"); - }, - onMenu: () => { - keyboard.handleAction("menu"); - return false; - }, - onAction: () => false, - }; - }, - }; -}; diff --git a/Bigscreen/src/views/overlays/guidePanel.css b/Bigscreen/src/views/overlays/guidePanel.css deleted file mode 100644 index 4637bd5..0000000 --- a/Bigscreen/src/views/overlays/guidePanel.css +++ /dev/null @@ -1,105 +0,0 @@ -.guide-panel-overlay { - position: fixed; - inset: 0; - z-index: 40; - display: grid; - place-items: center; - padding: var(--nebula-spacing-lg); - background: var(--nebula-color-overlay); -} - -.guide-panel-overlay[hidden] { - display: none !important; -} - -.guide-panel-sheet { - width: min(560px, 94vw); - max-height: min(80vh, 640px); - display: flex; - flex-direction: column; - gap: var(--nebula-spacing-md); -} - -.guide-panel-sheet-head { - margin: 0; -} - -.guide-panel-sheet-title { - margin: 0 0 var(--nebula-spacing-xs); -} - -.guide-panel-sheet-desc { - margin: 0; -} - -.guide-panel-sheet-body { - flex: 1; - min-height: 120px; -} - -.guide-panel-search-input { - width: 100%; - min-height: 52px; - padding: 0 var(--nebula-spacing-md); - border-radius: var(--nebula-radius-md); - border: 1px solid var(--nebula-color-border-mid); - background: var(--nebula-color-panel-alt); - color: var(--nebula-color-text); - font-size: 18px; -} - -.guide-panel-search-input:focus { - outline: 2px solid var(--nebula-color-accent); - outline-offset: 2px; -} - -.guide-panel-hint { - margin-top: var(--nebula-spacing-md); -} - -.guide-panel-placeholder-card { - padding: var(--nebula-spacing-lg); - border-radius: var(--nebula-radius-md); - border: 1px solid var(--nebula-color-border); - background: var(--nebula-color-panel-alt); -} - -.guide-panel-placeholder-title { - margin: 0 0 var(--nebula-spacing-sm); - font-size: 18px; - font-weight: 700; -} - -.guide-panel-placeholder-note { - margin: var(--nebula-spacing-md) 0 0; - font-size: 12px; - color: var(--nebula-color-muted); -} - -.guide-panel-close { - align-self: flex-end; - min-height: 48px; - padding: 0 var(--nebula-spacing-lg); - border: none; - border-radius: var(--nebula-radius-pill); - background: var(--nebula-color-panel-alt); - color: var(--nebula-color-text); - font-weight: 600; - cursor: pointer; -} - -.guide-panel-close.is-focused { - border: 2px solid var(--nebula-color-accent); -} - -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; -} diff --git a/Bigscreen/src/views/overlays/guidePanel.js b/Bigscreen/src/views/overlays/guidePanel.js deleted file mode 100644 index 78a4c43..0000000 --- a/Bigscreen/src/views/overlays/guidePanel.js +++ /dev/null @@ -1,152 +0,0 @@ -const PANEL_COPY = { - search: { - title: "Search", - description: "Search games, apps, and settings across NebulaOS.", - placeholder: "Search NebulaOS…", - }, - notifications: { - title: "Notifications", - description: "System alerts and download updates will appear here.", - }, - downloads: { - title: "Downloads", - description: "Active and completed downloads will be listed here.", - }, - controller: { - title: "Controller Settings", - description: "Map buttons, adjust dead zones, and test input.", - }, -}; - -export const createGuidePanelOverlay = ({ mountRoot }) => { - let openState = false; - let activePanel = null; - let onClose = null; - let overlay = null; - - const renderMarkup = () => { - mountRoot.insertAdjacentHTML( - "beforeend", - ` - - `, - ); - overlay = mountRoot.querySelector("[data-guide-panel-overlay]"); - }; - - const getBodyHtml = (panelId) => { - const copy = PANEL_COPY[panelId]; - if (!copy) { - return `

    This panel is not available yet.

    `; - } - - if (panelId === "search") { - return ` - -

    Results will appear here. (Placeholder)

    - `; - } - - return ` -
    -

    ${copy.title}

    -

    ${copy.description}

    -

    Connected to guide quick actions · stub UI

    -
    - `; - }; - - const close = () => { - openState = false; - activePanel = null; - if (overlay) { - overlay.hidden = true; - } - onClose?.(); - onClose = null; - }; - - const bindOverlayEvents = () => { - overlay?.addEventListener("click", (event) => { - if (event.target === overlay) { - close(); - } - }); - overlay?.querySelector(".guide-panel-close")?.addEventListener("click", () => close()); - }; - - const open = (panelId, options = {}) => { - if (!overlay) { - renderMarkup(); - bindOverlayEvents(); - } - - const copy = PANEL_COPY[panelId] ?? { title: "Panel", description: "" }; - overlay.querySelector("[data-panel-title]").textContent = copy.title; - overlay.querySelector("[data-panel-desc]").textContent = copy.description ?? ""; - overlay.querySelector("[data-panel-body]").innerHTML = getBodyHtml(panelId); - - openState = true; - activePanel = panelId; - onClose = options.onClose ?? null; - overlay.hidden = false; - - const closeBtn = overlay.querySelector("[data-guide-panel-close], .guide-panel-close"); - closeBtn?.focus({ preventScroll: true }); - closeBtn?.classList.add("is-focused"); - - console.log(`[GuidePanel] Opened: ${panelId}`); - }; - - const handleAction = (action) => { - if (!openState) { - return false; - } - - if (action === "accept") { - close(); - return true; - } - - if (action === "back" || action === "menu") { - close(); - return true; - } - - return true; - }; - - return { - open, - close, - isOpen: () => openState, - getActivePanel: () => activePanel, - handleAction, - }; -}; diff --git a/Bigscreen/src/views/overlays/keyboard.css b/Bigscreen/src/views/overlays/keyboard.css deleted file mode 100644 index 55ba1a1..0000000 --- a/Bigscreen/src/views/overlays/keyboard.css +++ /dev/null @@ -1,95 +0,0 @@ -.overlay-keyboard { - position: fixed; - left: 0; - right: 0; - bottom: 0; - z-index: 18; - padding: 10px 16px 14px; - pointer-events: none; -} - -.overlay-keyboard[hidden] { - display: none !important; -} - -.overlay-keyboard-inner { - width: min(1180px, 98vw); - margin: 0 auto; - border-radius: 14px; - border: 1px solid rgba(255, 255, 255, 0.12); - background: - linear-gradient(170deg, rgba(46, 74, 118, 0.46), rgba(14, 25, 43, 0.88)), - rgba(10, 17, 30, 0.9); - box-shadow: - 0 16px 30px rgba(0, 0, 0, 0.35), - inset 0 1px 0 rgba(255, 255, 255, 0.08); - backdrop-filter: blur(10px); - padding: 10px; -} - -.overlay-keyboard-rows { - display: flex; - flex-direction: column; - gap: 6px; -} - -.overlay-keyboard-row { - display: flex; - gap: 6px; - align-items: stretch; -} - -.overlay-keyboard-row.is-space-row { - justify-content: center; - padding-top: 2px; -} - -.overlay-keyboard-key { - flex: 1 1 0; - min-width: 0; - height: 40px; - border: 1px solid rgba(255, 255, 255, 0.16); - border-radius: 8px; - background: linear-gradient(160deg, rgba(60, 95, 143, 0.38), rgba(13, 24, 42, 0.84)); - color: var(--nebula-color-text); - font-size: 13px; - font-weight: 650; - padding: 0 6px; -} - -.overlay-keyboard-key[data-width="2"] { - flex: 1.55 1 0; -} - -.overlay-keyboard-key.is-space { - flex: 0 1 70%; - max-width: 760px; - min-height: 42px; -} - -.overlay-keyboard-key.is-modifier, -.overlay-keyboard-key.is-enter { - font-size: 11px; - letter-spacing: 0.02em; -} - -.overlay-keyboard-key.is-modifier.is-active { - border-color: rgba(79, 216, 255, 0.55); - background: linear-gradient(160deg, rgba(79, 216, 255, 0.28), rgba(13, 24, 42, 0.9)); - color: rgba(79, 216, 255, 0.95); -} - -.overlay-keyboard.is-uppercase .overlay-keyboard-key.is-letter { - color: rgba(220, 240, 255, 0.98); -} - -.overlay-keyboard-key.is-enter { - border-color: rgba(122, 255, 168, 0.46); -} - -.overlay-keyboard-key.is-focused { - box-shadow: - 0 0 0 2px rgba(79, 216, 255, 0.5), - 0 0 20px rgba(79, 216, 255, 0.22); - transform: translateY(-1px); -} diff --git a/Bigscreen/src/views/overlays/keyboard.js b/Bigscreen/src/views/overlays/keyboard.js deleted file mode 100644 index bd0b2d6..0000000 --- a/Bigscreen/src/views/overlays/keyboard.js +++ /dev/null @@ -1,312 +0,0 @@ -const letter = (ch) => ({ id: ch.toUpperCase(), label: ch }); -const symbol = (ch) => ({ id: ch, label: ch }); - -const KEY_ROWS = [ - [ - symbol("'"), - ..."1234567890".split("").map((ch) => symbol(ch)), - symbol("-"), - symbol("="), - { id: "BACKSP", label: "Backsp", width: 2 }, - ], - [ - { id: "TAB", label: "Tab", width: 2 }, - ..."qwertyuiop".split("").map(letter), - symbol("["), - symbol("]"), - { id: "\\", label: "\\", width: 2 }, - ], - [ - { id: "CAPS", label: "Caps", width: 2 }, - ..."asdfghjkl".split("").map(letter), - symbol(";"), - symbol("#"), - { id: "ENTER", label: "Enter", width: 2 }, - ], - [ - { id: "SHIFT", label: "Shift", width: 2 }, - ..."zxcvbnm".split("").map(letter), - symbol(","), - symbol("."), - symbol("/"), - { id: "SHIFT_R", label: "Shift", width: 2 }, - ], - [{ id: "SPACE", label: "", width: 1, space: true }], -]; - -const KEYBOARD_TEMPLATE = ` - -`; - -const clamp = (value, min, max) => Math.max(min, Math.min(max, value)); - -const isLetter = (key) => /^[A-Z]$/.test(key); - -export const createKeyboardOverlay = ({ mountRoot }) => { - mountRoot.innerHTML = KEYBOARD_TEMPLATE; - - const root = mountRoot.querySelector("[data-overlay-keyboard]"); - const rowsRoot = mountRoot.querySelector("[data-overlay-keyboard-rows]"); - const keyButtons = []; - let openState = false; - let selectedRow = 0; - let selectedCol = 0; - let handlers = null; - let capsLock = false; - let shiftActive = false; - let capsButton = null; - const shiftButtons = []; - - const rowWidth = (rowIndex) => KEY_ROWS[rowIndex]?.length ?? 0; - - const lettersAreUppercase = () => capsLock !== shiftActive; - - const applySelection = () => { - keyButtons.forEach((button) => button.classList.remove("is-focused")); - const selected = keyButtons.find( - (button) => - Number(button.dataset.row) === selectedRow && Number(button.dataset.col) === selectedCol, - ); - selected?.classList.add("is-focused"); - }; - - const currentKey = () => { - const selected = keyButtons.find( - (button) => - Number(button.dataset.row) === selectedRow && Number(button.dataset.col) === selectedCol, - ); - return selected?.dataset.key ?? null; - }; - - const updateModifierState = () => { - const uppercase = lettersAreUppercase(); - - root?.classList.toggle("is-uppercase", uppercase); - capsButton?.classList.toggle("is-active", capsLock); - shiftButtons.forEach((button) => button.classList.toggle("is-active", shiftActive)); - - keyButtons.forEach((button) => { - const key = button.dataset.key; - if (!isLetter(key)) { - return; - } - const base = button.dataset.baseLabel ?? key.toLowerCase(); - button.textContent = uppercase ? base.toUpperCase() : base; - }); - }; - - const resolveOutput = (key) => { - if (key === "SPACE") { - return " "; - } - if (isLetter(key)) { - return lettersAreUppercase() ? key : key.toLowerCase(); - } - return key; - }; - - const consumeShift = () => { - if (!shiftActive) { - return; - } - shiftActive = false; - updateModifierState(); - }; - - const build = () => { - if (!rowsRoot) { - return; - } - rowsRoot.innerHTML = ""; - keyButtons.length = 0; - capsButton = null; - shiftButtons.length = 0; - - KEY_ROWS.forEach((row, rowIndex) => { - const rowEl = document.createElement("div"); - rowEl.className = "overlay-keyboard-row"; - if (row.some((key) => key.space)) { - rowEl.classList.add("is-space-row"); - } - - row.forEach((keyDef, colIndex) => { - const button = document.createElement("button"); - button.type = "button"; - button.className = "overlay-keyboard-key"; - button.dataset.row = String(rowIndex); - button.dataset.col = String(colIndex); - button.dataset.key = keyDef.id; - button.dataset.width = String(keyDef.width ?? 1); - if (isLetter(keyDef.id)) { - button.dataset.baseLabel = (keyDef.label ?? keyDef.id).toLowerCase(); - button.classList.add("is-letter"); - } - button.textContent = keyDef.label ?? keyDef.id; - - if (keyDef.width && keyDef.width > 1) { - button.classList.add("is-wide"); - } - if (keyDef.space) { - button.classList.add("is-space"); - button.setAttribute("aria-label", "Space"); - } - if (keyDef.id === "ENTER") { - button.classList.add("is-enter"); - } - if (keyDef.id === "CAPS") { - button.classList.add("is-modifier"); - capsButton = button; - } - if (keyDef.id === "SHIFT" || keyDef.id === "SHIFT_R") { - button.classList.add("is-modifier"); - shiftButtons.push(button); - } - - rowEl.append(button); - keyButtons.push(button); - }); - - rowsRoot.append(rowEl); - }); - - updateModifierState(); - }; - - build(); - - const open = (nextHandlers = {}) => { - handlers = nextHandlers; - openState = true; - capsLock = false; - shiftActive = false; - selectedRow = 2; - selectedCol = 1; - root.hidden = false; - updateModifierState(); - applySelection(); - }; - - const close = () => { - openState = false; - root.hidden = true; - handlers = null; - capsLock = false; - shiftActive = false; - updateModifierState(); - }; - - const move = (direction) => { - if (direction === "left") { - selectedCol = clamp(selectedCol - 1, 0, Math.max(0, rowWidth(selectedRow) - 1)); - return; - } - if (direction === "right") { - selectedCol = clamp(selectedCol + 1, 0, Math.max(0, rowWidth(selectedRow) - 1)); - return; - } - if (direction === "up") { - selectedRow = clamp(selectedRow - 1, 0, KEY_ROWS.length - 1); - selectedCol = clamp(selectedCol, 0, Math.max(0, rowWidth(selectedRow) - 1)); - return; - } - if (direction === "down") { - selectedRow = clamp(selectedRow + 1, 0, KEY_ROWS.length - 1); - selectedCol = clamp(selectedCol, 0, Math.max(0, rowWidth(selectedRow) - 1)); - } - }; - - const activateKey = (key) => { - if (!key) { - return; - } - - if (key === "ENTER") { - handlers?.onSubmit?.(); - return; - } - if (key === "BACKSP") { - handlers?.onBackspace?.(); - return; - } - if (key === "TAB") { - handlers?.onNextField?.(); - return; - } - if (key === "CAPS") { - capsLock = !capsLock; - updateModifierState(); - return; - } - if (key === "SHIFT" || key === "SHIFT_R") { - shiftActive = !shiftActive; - updateModifierState(); - return; - } - if (key === "SPACE") { - handlers?.onKey?.(" "); - consumeShift(); - return; - } - - const output = resolveOutput(key); - if (output) { - handlers?.onKey?.(output); - consumeShift(); - } - }; - - const handleAction = (action) => { - if (!openState) { - return false; - } - - if (action === "up" || action === "down" || action === "left" || action === "right") { - move(action); - applySelection(); - return true; - } - - if (action === "accept") { - activateKey(currentKey()); - return true; - } - - if (action === "back") { - handlers?.onBackspace?.(); - return true; - } - - if (action === "menu") { - handlers?.onSubmit?.(); - return true; - } - - if (action === "clear") { - handlers?.onClear?.(); - return true; - } - - if (action === "l1") { - handlers?.onPrevField?.(); - return true; - } - - if (action === "r1") { - handlers?.onNextField?.(); - return true; - } - - return false; - }; - - return { - open, - close, - isOpen: () => openState, - handleAction, - }; -}; diff --git a/Bigscreen/src/views/overlays/powerMenu.css b/Bigscreen/src/views/overlays/powerMenu.css deleted file mode 100644 index 224ff2f..0000000 --- a/Bigscreen/src/views/overlays/powerMenu.css +++ /dev/null @@ -1,34 +0,0 @@ -.power-overlay { - position: fixed; - inset: 0; - z-index: 20; - display: grid; - place-items: center; - background: var(--nebula-color-overlay); -} - -.power-overlay[hidden] { - display: none !important; -} - -.power-panel { - width: min(520px, 92vw); -} - -.power-title { - margin: 0 0 var(--nebula-spacing-md); -} - -.power-options { - display: grid; - gap: var(--nebula-spacing-sm); -} - -.power-option { - min-height: 62px; - border: none; - background: var(--nebula-color-panelAlt); - color: var(--nebula-color-text); - text-align: left; - padding: var(--nebula-spacing-md); -} diff --git a/Bigscreen/src/views/overlays/powerMenu.html b/Bigscreen/src/views/overlays/powerMenu.html deleted file mode 100644 index b13bafa..0000000 --- a/Bigscreen/src/views/overlays/powerMenu.html +++ /dev/null @@ -1,12 +0,0 @@ - diff --git a/Bigscreen/src/views/overlays/powerMenu.js b/Bigscreen/src/views/overlays/powerMenu.js deleted file mode 100644 index 6a4c474..0000000 --- a/Bigscreen/src/views/overlays/powerMenu.js +++ /dev/null @@ -1,107 +0,0 @@ -const POWER_MENU_TEMPLATE = ` - -`; - -const ACTION_LOG = { - sleep: "Sleep requested (stub)", - "restart-nebula": "Restart NebulaOS requested (stub)", - "restart-system": "Restart System requested (stub)", - shutdown: "Shut Down requested (stub)", -}; - -export const createPowerMenuOverlay = ({ mountRoot }) => { - mountRoot.insertAdjacentHTML("beforeend", POWER_MENU_TEMPLATE); - - const overlay = mountRoot.querySelector("[data-power-overlay]"); - const focusables = Array.from(overlay?.querySelectorAll("[data-focusable='true']") ?? []); - let focusedIndex = 0; - let openState = false; - let onClose = null; - - if (overlay) { - overlay.hidden = true; - } - - const focusAt = (index) => { - focusables.forEach((element) => { - element.classList.remove("is-focused"); - element.setAttribute("aria-selected", "false"); - }); - - focusedIndex = Math.max(0, Math.min(index, focusables.length - 1)); - const target = focusables[focusedIndex]; - target.classList.add("is-focused"); - target.setAttribute("aria-selected", "true"); - target.focus({ preventScroll: true }); - }; - - const close = () => { - openState = false; - overlay.hidden = true; - onClose?.(); - onClose = null; - }; - - const open = (options = {}) => { - openState = true; - onClose = options.onClose ?? null; - overlay.hidden = false; - focusAt(0); - }; - - const runAction = (action) => { - if (action === "cancel") { - close(); - return; - } - - if (ACTION_LOG[action]) { - console.log(`[PowerMenu] ${ACTION_LOG[action]}`); - close(); - } - }; - - const handleAction = (action) => { - if (!openState) { - return; - } - - if (action === "up") { - focusAt(focusedIndex - 1); - return; - } - - if (action === "down") { - focusAt(focusedIndex + 1); - return; - } - - if (action === "accept") { - const current = focusables[focusedIndex]; - runAction(current?.dataset.action ?? "cancel"); - return; - } - - if (action === "back" || action === "menu") { - close(); - } - }; - - return { - open, - close, - isOpen: () => openState, - handleAction, - }; -}; diff --git a/Bigscreen/src/views/placeholder/placeholder.js b/Bigscreen/src/views/placeholder/placeholder.js deleted file mode 100644 index ffd7b7d..0000000 --- a/Bigscreen/src/views/placeholder/placeholder.js +++ /dev/null @@ -1,50 +0,0 @@ -export const createPlaceholderView = (context, { id, title, subtitle, backTarget = "home" }) => ({ - id, - render: () => ` -
    -
    -
    -

    Nebula OS

    -
    - -

    --:--

    -
    -
    -
    -
    -
    -

    ${subtitle}

    -

    ${title}

    -

    This area is part of the NebulaOS shell roadmap. Navigation is wired; content ships in a future update.

    - -
    -
    - `, - mount() {}, - getNavigationContract: () => { - const focusRoot = document.querySelector(`[data-view="${id}"] [data-focus-root]`); - const defaultFocus = focusRoot?.querySelector("[data-focusable='true']"); - return { - focusRoot, - defaultFocus, - hintsTemplate: "#global-hints-template", - onAccept(focused) { - const target = focused?.dataset?.target; - if (target) { - context.renderView(target); - } - }, - onBack() { - context.renderView(backTarget); - }, - }; - }, -}); diff --git a/Bigscreen/src/views/settings/settings.css b/Bigscreen/src/views/settings/settings.css deleted file mode 100644 index 6cc4074..0000000 --- a/Bigscreen/src/views/settings/settings.css +++ /dev/null @@ -1,181 +0,0 @@ -.settings-view { - gap: var(--nebula-spacing-xl); -} - -.settings-header-copy { - display: grid; - gap: var(--nebula-spacing-xs); - padding-left: var(--nebula-spacing-xl); -} - -.settings-header-copy .muted { - margin: 0; - letter-spacing: 0.14em; - text-transform: uppercase; - font-size: 14px; - font-weight: 600; - opacity: 0.72; -} - -.settings-header-copy .view-title { - font-size: clamp(32px, 3.2vw, 44px); - font-weight: 700; - letter-spacing: -0.02em; -} - -.settings-body { - display: grid; - gap: var(--nebula-spacing-lg); - padding: 0 var(--nebula-spacing-xl); -} - -.settings-category-bar { - display: flex; - gap: 10px; - padding-bottom: var(--nebula-spacing-md); - border-bottom: 2px solid rgba(79, 216, 255, 0.15); - position: relative; -} - -.settings-category { - min-height: 56px; - padding: 0 24px; - border-radius: var(--nebula-radius-sm); - border: none; - background: rgba(24, 38, 68, 0.5); - font-weight: 660; - font-size: 16px; - position: relative; - transition: - background-color var(--nebula-duration-nav) var(--nebula-ease-console), - color var(--nebula-duration-nav) var(--nebula-ease-console); -} - -.settings-category.is-focused { - background: rgba(38, 58, 98, 0.7); -} - -.settings-category.is-active { - background: rgba(50, 78, 128, 0.8); - color: var(--nebula-color-accent); -} - -.settings-category::after { - content: ""; - position: absolute; - left: 12px; - right: 12px; - bottom: -14px; - height: 3px; - background: linear-gradient(90deg, var(--nebula-color-accent), rgba(79, 216, 255, 0.6)); - border-radius: 2px 2px 0 0; - box-shadow: 0 0 12px rgba(79, 216, 255, 0.6); - transform: scaleX(0); - transform-origin: center; - transition: transform var(--nebula-duration-nav) var(--nebula-ease-console); -} - -.settings-category.is-active::after { - transform: scaleX(1); -} - -.settings-panel { - display: flex; - flex-direction: column; - gap: var(--nebula-spacing-lg); - min-height: 440px; - background: - linear-gradient(160deg, rgba(65, 108, 189, 0.18), rgba(24, 36, 72, 0.65)), - radial-gradient(circle at 75% 25%, rgba(79, 216, 255, 0.08), transparent 48%), - var(--nebula-color-panel); - border-radius: var(--nebula-radius-md); - padding: var(--nebula-spacing-lg); - box-shadow: - 0 8px 24px rgba(2, 6, 18, 0.35), - inset 0 1px 0 rgba(255, 255, 255, 0.08); -} - -.settings-panel-head { - display: grid; - gap: 8px; - padding-bottom: var(--nebula-spacing-md); - border-bottom: 1px solid rgba(79, 216, 255, 0.12); -} - -.settings-panel-title { - margin: 0; - font-size: 28px; - font-weight: 720; - letter-spacing: -0.01em; -} - -.settings-card-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: var(--nebula-spacing-md); -} - -.settings-card { - border: 1px solid rgba(79, 216, 255, 0.15); - border-radius: var(--nebula-radius-md); - padding: var(--nebula-spacing-lg); - min-height: 180px; - background: - linear-gradient(165deg, rgba(70, 108, 186, 0.28), rgba(22, 34, 68, 0.88)); - text-align: left; - display: grid; - align-content: space-between; - position: relative; - overflow: hidden; - transition: - transform var(--nebula-duration-nav) var(--nebula-ease-console), - border-color var(--nebula-duration-nav) var(--nebula-ease-console); -} - -.settings-card::before { - content: ""; - position: absolute; - inset: 0; - background: linear-gradient(135deg, rgba(79, 216, 255, 0.12), transparent 60%); - opacity: 0; - transition: opacity var(--nebula-duration-nav) var(--nebula-ease-console); -} - -.settings-card.is-focused { - transform: scale(1.04); - border-color: rgba(79, 216, 255, 0.5); -} - -.settings-card.is-focused::before { - opacity: 1; -} - -.settings-card-title { - margin: 0; - font-size: 20px; - font-weight: 680; - letter-spacing: -0.01em; - position: relative; - z-index: 1; -} - -.settings-card p { - margin: 0; - position: relative; - z-index: 1; -} - -.settings-card-info { - border-style: dashed; - opacity: 0.85; -} - -.settings-card.is-disabled { - opacity: 0.55; - border-style: dashed; - filter: grayscale(0.15); -} - -.settings-card.is-disabled.is-focused { - transform: none; -} diff --git a/Bigscreen/src/views/settings/settings.html b/Bigscreen/src/views/settings/settings.html deleted file mode 100644 index e36e1e0..0000000 --- a/Bigscreen/src/views/settings/settings.html +++ /dev/null @@ -1,48 +0,0 @@ -
    -
    -
    -

    Nebula OS

    -
    - -

    --:--

    -
    -
    -
    -
    - -
    -

    System

    -

    Settings

    -
    - -
    -
    - - - - - -
    - -
    -
    -

    Network

    -

    Optimize connection and online routing.

    -
    -
    - - -
    -

    Status

    -

    Applying Nebula profile

    -
    -
    -
    -
    -
    diff --git a/Bigscreen/src/views/settings/settings.js b/Bigscreen/src/views/settings/settings.js deleted file mode 100644 index 1be1101..0000000 --- a/Bigscreen/src/views/settings/settings.js +++ /dev/null @@ -1,228 +0,0 @@ -const SETTINGS_TEMPLATE = ` -
    -
    -
    -

    Nebula OS

    -
    - -

    --:--

    -
    -
    -
    -
    - -
    -

    System

    -

    Settings

    -
    - -
    -
    - - - - - -
    - -
    -
    -

    Network

    -

    Optimize connection and online routing.

    -
    -
    - - - - - -
    -

    Status

    -

    Applying Nebula profile

    -
    -
    -
    -
    -
    -`; - -const CATEGORIES = { - network: "Network", - audio: "Audio", - display: "Display", - storage: "Storage", - system: "System", -}; - -export const createSettingsView = ({ state, renderView }) => { - const updatePasskey = (partial) => { - state.passkey.updateConfig(partial); - refreshPanel(); - }; - - const refreshPanel = () => { - const title = document.querySelector("[data-panel-title]"); - const copy = document.querySelector("[data-panel-copy]"); - const passkeyEnabled = document.querySelector("[data-passkey-enabled]"); - const passkeyLength = document.querySelector("[data-passkey-length]"); - const passkeyConfirm = document.querySelector("[data-passkey-confirm]"); - const passkeyKeyboard = document.querySelector("[data-passkey-keyboard]"); - const status = document.querySelector("[data-status-note]"); - const passkeyConfig = state.passkey.getConfig(); - - document.querySelectorAll(".settings-category").forEach((button) => { - button.classList.toggle("is-active", button.dataset.cat === state.settingsCategory); - }); - - if (title) { - title.textContent = CATEGORIES[state.settingsCategory] ?? "Settings"; - } - if (copy) { - if (state.settingsCategory === "system") { - copy.textContent = "Configure passkey security and controller login behavior."; - } else { - copy.textContent = `Tune ${CATEGORIES[state.settingsCategory] ?? "system"} options with controller-first cards.`; - } - } - if (passkeyEnabled) { - passkeyEnabled.textContent = passkeyConfig.enabled ? "Enabled" : "Disabled"; - } - if (passkeyLength) { - passkeyLength.textContent = `${passkeyConfig.length} digits`; - } - if (passkeyConfirm) { - passkeyConfirm.textContent = passkeyConfig.requireConfirm ? "Enabled" : "Disabled"; - } - if (passkeyKeyboard) { - passkeyKeyboard.textContent = passkeyConfig.keyboardSupport ? "Enabled" : "Disabled"; - } - if (status) { - status.textContent = state.settingsCategory === "system" - ? `Attempts: ${passkeyConfig.maxAttempts}, cooldown: ${passkeyConfig.cooldownSeconds}s` - : `${CATEGORIES[state.settingsCategory] ?? "System"} profile synced`; - } - - document.querySelectorAll("[data-toggle^='passkey']").forEach((button) => { - button.classList.toggle("is-disabled", state.settingsCategory !== "system"); - button.setAttribute("aria-disabled", state.settingsCategory === "system" ? "false" : "true"); - }); - }; - - const toggleCurrent = (suffix = "") => { - const key = suffix ? `${state.settingsCategory}_${suffix}` : state.settingsCategory; - state.settingsValues[key] = !Boolean(state.settingsValues[key]); - refreshPanel(); - }; - - return { - id: "settings", - render: () => SETTINGS_TEMPLATE, - mount: () => { - const root = document.querySelector("[data-focus-root]"); - root?.addEventListener("focusin", (event) => { - const focused = event.target.closest("[data-focusable='true']"); - const col = Number(focused?.dataset.col ?? 0); - document.documentElement.style.setProperty("--nebula-accent-line-x", `${24 + col * 110}px`); - }); - refreshPanel(); - document.documentElement.style.setProperty("--nebula-focus-strength", "1"); - }, - getNavigationContract: () => { - const root = document.querySelector("[data-focus-root]"); - return { - focusRoot: root, - defaultFocus: root?.querySelector("[data-cat='network']") ?? null, - layout: { type: "grid", cols: 5, rows: 2 }, - hintsTemplate: "#global-hints-template", - nebulaNavigation: state.nebula.navigation, - onAccept: (element) => { - if (!element) { - return; - } - - const category = element.dataset.cat; - const toggle = element.dataset.toggle; - - if (category) { - state.settingsCategory = category; - refreshPanel(); - return; - } - - if (toggle === "primary") { - toggleCurrent(); - return; - } - - if (toggle === "secondary") { - toggleCurrent("secondary"); - return; - } - - if (state.settingsCategory !== "system") { - return; - } - - if (toggle === "passkey-enabled") { - updatePasskey({ enabled: !state.passkey.getConfig().enabled }); - return; - } - - if (toggle === "passkey-change") { - state.passkey.resetSequence(); - state.passkeySetupRequired = true; - state.locked = true; - state.activeView = "lock"; - renderView("lock"); - return; - } - - if (toggle === "passkey-length") { - return; - } - - if (toggle === "passkey-confirm") { - updatePasskey({ requireConfirm: !state.passkey.getConfig().requireConfirm }); - return; - } - - if (toggle === "passkey-keyboard") { - updatePasskey({ keyboardSupport: !state.passkey.getConfig().keyboardSupport }); - } - }, - onBack: () => { - state.activeView = "home"; - renderView("home"); - }, - onMenu: () => {}, - onAction: (action, element) => { - if (state.settingsCategory !== "system") { - return false; - } - - if (element?.dataset.toggle !== "passkey-length") { - return false; - } - - return false; - }, - }; - }, - }; -};