commit 09554c68df1ddbd62fba84209bbddb9a388be476 Author: Youwes09 Date: Fri Feb 20 23:34:10 2026 -0600 [BETA] Initial Commit (Nix Support Only) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1d21e57 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +# --- Build Artifacts --- +node_modules/ +dist/ +dist-tauri/ +target/ +bin/ +out/ + +# --- Nix --- +.direnv/ +result +result-* + +# --- Logs --- +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.env +.env.local +.env.*.local + +# --- IDEs & OS --- +.vscode/ +.idea/ +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.swp + +# --- Tauri specific --- +src-tauri/target/ +src-tauri/gen/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e00942b --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +
+ +

Moku

+

A manga reader frontend for Suwayomi-Server, built with Tauri and React.

+
+ +--- + +## Requirements + +- [Suwayomi-Server](https://github.com/Suwayomi/Suwayomi-Server) running on `http://127.0.0.1:4567` + +## Installation + +### Nix + +```bash +nix run github:Youwes09/moku +``` + +Or add to your flake: + +```nix +inputs.moku.url = "github:Youwes09/moku"; +``` + +### From source + +```bash +git clone https://github.com/Youwes09/moku +cd moku +nix build +./result/bin/moku +``` + +## Development + +```bash +nix develop +pnpm install +pnpm tauri dev +``` + +## Stack + +- [Tauri v2](https://tauri.app) — app shell +- [React](https://react.dev) + [TypeScript](https://www.typescriptlang.org) — UI +- [Vite](https://vitejs.dev) — frontend build +- [Zustand](https://zustand-demo.pmnd.rs) — state +- [Crane](https://github.com/ipetkov/crane) — Nix Rust builds \ No newline at end of file diff --git a/Todo b/Todo new file mode 100644 index 0000000..e69de29 diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..896735e --- /dev/null +++ b/flake.lock @@ -0,0 +1,98 @@ +{ + "nodes": { + "crane": { + "locked": { + "lastModified": 1771438068, + "narHash": "sha256-nGBbXvEZVe/egCPVPFcu89RFtd8Rf6J+4RFoVCFec0A=", + "owner": "ipetkov", + "repo": "crane", + "rev": "b5090e53e9d68c523a4bb9ad42b4737ee6747597", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1769996383, + "narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "57928607ea566b5db3ad13af0e57e921e6b12381", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1771369470, + "narHash": "sha256-0NBlEBKkN3lufyvFegY4TYv5mCNHbi5OmBDrzihbBMQ=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "0182a361324364ae3f436a63005877674cf45efb", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1769909678, + "narHash": "sha256-cBEymOf4/o3FD5AZnzC3J9hLbiZ+QDT/KDuyHXVJOpM=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "72716169fe93074c333e8d0173151350670b824c", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "root": { + "inputs": { + "crane": "crane", + "flake-parts": "flake-parts", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1771556776, + "narHash": "sha256-zKprqMQDl3xVfhSSYvgru1IGXjFdxryWk+KqK0I20Xk=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "8b3f46b8a6d17ab46e533a5e3d5b1cc2ff228860", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..01eaed9 --- /dev/null +++ b/flake.nix @@ -0,0 +1,162 @@ +{ + description = "Moku — manga reader frontend for Suwayomi"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-parts.url = "github:hercules-ci/flake-parts"; + crane.url = "github:ipetkov/crane"; + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = + inputs@{ flake-parts, crane, rust-overlay, ... }: + flake-parts.lib.mkFlake { inherit inputs; } { + systems = [ + "x86_64-linux" + "aarch64-linux" + ]; + + perSystem = + { system, pkgs, lib, ... }: + let + pkgs' = import inputs.nixpkgs { + inherit system; + overlays = [ rust-overlay.overlays.default ]; + }; + + rustToolchain = pkgs'.rust-bin.stable.latest.default.override { + extensions = [ + "rust-src" + "rust-analyzer" + ]; + }; + + craneLib = (crane.mkLib pkgs').overrideToolchain rustToolchain; + + runtimeLibs = with pkgs; [ + webkitgtk_4_1 + gtk3 + glib + cairo + pango + atk + gdk-pixbuf + libsoup_3 + openssl + dbus + libappindicator-gtk3 + gsettings-desktop-schemas + ]; + + # Frontend (Vite/TypeScript) built as a separate derivation. + # Update `hash` whenever pnpm-lock.yaml changes: + # nix build .#frontend 2>&1 | grep "got:" + frontend = pkgs.stdenv.mkDerivation { + pname = "moku-frontend"; + version = "0.1.0"; + src = lib.cleanSource ./.; + + nativeBuildInputs = with pkgs; [ + nodejs_22 + pnpm + pnpmConfigHook + ]; + + pnpmDeps = pkgs.fetchPnpmDeps { + pname = "moku-frontend"; + version = "0.1.0"; + src = lib.cleanSource ./.; + fetcherVersion = 1; + hash = "sha256-2Hdzsjwbb+CKiRn/nGHwLeysKvpvEhd5C213YgWmOSU="; + }; + + buildPhase = "pnpm build"; + installPhase = "cp -r dist $out"; + }; + + # tauri::generate_context!() embeds icons and reads tauri.conf.json + + # capabilities at compile time — all must survive the source filter. + cargoSrc = lib.cleanSourceWith { + src = ./src-tauri; + filter = path: type: + (craneLib.filterCargoSources path type) + || (lib.hasInfix "/icons/" path) + || (lib.hasInfix "/capabilities/" path) + || (builtins.baseNameOf path == "tauri.conf.json"); + }; + + commonArgs = { + src = cargoSrc; + cargoToml = ./src-tauri/Cargo.toml; + cargoLock = ./src-tauri/Cargo.lock; + strictDeps = true; + + buildInputs = runtimeLibs; + + nativeBuildInputs = with pkgs; [ + pkg-config + wrapGAppsHook3 + ]; + + # Crane unpacks source to /build/source (src-tauri/). + # tauri.conf.json has frontendDist = "../dist", so dist goes one + # level up at /build/dist. + preBuild = '' + cp -r ${frontend} ../dist + ''; + + WEBKIT_DISABLE_COMPOSITING_MODE = "1"; + }; + + cargoArtifacts = craneLib.buildDepsOnly commonArgs; + + moku = craneLib.buildPackage (commonArgs // { + inherit cargoArtifacts; + + postInstall = '' + wrapProgram $out/bin/moku \ + --prefix XDG_DATA_DIRS : "${lib.makeSearchPath "share/gsettings-schemas" [ + pkgs.gsettings-desktop-schemas + pkgs.gtk3 + ]}" \ + --prefix LD_LIBRARY_PATH : "${lib.makeLibraryPath runtimeLibs}" \ + --prefix PATH : "${lib.makeBinPath [ pkgs.suwayomi-server ]}" + ''; + }); + + in + { + packages = { + inherit moku frontend; + default = moku; + }; + + devShells.default = pkgs.mkShell { + buildInputs = runtimeLibs; + + nativeBuildInputs = with pkgs; [ + rustToolchain + pkg-config + wrapGAppsHook3 + nodejs_22 + pnpm + suwayomi-server + ]; + + shellHook = '' + export WEBKIT_DISABLE_COMPOSITING_MODE=1 + export PKG_CONFIG_PATH="${pkgs.openssl.dev}/lib/pkgconfig''${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" + export XDG_DATA_DIRS="${pkgs.gsettings-desktop-schemas}/share/gsettings-schemas/${pkgs.gsettings-desktop-schemas.name}:${pkgs.gtk3}/share/gsettings-schemas/${pkgs.gtk3.name}''${XDG_DATA_DIRS:+:$XDG_DATA_DIRS}" + + echo "Moku dev shell" + echo " pnpm install && pnpm tauri dev" + ''; + }; + + formatter = pkgs.nixfmt-rfc-style; + }; + }; +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..d21faea --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + + Moku + + +
+ + + \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..2402fd1 --- /dev/null +++ b/package.json @@ -0,0 +1,40 @@ +{ + "name": "moku", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "tauri": "tauri", + "tauri:dev": "tauri dev", + "tauri:build": "tauri build" + }, + "dependencies": { + "@phosphor-icons/react": "^2.1.10", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-progress": "^1.1.8", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-tooltip": "^1.2.8", + "@tauri-apps/api": "^2.0.0", + "@tauri-apps/plugin-shell": "~2", + "clsx": "^2.1.1", + "lucide-react": "^0.575.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.26.0", + "zustand": "^5.0.0" + }, + "devDependencies": { + "@tauri-apps/cli": "^2.0.0", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.40", + "tailwindcss": "^3.4.7", + "typescript": "^5.5.3", + "vite": "^5.4.0" + } +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..2a7ca53 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2681 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@phosphor-icons/react': + specifier: ^2.1.10 + version: 2.1.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.16 + version: 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-progress': + specifier: ^1.1.8 + version: 1.1.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-scroll-area': + specifier: ^1.2.10 + version: 1.2.10(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-tooltip': + specifier: ^1.2.8 + version: 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tauri-apps/api': + specifier: ^2.0.0 + version: 2.10.1 + '@tauri-apps/plugin-shell': + specifier: ~2 + version: 2.3.5 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + lucide-react: + specifier: ^0.575.0 + version: 0.575.0(react@18.3.1) + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + react-router-dom: + specifier: ^6.26.0 + version: 6.30.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + zustand: + specifier: ^5.0.0 + version: 5.0.11(@types/react@18.3.28)(react@18.3.1) + devDependencies: + '@tauri-apps/cli': + specifier: ^2.0.0 + version: 2.10.0 + '@types/react': + specifier: ^18.3.3 + version: 18.3.28 + '@types/react-dom': + specifier: ^18.3.0 + version: 18.3.7(@types/react@18.3.28) + '@vitejs/plugin-react': + specifier: ^4.3.1 + version: 4.7.0(vite@5.4.21) + autoprefixer: + specifier: ^10.4.20 + version: 10.4.24(postcss@8.5.6) + postcss: + specifier: ^8.4.40 + version: 8.5.6 + tailwindcss: + specifier: ^3.4.7 + version: 3.4.19 + typescript: + specifier: ^5.5.3 + version: 5.9.3 + vite: + specifier: ^5.4.0 + version: 5.4.21 + +packages: + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.0': + resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@floating-ui/core@1.7.4': + resolution: {integrity: sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==} + + '@floating-ui/dom@1.7.5': + resolution: {integrity: sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==} + + '@floating-ui/react-dom@2.1.7': + resolution: {integrity: sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.10': + resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@phosphor-icons/react@2.1.10': + resolution: {integrity: sha512-vt8Tvq8GLjheAZZYa+YG/pW7HDbov8El/MANW8pOAz4eGxrwhnbfrQZq0Cp4q8zBEu8NIhHdnr+r8thnfRSNYA==} + engines: {node: '>=10'} + peerDependencies: + react: '>= 16.8' + react-dom: '>= 16.8' + + '@radix-ui/number@1.1.1': + resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + + '@radix-ui/primitive@1.1.3': + resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + + '@radix-ui/react-arrow@1.1.7': + resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.3': + resolution: {integrity: sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.11': + resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-dropdown-menu@2.1.16': + resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.3': + resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-menu@2.1.16': + resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popper@1.2.8': + resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.5': + resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.4': + resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-progress@1.1.8': + resolution: {integrity: sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.1.11': + resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-scroll-area@1.2.10': + resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-slot@1.2.4': + resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-tooltip@1.2.8': + resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.1': + resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-visually-hidden@1.2.3': + resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/rect@1.1.1': + resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + + '@remix-run/router@1.23.2': + resolution: {integrity: sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==} + engines: {node: '>=14.0.0'} + + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + + '@rollup/rollup-android-arm-eabi@4.58.0': + resolution: {integrity: sha512-mr0tmS/4FoVk1cnaeN244A/wjvGDNItZKR8hRhnmCzygyRXYtKF5jVDSIILR1U97CTzAYmbgIj/Dukg62ggG5w==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.58.0': + resolution: {integrity: sha512-+s++dbp+/RTte62mQD9wLSbiMTV+xr/PeRJEc/sFZFSBRlHPNPVaf5FXlzAL77Mr8FtSfQqCN+I598M8U41ccQ==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.58.0': + resolution: {integrity: sha512-MFWBwTcYs0jZbINQBXHfSrpSQJq3IUOakcKPzfeSznONop14Pxuqa0Kg19GD0rNBMPQI2tFtu3UzapZpH0Uc1Q==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.58.0': + resolution: {integrity: sha512-yiKJY7pj9c9JwzuKYLFaDZw5gma3fI9bkPEIyofvVfsPqjCWPglSHdpdwXpKGvDeYDms3Qal8qGMEHZ1M/4Udg==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.58.0': + resolution: {integrity: sha512-x97kCoBh5MOevpn/CNK9W1x8BEzO238541BGWBc315uOlN0AD/ifZ1msg+ZQB05Ux+VF6EcYqpiagfLJ8U3LvQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.58.0': + resolution: {integrity: sha512-Aa8jPoZ6IQAG2eIrcXPpjRcMjROMFxCt1UYPZZtCxRV68WkuSigYtQ/7Zwrcr2IvtNJo7T2JfDXyMLxq5L4Jlg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.58.0': + resolution: {integrity: sha512-Ob8YgT5kD/lSIYW2Rcngs5kNB/44Q2RzBSPz9brf2WEtcGR7/f/E9HeHn1wYaAwKBni+bdXEwgHvUd0x12lQSA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.58.0': + resolution: {integrity: sha512-K+RI5oP1ceqoadvNt1FecL17Qtw/n9BgRSzxif3rTL2QlIu88ccvY+Y9nnHe/cmT5zbH9+bpiJuG1mGHRVwF4Q==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.58.0': + resolution: {integrity: sha512-T+17JAsCKUjmbopcKepJjHWHXSjeW7O5PL7lEFaeQmiVyw4kkc5/lyYKzrv6ElWRX/MrEWfPiJWqbTvfIvjM1Q==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.58.0': + resolution: {integrity: sha512-cCePktb9+6R9itIJdeCFF9txPU7pQeEHB5AbHu/MKsfH/k70ZtOeq1k4YAtBv9Z7mmKI5/wOLYjQ+B9QdxR6LA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.58.0': + resolution: {integrity: sha512-iekUaLkfliAsDl4/xSdoCJ1gnnIXvoNz85C8U8+ZxknM5pBStfZjeXgB8lXobDQvvPRCN8FPmmuTtH+z95HTmg==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.58.0': + resolution: {integrity: sha512-68ofRgJNl/jYJbxFjCKE7IwhbfxOl1muPN4KbIqAIe32lm22KmU7E8OPvyy68HTNkI2iV/c8y2kSPSm2mW/Q9Q==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.58.0': + resolution: {integrity: sha512-dpz8vT0i+JqUKuSNPCP5SYyIV2Lh0sNL1+FhM7eLC457d5B9/BC3kDPp5BBftMmTNsBarcPcoz5UGSsnCiw4XQ==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.58.0': + resolution: {integrity: sha512-4gdkkf9UJ7tafnweBCR/mk4jf3Jfl0cKX9Np80t5i78kjIH0ZdezUv/JDI2VtruE5lunfACqftJ8dIMGN4oHew==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.58.0': + resolution: {integrity: sha512-YFS4vPnOkDTD/JriUeeZurFYoJhPf9GQQEF/v4lltp3mVcBmnsAdjEWhr2cjUCZzZNzxCG0HZOvJU44UGHSdzw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.58.0': + resolution: {integrity: sha512-x2xgZlFne+QVNKV8b4wwaCS8pwq3y14zedZ5DqLzjdRITvreBk//4Knbcvm7+lWmms9V9qFp60MtUd0/t/PXPw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.58.0': + resolution: {integrity: sha512-jIhrujyn4UnWF8S+DHSkAkDEO3hLX0cjzxJZPLF80xFyzyUIYgSMRcYQ3+uqEoyDD2beGq7Dj7edi8OnJcS/hg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.58.0': + resolution: {integrity: sha512-+410Srdoh78MKSJxTQ+hZ/Mx+ajd6RjjPwBPNd0R3J9FtL6ZA0GqiiyNjCO9In0IzZkCNrpGymSfn+kgyPQocg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.58.0': + resolution: {integrity: sha512-ZjMyby5SICi227y1MTR3VYBpFTdZs823Rs/hpakufleBoufoOIB6jtm9FEoxn/cgO7l6PM2rCEl5Kre5vX0QrQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.58.0': + resolution: {integrity: sha512-ds4iwfYkSQ0k1nb8LTcyXw//ToHOnNTJtceySpL3fa7tc/AsE+UpUFphW126A6fKBGJD5dhRvg8zw1rvoGFxmw==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.58.0': + resolution: {integrity: sha512-fd/zpJniln4ICdPkjWFhZYeY/bpnaN9pGa6ko+5WD38I0tTqk9lXMgXZg09MNdhpARngmxiCg0B0XUamNw/5BQ==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.58.0': + resolution: {integrity: sha512-YpG8dUOip7DCz3nr/JUfPbIUo+2d/dy++5bFzgi4ugOGBIox+qMbbqt/JoORwvI/C9Kn2tz6+Bieoqd5+B1CjA==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.58.0': + resolution: {integrity: sha512-b9DI8jpFQVh4hIXFr0/+N/TzLdpBIoPzjt0Rt4xJbW3mzguV3mduR9cNgiuFcuL/TeORejJhCWiAXe3E/6PxWA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.58.0': + resolution: {integrity: sha512-CSrVpmoRJFN06LL9xhkitkwUcTZtIotYAF5p6XOR2zW0Zz5mzb3IPpcoPhB02frzMHFNo1reQ9xSF5fFm3hUsQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.58.0': + resolution: {integrity: sha512-QFsBgQNTnh5K0t/sBsjJLq24YVqEIVkGpfN2VHsnN90soZyhaiA9UUHufcctVNL4ypJY0wrwad0wslx2KJQ1/w==} + cpu: [x64] + os: [win32] + + '@tauri-apps/api@2.10.1': + resolution: {integrity: sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw==} + + '@tauri-apps/cli-darwin-arm64@2.10.0': + resolution: {integrity: sha512-avqHD4HRjrMamE/7R/kzJPcAJnZs0IIS+1nkDP5b+TNBn3py7N2aIo9LIpy+VQq0AkN8G5dDpZtOOBkmWt/zjA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tauri-apps/cli-darwin-x64@2.10.0': + resolution: {integrity: sha512-keDmlvJRStzVFjZTd0xYkBONLtgBC9eMTpmXnBXzsHuawV2q9PvDo2x6D5mhuoMVrJ9QWjgaPKBBCFks4dK71Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tauri-apps/cli-linux-arm-gnueabihf@2.10.0': + resolution: {integrity: sha512-e5u0VfLZsMAC9iHaOEANumgl6lfnJx0Dtjkd8IJpysZ8jp0tJ6wrIkto2OzQgzcYyRCKgX72aKE0PFgZputA8g==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tauri-apps/cli-linux-arm64-gnu@2.10.0': + resolution: {integrity: sha512-YrYYk2dfmBs5m+OIMCrb+JH/oo+4FtlpcrTCgiFYc7vcs6m3QDd1TTyWu0u01ewsCtK2kOdluhr/zKku+KP7HA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tauri-apps/cli-linux-arm64-musl@2.10.0': + resolution: {integrity: sha512-GUoPdVJmrJRIXFfW3Rkt+eGK9ygOdyISACZfC/bCSfOnGt8kNdQIQr5WRH9QUaTVFIwxMlQyV3m+yXYP+xhSVA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tauri-apps/cli-linux-riscv64-gnu@2.10.0': + resolution: {integrity: sha512-JO7s3TlSxshwsoKNCDkyvsx5gw2QAs/Y2GbR5UE2d5kkU138ATKoPOtxn8G1fFT1aDW4LH0rYAAfBpGkDyJJnw==} + engines: {node: '>= 10'} + cpu: [riscv64] + os: [linux] + + '@tauri-apps/cli-linux-x64-gnu@2.10.0': + resolution: {integrity: sha512-Uvh4SUUp4A6DVRSMWjelww0GnZI3PlVy7VS+DRF5napKuIehVjGl9XD0uKoCoxwAQBLctvipyEK+pDXpJeoHng==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tauri-apps/cli-linux-x64-musl@2.10.0': + resolution: {integrity: sha512-AP0KRK6bJuTpQ8kMNWvhIpKUkQJfcPFeba7QshOQZjJ8wOS6emwTN4K5g/d3AbCMo0RRdnZWwu67MlmtJyxC1Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tauri-apps/cli-win32-arm64-msvc@2.10.0': + resolution: {integrity: sha512-97DXVU3dJystrq7W41IX+82JEorLNY+3+ECYxvXWqkq7DBN6FsA08x/EFGE8N/b0LTOui9X2dvpGGoeZKKV08g==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tauri-apps/cli-win32-ia32-msvc@2.10.0': + resolution: {integrity: sha512-EHyQ1iwrWy1CwMalEm9z2a6L5isQ121pe7FcA2xe4VWMJp+GHSDDGvbTv/OPdkt2Lyr7DAZBpZHM6nvlHXEc4A==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@tauri-apps/cli-win32-x64-msvc@2.10.0': + resolution: {integrity: sha512-NTpyQxkpzGmU6ceWBTY2xRIEaS0ZLbVx1HE1zTA3TY/pV3+cPoPPOs+7YScr4IMzXMtOw7tLw5LEXo5oIG3qaQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tauri-apps/cli@2.10.0': + resolution: {integrity: sha512-ZwT0T+7bw4+DPCSWzmviwq5XbXlM0cNoleDKOYPFYqcZqeKY31KlpoMW/MOON/tOFBPgi31a2v3w9gliqwL2+Q==} + engines: {node: '>= 10'} + hasBin: true + + '@tauri-apps/plugin-shell@2.3.5': + resolution: {integrity: sha512-jewtULhiQ7lI7+owCKAjc8tYLJr92U16bPOeAa472LHJdgaibLP83NcfAF2e+wkEcA53FxKQAZ7byDzs2eeizg==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} + peerDependencies: + '@types/react': ^18.0.0 + + '@types/react@18.3.28': + resolution: {integrity: sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==} + + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + + autoprefixer@10.4.24: + resolution: {integrity: sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + baseline-browser-mapping@2.10.0: + resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==} + engines: {node: '>=6.0.0'} + hasBin: true + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + caniuse-lite@1.0.30001770: + resolution: {integrity: sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + electron-to-chromium@1.5.302: + resolution: {integrity: sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lucide-react@0.575.0: + resolution: {integrity: sha512-VuXgKZrk0uiDlWjGGXmKV6MSk9Yy4l10qgVvzGn2AWBx1Ylt0iBexKOAoA6I7JO3m+M9oeovJd3yYENfkUbOeg==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.1.0: + resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-router-dom@6.30.3: + resolution: {integrity: sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + + react-router@6.30.3: + resolution: {integrity: sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rollup@4.58.0: + resolution: {integrity: sha512-wbT0mBmWbIvvq8NeEYWWvevvxnOyhKChir47S66WCxw1SXqhw7ssIYejnQEVt7XYQpsj2y8F9PM+Cr3SNEa0gw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + tailwindcss@3.4.19: + resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==} + engines: {node: '>=14.0.0'} + hasBin: true + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + zustand@5.0.11: + resolution: {integrity: sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + + '@babel/parser@7.29.0': + dependencies: + '@babel/types': 7.29.0 + + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@floating-ui/core@1.7.4': + dependencies: + '@floating-ui/utils': 0.2.10 + + '@floating-ui/dom@1.7.5': + dependencies: + '@floating-ui/core': 1.7.4 + '@floating-ui/utils': 0.2.10 + + '@floating-ui/react-dom@2.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/dom': 1.7.5 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@floating-ui/utils@0.2.10': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@phosphor-icons/react@2.1.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@radix-ui/number@1.1.1': {} + + '@radix-ui/primitive@1.1.3': {} + + '@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-collection@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-compose-refs@1.1.2(@types/react@18.3.28)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-context@1.1.2(@types/react@18.3.28)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-context@1.1.3(@types/react@18.3.28)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-direction@1.1.1(@types/react@18.3.28)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-focus-guards@1.1.3(@types/react@18.3.28)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-id@1.1.1(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-menu@2.1.16(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + aria-hidden: 1.2.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.7.2(@types/react@18.3.28)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-popper@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/react-dom': 2.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-rect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/rect': 1.1.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-portal@1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-presence@1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-primitive@2.1.4(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-slot': 1.2.4(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-progress@1.1.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-context': 1.1.3(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-slot@1.2.3(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-slot@1.2.4(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@18.3.28)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@18.3.28)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@18.3.28)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-use-rect@1.1.1(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-use-size@1.1.1(@types/react@18.3.28)(react@18.3.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/rect@1.1.1': {} + + '@remix-run/router@1.23.2': {} + + '@rolldown/pluginutils@1.0.0-beta.27': {} + + '@rollup/rollup-android-arm-eabi@4.58.0': + optional: true + + '@rollup/rollup-android-arm64@4.58.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.58.0': + optional: true + + '@rollup/rollup-darwin-x64@4.58.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.58.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.58.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.58.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.58.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.58.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.58.0': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.58.0': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.58.0': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.58.0': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.58.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.58.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.58.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.58.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.58.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.58.0': + optional: true + + '@rollup/rollup-openbsd-x64@4.58.0': + optional: true + + '@rollup/rollup-openharmony-arm64@4.58.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.58.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.58.0': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.58.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.58.0': + optional: true + + '@tauri-apps/api@2.10.1': {} + + '@tauri-apps/cli-darwin-arm64@2.10.0': + optional: true + + '@tauri-apps/cli-darwin-x64@2.10.0': + optional: true + + '@tauri-apps/cli-linux-arm-gnueabihf@2.10.0': + optional: true + + '@tauri-apps/cli-linux-arm64-gnu@2.10.0': + optional: true + + '@tauri-apps/cli-linux-arm64-musl@2.10.0': + optional: true + + '@tauri-apps/cli-linux-riscv64-gnu@2.10.0': + optional: true + + '@tauri-apps/cli-linux-x64-gnu@2.10.0': + optional: true + + '@tauri-apps/cli-linux-x64-musl@2.10.0': + optional: true + + '@tauri-apps/cli-win32-arm64-msvc@2.10.0': + optional: true + + '@tauri-apps/cli-win32-ia32-msvc@2.10.0': + optional: true + + '@tauri-apps/cli-win32-x64-msvc@2.10.0': + optional: true + + '@tauri-apps/cli@2.10.0': + 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 + + '@tauri-apps/plugin-shell@2.3.5': + dependencies: + '@tauri-apps/api': 2.10.1 + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/estree@1.0.8': {} + + '@types/prop-types@15.7.15': {} + + '@types/react-dom@18.3.7(@types/react@18.3.28)': + dependencies: + '@types/react': 18.3.28 + + '@types/react@18.3.28': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.2.3 + + '@vitejs/plugin-react@4.7.0(vite@5.4.21)': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 5.4.21 + transitivePeerDependencies: + - supports-color + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + arg@5.0.2: {} + + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + + autoprefixer@10.4.24(postcss@8.5.6): + dependencies: + browserslist: 4.28.1 + caniuse-lite: 1.0.30001770 + fraction.js: 5.3.4 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + baseline-browser-mapping@2.10.0: {} + + binary-extensions@2.3.0: {} + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.10.0 + caniuse-lite: 1.0.30001770 + electron-to-chromium: 1.5.302 + node-releases: 2.0.27 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + + camelcase-css@2.0.1: {} + + caniuse-lite@1.0.30001770: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + clsx@2.1.1: {} + + commander@4.1.1: {} + + convert-source-map@2.0.0: {} + + cssesc@3.0.0: {} + + csstype@3.2.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + detect-node-es@1.1.0: {} + + didyoumean@1.2.2: {} + + dlv@1.1.3: {} + + electron-to-chromium@1.5.302: {} + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + escalade@3.2.0: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + fraction.js@5.3.4: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + get-nonce@1.0.1: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + jiti@1.21.7: {} + + js-tokens@4.0.0: {} + + jsesc@3.1.0: {} + + json5@2.2.3: {} + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucide-react@0.575.0(react@18.3.1): + dependencies: + react: 18.3.1 + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.11: {} + + node-releases@2.0.27: {} + + normalize-path@3.0.0: {} + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + path-parse@1.0.7: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pify@2.3.0: {} + + pirates@4.0.7: {} + + postcss-import@15.1.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.11 + + postcss-js@4.1.0(postcss@8.5.6): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.5.6 + + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + jiti: 1.21.7 + postcss: 8.5.6 + + postcss-nested@6.2.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + queue-microtask@1.2.3: {} + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-refresh@0.17.0: {} + + react-remove-scroll-bar@2.3.8(@types/react@18.3.28)(react@18.3.1): + dependencies: + react: 18.3.1 + react-style-singleton: 2.2.3(@types/react@18.3.28)(react@18.3.1) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.28 + + react-remove-scroll@2.7.2(@types/react@18.3.28)(react@18.3.1): + dependencies: + react: 18.3.1 + react-remove-scroll-bar: 2.3.8(@types/react@18.3.28)(react@18.3.1) + react-style-singleton: 2.2.3(@types/react@18.3.28)(react@18.3.1) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@18.3.28)(react@18.3.1) + use-sidecar: 1.1.3(@types/react@18.3.28)(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + + react-router-dom@6.30.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@remix-run/router': 1.23.2 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-router: 6.30.3(react@18.3.1) + + react-router@6.30.3(react@18.3.1): + dependencies: + '@remix-run/router': 1.23.2 + react: 18.3.1 + + react-style-singleton@2.2.3(@types/react@18.3.28)(react@18.3.1): + dependencies: + get-nonce: 1.0.1 + react: 18.3.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.28 + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.1.0: {} + + rollup@4.58.0: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.58.0 + '@rollup/rollup-android-arm64': 4.58.0 + '@rollup/rollup-darwin-arm64': 4.58.0 + '@rollup/rollup-darwin-x64': 4.58.0 + '@rollup/rollup-freebsd-arm64': 4.58.0 + '@rollup/rollup-freebsd-x64': 4.58.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.58.0 + '@rollup/rollup-linux-arm-musleabihf': 4.58.0 + '@rollup/rollup-linux-arm64-gnu': 4.58.0 + '@rollup/rollup-linux-arm64-musl': 4.58.0 + '@rollup/rollup-linux-loong64-gnu': 4.58.0 + '@rollup/rollup-linux-loong64-musl': 4.58.0 + '@rollup/rollup-linux-ppc64-gnu': 4.58.0 + '@rollup/rollup-linux-ppc64-musl': 4.58.0 + '@rollup/rollup-linux-riscv64-gnu': 4.58.0 + '@rollup/rollup-linux-riscv64-musl': 4.58.0 + '@rollup/rollup-linux-s390x-gnu': 4.58.0 + '@rollup/rollup-linux-x64-gnu': 4.58.0 + '@rollup/rollup-linux-x64-musl': 4.58.0 + '@rollup/rollup-openbsd-x64': 4.58.0 + '@rollup/rollup-openharmony-arm64': 4.58.0 + '@rollup/rollup-win32-arm64-msvc': 4.58.0 + '@rollup/rollup-win32-ia32-msvc': 4.58.0 + '@rollup/rollup-win32-x64-gnu': 4.58.0 + '@rollup/rollup-win32-x64-msvc': 4.58.0 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + semver@6.3.1: {} + + source-map-js@1.2.1: {} + + sucrase@3.35.1: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.15 + ts-interface-checker: 0.1.13 + + supports-preserve-symlinks-flag@1.0.0: {} + + tailwindcss@3.4.19: + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.3 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 3.1.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-import: 15.1.0(postcss@8.5.6) + postcss-js: 4.1.0(postcss@8.5.6) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6) + postcss-nested: 6.2.0(postcss@8.5.6) + postcss-selector-parser: 6.1.2 + resolve: 1.22.11 + sucrase: 3.35.1 + transitivePeerDependencies: + - tsx + - yaml + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + ts-interface-checker@0.1.13: {} + + tslib@2.8.1: {} + + typescript@5.9.3: {} + + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + use-callback-ref@1.3.3(@types/react@18.3.28)(react@18.3.1): + dependencies: + react: 18.3.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.28 + + use-sidecar@1.1.3(@types/react@18.3.28)(react@18.3.1): + dependencies: + detect-node-es: 1.1.0 + react: 18.3.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.28 + + util-deprecate@1.0.2: {} + + vite@5.4.21: + dependencies: + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.58.0 + optionalDependencies: + fsevents: 2.3.3 + + yallist@3.1.1: {} + + zustand@5.0.11(@types/react@18.3.28)(react@18.3.1): + optionalDependencies: + '@types/react': 18.3.28 + react: 18.3.1 diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock new file mode 100644 index 0000000..c85ba53 --- /dev/null +++ b/src-tauri/Cargo.lock @@ -0,0 +1,4933 @@ +# 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 = "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.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[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 = "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.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[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 = "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.117", +] + +[[package]] +name = "ctor" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[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.117", +] + +[[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.117", +] + +[[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.117", +] + +[[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.117", +] + +[[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.117", +] + +[[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 = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[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 = "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.117", +] + +[[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.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "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.117", +] + +[[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.117", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[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 = "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 = "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 = "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.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d36139f1c97c42c0c86a411910b04e48d4939a0376e6e0f989420cbdee0120e5" +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 = "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.117", +] + +[[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 = "moku" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-shell", +] + +[[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 = "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.117", +] + +[[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 = "os_pipe" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[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_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.117", +] + +[[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 = "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 = "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.117", +] + +[[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.117", +] + +[[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 = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[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.117", +] + +[[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.117", +] + +[[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.117", +] + +[[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.117", +] + +[[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.117", +] + +[[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.117", +] + +[[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 = "shared_child" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7" +dependencies = [ + "libc", + "sigchld", + "windows-sys 0.60.2", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "sigchld" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1" +dependencies = [ + "libc", + "os_pipe", + "signal-hook", +] + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[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 = "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.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +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.117", +] + +[[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.117", +] + +[[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", + "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.117", + "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.117", + "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-shell" +version = "2.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8457dbf9e2bab1edd8df22bb2c20857a59a9868e79cb3eac5ed639eec4d0c73b" +dependencies = [ + "encoding_rs", + "log", + "open", + "os_pipe", + "regex", + "schemars 0.8.22", + "serde", + "serde_json", + "shared_child", + "tauri", + "tauri-plugin", + "thiserror 2.0.18", + "tokio", +] + +[[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 = "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.117", +] + +[[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.117", +] + +[[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.9+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" +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-core", +] + +[[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 = "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.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[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 = "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_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 = "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.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ff9c7baef35ac3c0e17d8bfc9ad75eb62f85a2f02bccc906699dadb0aa9c622" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24699cd39db9966cf6e2ef10d2f72779c961ad905911f395ea201c3ec9f545d" +dependencies = [ + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39455e84ad887a0bbc93c116d72403f1bb0a39e37dd6f235a43e2128a0c7f1fd" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff4761f60b0b51fd13fec8764167b7bbcc34498ce3e52805fe1db6f2d56b6d6" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6a171c53d98021a93a474c4a4579d76ba97f9517d871bc12e27640f218b6dd" +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.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668fa5d00434e890a452ab060d24e3904d1be93f7bb01b70e5603baa2b8ab23b" +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 = "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.117", +] + +[[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.117", +] + +[[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.117", +] + +[[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.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.117", + "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.117", + "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.117", + "synstructure", +] + +[[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.117", +] + +[[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.117", + "synstructure", +] + +[[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.117", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml new file mode 100644 index 0000000..229c1fb --- /dev/null +++ b/src-tauri/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "moku" +version = "0.1.0" +edition = "2021" + +[lib] +name = "moku_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + +[[bin]] +name = "moku" +path = "src/main.rs" + +[build-dependencies] +tauri-build = { version = "2.0", features = [] } + +[dependencies] +tauri = { version = "2.0", features = [] } +tauri-plugin-shell = "2" +serde = { version = "1", features = ["derive"] } +serde_json = "1" + +[profile.release] +codegen-units = 1 +lto = true +opt-level = "s" +panic = "abort" +strip = true diff --git a/src-tauri/build.rs b/src-tauri/build.rs new file mode 100644 index 0000000..82d481c --- /dev/null +++ b/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} \ No newline at end of file diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json new file mode 100644 index 0000000..a45c367 --- /dev/null +++ b/src-tauri/capabilities/default.json @@ -0,0 +1,19 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Allow launching tachidesk-server", + "windows": ["main"], + "permissions": [ + "core:default", + "shell:allow-open", + { + "identifier": "shell:allow-spawn", + "allow": [ + { + "name": "tachidesk-server", + "cmd": "tachidesk-server" + } + ] + } + ] +} \ No newline at end of file diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png new file mode 100644 index 0000000..b2b2bae Binary files /dev/null and b/src-tauri/icons/128x128.png differ diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png new file mode 100644 index 0000000..59365a7 Binary files /dev/null and b/src-tauri/icons/128x128@2x.png differ diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png new file mode 100644 index 0000000..aaad22b Binary files /dev/null and b/src-tauri/icons/32x32.png differ diff --git a/src-tauri/icons/64x64.png b/src-tauri/icons/64x64.png new file mode 100644 index 0000000..0b6a9cd Binary files /dev/null and b/src-tauri/icons/64x64.png differ diff --git a/src-tauri/icons/Square107x107Logo.png b/src-tauri/icons/Square107x107Logo.png new file mode 100644 index 0000000..eb00fcb Binary files /dev/null and b/src-tauri/icons/Square107x107Logo.png differ diff --git a/src-tauri/icons/Square142x142Logo.png b/src-tauri/icons/Square142x142Logo.png new file mode 100644 index 0000000..88abb37 Binary files /dev/null and b/src-tauri/icons/Square142x142Logo.png differ diff --git a/src-tauri/icons/Square150x150Logo.png b/src-tauri/icons/Square150x150Logo.png new file mode 100644 index 0000000..4b3c956 Binary files /dev/null and b/src-tauri/icons/Square150x150Logo.png differ diff --git a/src-tauri/icons/Square284x284Logo.png b/src-tauri/icons/Square284x284Logo.png new file mode 100644 index 0000000..eeb8903 Binary files /dev/null and b/src-tauri/icons/Square284x284Logo.png differ diff --git a/src-tauri/icons/Square30x30Logo.png b/src-tauri/icons/Square30x30Logo.png new file mode 100644 index 0000000..da113eb Binary files /dev/null and b/src-tauri/icons/Square30x30Logo.png differ diff --git a/src-tauri/icons/Square310x310Logo.png b/src-tauri/icons/Square310x310Logo.png new file mode 100644 index 0000000..77d4c2e Binary files /dev/null and b/src-tauri/icons/Square310x310Logo.png differ diff --git a/src-tauri/icons/Square44x44Logo.png b/src-tauri/icons/Square44x44Logo.png new file mode 100644 index 0000000..9cef848 Binary files /dev/null and b/src-tauri/icons/Square44x44Logo.png differ diff --git a/src-tauri/icons/Square71x71Logo.png b/src-tauri/icons/Square71x71Logo.png new file mode 100644 index 0000000..09aaf2a Binary files /dev/null and b/src-tauri/icons/Square71x71Logo.png differ diff --git a/src-tauri/icons/Square89x89Logo.png b/src-tauri/icons/Square89x89Logo.png new file mode 100644 index 0000000..b17b33f Binary files /dev/null and b/src-tauri/icons/Square89x89Logo.png differ diff --git a/src-tauri/icons/StoreLogo.png b/src-tauri/icons/StoreLogo.png new file mode 100644 index 0000000..d972a07 Binary files /dev/null and b/src-tauri/icons/StoreLogo.png differ diff --git a/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml b/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..2ffbf24 --- /dev/null +++ b/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..14d2123 Binary files /dev/null and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..6e40136 Binary files /dev/null and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..a30f348 Binary files /dev/null and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..a03036a Binary files /dev/null and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..f09ce47 Binary files /dev/null and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..642780b Binary files /dev/null and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..26c0293 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..3c9f7fa Binary files /dev/null and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..ebaa520 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..50ad721 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..6fd630b Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..49270df Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..724879f Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..8957c13 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..e3b1135 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/values/ic_launcher_background.xml b/src-tauri/icons/android/values/ic_launcher_background.xml new file mode 100644 index 0000000..ea9c223 --- /dev/null +++ b/src-tauri/icons/android/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #fff + \ No newline at end of file diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns new file mode 100644 index 0000000..0bf6444 Binary files /dev/null and b/src-tauri/icons/icon.icns differ diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico new file mode 100644 index 0000000..af8bd84 Binary files /dev/null and b/src-tauri/icons/icon.ico differ diff --git a/src-tauri/icons/icon.png b/src-tauri/icons/icon.png new file mode 100644 index 0000000..7796e0e Binary files /dev/null and b/src-tauri/icons/icon.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@1x.png b/src-tauri/icons/ios/AppIcon-20x20@1x.png new file mode 100644 index 0000000..983a6a2 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@2x-1.png b/src-tauri/icons/ios/AppIcon-20x20@2x-1.png new file mode 100644 index 0000000..36dd0f2 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@2x-1.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@2x.png b/src-tauri/icons/ios/AppIcon-20x20@2x.png new file mode 100644 index 0000000..36dd0f2 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@3x.png b/src-tauri/icons/ios/AppIcon-20x20@3x.png new file mode 100644 index 0000000..fc94be6 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@1x.png b/src-tauri/icons/ios/AppIcon-29x29@1x.png new file mode 100644 index 0000000..f34584f Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@2x-1.png b/src-tauri/icons/ios/AppIcon-29x29@2x-1.png new file mode 100644 index 0000000..9779412 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@2x-1.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@2x.png b/src-tauri/icons/ios/AppIcon-29x29@2x.png new file mode 100644 index 0000000..9779412 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@3x.png b/src-tauri/icons/ios/AppIcon-29x29@3x.png new file mode 100644 index 0000000..7c668c6 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@1x.png b/src-tauri/icons/ios/AppIcon-40x40@1x.png new file mode 100644 index 0000000..36dd0f2 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@2x-1.png b/src-tauri/icons/ios/AppIcon-40x40@2x-1.png new file mode 100644 index 0000000..7cc327a Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@2x-1.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@2x.png b/src-tauri/icons/ios/AppIcon-40x40@2x.png new file mode 100644 index 0000000..7cc327a Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@3x.png b/src-tauri/icons/ios/AppIcon-40x40@3x.png new file mode 100644 index 0000000..3c15d3c Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-512@2x.png b/src-tauri/icons/ios/AppIcon-512@2x.png new file mode 100644 index 0000000..3e3e73c Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-512@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-60x60@2x.png b/src-tauri/icons/ios/AppIcon-60x60@2x.png new file mode 100644 index 0000000..3c15d3c Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-60x60@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-60x60@3x.png b/src-tauri/icons/ios/AppIcon-60x60@3x.png new file mode 100644 index 0000000..6fea863 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-60x60@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-76x76@1x.png b/src-tauri/icons/ios/AppIcon-76x76@1x.png new file mode 100644 index 0000000..bdf86ed Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-76x76@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-76x76@2x.png b/src-tauri/icons/ios/AppIcon-76x76@2x.png new file mode 100644 index 0000000..163db88 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-76x76@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png b/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png new file mode 100644 index 0000000..eca72fc Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png differ diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs new file mode 100644 index 0000000..52354b1 --- /dev/null +++ b/src-tauri/src/lib.rs @@ -0,0 +1,34 @@ +use std::sync::Mutex; +use tauri::Manager; +use tauri_plugin_shell::{ShellExt, process::CommandChild}; + +struct ServerState(Mutex>); + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + tauri::Builder::default() + .plugin(tauri_plugin_shell::init()) + .manage(ServerState(Mutex::new(None))) + .setup(|app| { + let shell = app.shell(); + let app_handle = app.handle().clone(); + + let status = shell.command("tachidesk-server").spawn(); + + match status { + Ok((_rx, child)) => { + println!("Tachidesk server process spawned successfully."); + let state = app_handle.state::(); + let mut guard = state.0.lock().unwrap(); + *guard = Some(child); + } + Err(e) => { + eprintln!("Failed to spawn Tachidesk server: {}", e); + } + } + + Ok(()) + }) + .run(tauri::generate_context!()) + .expect("error while running moku"); +} \ No newline at end of file diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs new file mode 100644 index 0000000..25c98f4 --- /dev/null +++ b/src-tauri/src/main.rs @@ -0,0 +1,5 @@ +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + moku_lib::run(); +} \ No newline at end of file diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json new file mode 100644 index 0000000..e05174e --- /dev/null +++ b/src-tauri/tauri.conf.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "Moku", + "version": "0.1.0", + "identifier": "dev.moku.app", + "build": { + "frontendDist": "../dist", + "beforeBuildCommand": "pnpm build" + }, + "app": { + "windows": [ + { + "title": "Moku", + "width": 1280, + "height": 800, + "minWidth": 800, + "minHeight": 600, + "resizable": true, + "fullscreen": false, + "decorations": false + } + ], + "security": { + "csp": null + } + }, + "bundle": { + "active": true, + "targets": "all", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ] + }, + "plugins": { + "shell": { + "open": true + } + } +} \ No newline at end of file diff --git a/src/App.module.css b/src/App.module.css new file mode 100644 index 0000000..33b8920 --- /dev/null +++ b/src/App.module.css @@ -0,0 +1,12 @@ +.root { + display: flex; + flex-direction: column; + height: 100%; + overflow: hidden; +} + +.content { + flex: 1; + overflow: hidden; + min-height: 0; +} \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..26e6bc6 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,54 @@ +import { useEffect } from "react"; +import { invoke } from "@tauri-apps/api/core"; +import { listen } from "@tauri-apps/api/event"; +import "./styles/global.css"; +import { useStore } from "./store"; +import Layout from "./components/layout/Layout"; +import Reader from "./components/pages/Reader"; +import Settings from "./components/settings/Settings"; +import TitleBar from "./components/layout/TitleBar"; +import s from "./App.module.css"; + +export default function App() { + const activeChapter = useStore((s) => s.activeChapter); + const settingsOpen = useStore((s) => s.settingsOpen); + const settings = useStore((s) => s.settings); + const setActiveDownloads = useStore((s) => s.setActiveDownloads); + + useEffect(() => { + document.documentElement.style.zoom = `${settings.uiScale}%`; + }, [settings.uiScale]); + + useEffect(() => { + const prevent = (e: MouseEvent) => e.preventDefault(); + document.addEventListener("contextmenu", prevent); + return () => document.removeEventListener("contextmenu", prevent); + }, []); + + useEffect(() => { + if (!settings.autoStartServer) return; + invoke("spawn_server", { binary: settings.serverBinary }).catch((err) => + console.warn("Could not start server:", err) + ); + return () => { invoke("kill_server").catch(() => {}); }; + }, [settings.autoStartServer, settings.serverBinary]); + + // Global Tauri download-progress listener — no polling, always current + useEffect(() => { + type DlPayload = { chapterId: number; mangaId: number; progress: number }[]; + const unsub = listen("download-progress", (e) => { + setActiveDownloads(e.payload); + }); + return () => { unsub.then((fn) => fn()); }; + }, [setActiveDownloads]); + + return ( +
+ {!activeChapter && } +
+ {activeChapter ? : } +
+ {settingsOpen && } +
+ ); +} \ No newline at end of file diff --git a/src/assets/Moku-Icon.svg b/src/assets/Moku-Icon.svg new file mode 100644 index 0000000..cf3e706 --- /dev/null +++ b/src/assets/Moku-Icon.svg @@ -0,0 +1,27 @@ + + + + + + + diff --git a/src/assets/logo.png b/src/assets/logo.png new file mode 100644 index 0000000..b2b2bae Binary files /dev/null and b/src/assets/logo.png differ diff --git a/src/components/context/ContextMenu.module.css b/src/components/context/ContextMenu.module.css new file mode 100644 index 0000000..fd630ea --- /dev/null +++ b/src/components/context/ContextMenu.module.css @@ -0,0 +1,55 @@ +.menu { + position: fixed; + z-index: 200; + background: var(--bg-raised); + border: 1px solid var(--border-base); + border-radius: var(--radius-lg); + padding: var(--sp-1); + min-width: 180px; + box-shadow: + 0 4px 16px rgba(0, 0, 0, 0.5), + 0 1px 4px rgba(0, 0, 0, 0.3); + animation: scaleIn 0.1s ease both; + transform-origin: top left; +} + +.item { + display: flex; + align-items: center; + gap: var(--sp-2); + width: 100%; + padding: 6px var(--sp-3); + border-radius: var(--radius-md); + font-size: var(--text-sm); + color: var(--text-secondary); + text-align: left; + cursor: pointer; + transition: background var(--t-fast), color var(--t-fast); + border: none; + background: none; +} + +.item:hover:not(:disabled) { + background: var(--bg-overlay); + color: var(--text-primary); +} + +.itemDanger { color: var(--color-error); } +.itemDanger:hover:not(:disabled) { background: var(--color-error-bg); color: var(--color-error); } + +.itemDisabled { opacity: 0.35; cursor: default; } + +.itemIcon { + display: flex; + align-items: center; + color: inherit; + flex-shrink: 0; +} + +.itemLabel { flex: 1; } + +.separator { + height: 1px; + background: var(--border-dim); + margin: var(--sp-1) var(--sp-2); +} \ No newline at end of file diff --git a/src/components/context/ContextMenu.tsx b/src/components/context/ContextMenu.tsx new file mode 100644 index 0000000..2811baf --- /dev/null +++ b/src/components/context/ContextMenu.tsx @@ -0,0 +1,90 @@ +import { useEffect, useRef, useCallback } from "react"; +import { createPortal } from "react-dom"; +import s from "./ContextMenu.module.css"; + +export interface ContextMenuItem { + label: string; + icon?: React.ReactNode; + onClick: () => void; + danger?: boolean; + disabled?: boolean; + separator?: never; +} + +export interface ContextMenuSeparator { + separator: true; + label?: never; + icon?: never; + onClick?: never; + danger?: never; + disabled?: never; +} + +export type ContextMenuEntry = ContextMenuItem | ContextMenuSeparator; + +interface Props { + x: number; + y: number; + items: ContextMenuEntry[]; + onClose: () => void; +} + +export default function ContextMenu({ x, y, items, onClose }: Props) { + const menuRef = useRef(null); + + // Close on outside click or Escape + useEffect(() => { + function onDown(e: MouseEvent) { + if (menuRef.current && !menuRef.current.contains(e.target as Node)) { + onClose(); + } + } + function onKey(e: KeyboardEvent) { + if (e.key === "Escape") onClose(); + } + // Use capture so we intercept before other handlers + document.addEventListener("mousedown", onDown, true); + document.addEventListener("keydown", onKey, true); + return () => { + document.removeEventListener("mousedown", onDown, true); + document.removeEventListener("keydown", onKey, true); + }; + }, [onClose]); + + // Adjust position so menu doesn't clip outside viewport + const style = useCallback(() => { + const menuW = 200; + const menuH = items.length * 32; + const left = x + menuW > window.innerWidth ? x - menuW : x; + const top = y + menuH > window.innerHeight ? y - menuH : y; + return { left, top }; + }, [x, y, items.length]); + + return createPortal( +
e.preventDefault()} + > + {items.map((item, i) => { + if ("separator" in item && item.separator) { + return
; + } + const mi = item as ContextMenuItem; + return ( + + ); + })} +
, + document.body + ); +} \ No newline at end of file diff --git a/src/components/downloads/DownloadQueue.module.css b/src/components/downloads/DownloadQueue.module.css new file mode 100644 index 0000000..75942a2 --- /dev/null +++ b/src/components/downloads/DownloadQueue.module.css @@ -0,0 +1,200 @@ +.root { + padding: var(--sp-6); + overflow-y: auto; + height: 100%; + animation: fadeIn 0.14s ease both; +} + +.header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--sp-5); +} + +.heading { + font-family: var(--font-ui); + font-size: var(--text-xs); + font-weight: var(--weight-normal); + color: var(--text-faint); + letter-spacing: var(--tracking-wider); + text-transform: uppercase; +} + +.headerActions { display: flex; gap: var(--sp-2); } + +.iconBtn { + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border-radius: var(--radius-md); + border: 1px solid var(--border-dim); + color: var(--text-muted); + transition: color var(--t-base), border-color var(--t-base), background var(--t-base); +} + +.iconBtn:hover { color: var(--text-secondary); border-color: var(--border-strong); background: var(--bg-raised); } +.iconBtn:disabled { opacity: 0.3; cursor: default; } + +.statusBar { + display: flex; + align-items: center; + gap: var(--sp-3); + padding: var(--sp-3); + background: var(--bg-raised); + border: 1px solid var(--border-dim); + border-radius: var(--radius-md); + margin-bottom: var(--sp-4); +} + +.statusDot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--text-faint); + flex-shrink: 0; +} + +.statusDotActive { + background: var(--accent); + animation: pulse 1.6s ease infinite; +} + +.statusText { + font-family: var(--font-ui); + font-size: var(--text-xs); + color: var(--text-muted); + flex: 1; + letter-spacing: var(--tracking-wide); +} + +.statusCount { + font-family: var(--font-ui); + font-size: var(--text-xs); + color: var(--text-faint); + letter-spacing: var(--tracking-wide); +} + +.list { display: flex; flex-direction: column; gap: var(--sp-2); } + +.row { + display: flex; + align-items: center; + gap: var(--sp-3); + padding: var(--sp-3); + background: var(--bg-raised); + border: 1px solid var(--border-dim); + border-radius: var(--radius-md); + transition: border-color var(--t-fast); +} + +.rowActive { border-color: var(--accent-dim); } + +/* Thumbnail */ +.thumb { + width: 36px; + height: 54px; + border-radius: var(--radius-sm); + overflow: hidden; + background: var(--bg-overlay); + flex-shrink: 0; + border: 1px solid var(--border-dim); +} + +.thumbImg { + width: 100%; + height: 100%; + object-fit: cover; +} + +/* Info block */ +.info { + flex: 1; + display: flex; + flex-direction: column; + gap: 3px; + overflow: hidden; + min-width: 0; +} + +.mangaTitle { + font-size: var(--text-sm); + font-weight: var(--weight-medium); + color: var(--text-secondary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.chapterName { + font-size: var(--text-xs); + color: var(--text-muted); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.pagesLabel { + font-family: var(--font-ui); + font-size: var(--text-2xs); + color: var(--text-faint); + letter-spacing: var(--tracking-wide); +} + +.progressWrap { + height: 2px; + background: var(--border-base); + border-radius: var(--radius-full); + overflow: hidden; + margin-top: 4px; +} + +.progressBar { + height: 100%; + background: var(--accent); + border-radius: var(--radius-full); + transition: width 0.4s ease; +} + +/* Right side */ +.rowRight { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: var(--sp-1); + flex-shrink: 0; +} + +.stateLabel { + font-family: var(--font-ui); + font-size: var(--text-2xs); + color: var(--text-faint); + letter-spacing: var(--tracking-wider); + text-transform: uppercase; +} + +.removeBtn { + display: flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + border-radius: var(--radius-sm); + color: var(--text-faint); + transition: color var(--t-base), background var(--t-base); +} + +.removeBtn:hover { color: var(--color-error); background: var(--color-error-bg); } + +.empty { + display: flex; + align-items: center; + justify-content: center; + height: 160px; + color: var(--text-faint); + font-family: var(--font-ui); + font-size: var(--text-xs); + letter-spacing: var(--tracking-wide); +} \ No newline at end of file diff --git a/src/components/downloads/DownloadQueue.tsx b/src/components/downloads/DownloadQueue.tsx new file mode 100644 index 0000000..982fa28 --- /dev/null +++ b/src/components/downloads/DownloadQueue.tsx @@ -0,0 +1,152 @@ +import { useEffect, useState } from "react"; +import { Play, Pause, Trash, CircleNotch, X } from "@phosphor-icons/react"; +import { gql, thumbUrl } from "../../lib/client"; +import { + GET_DOWNLOAD_STATUS, START_DOWNLOADER, STOP_DOWNLOADER, + CLEAR_DOWNLOADER, DEQUEUE_DOWNLOAD, +} from "../../lib/queries"; +import { useStore } from "../../store"; +import type { DownloadStatus } from "../../lib/types"; +import s from "./DownloadQueue.module.css"; + +export default function DownloadQueue() { + const [status, setStatus] = useState(null); + const [loading, setLoading] = useState(true); + const setActiveDownloads = useStore((s) => s.setActiveDownloads); + + async function poll() { + gql<{ downloadStatus: DownloadStatus }>(GET_DOWNLOAD_STATUS) + .then((d) => { + setStatus(d.downloadStatus); + setActiveDownloads( + d.downloadStatus.queue.map((item) => ({ + chapterId: item.chapter.id, + mangaId: item.chapter.mangaId, + progress: item.progress, + })) + ); + }) + .catch(console.error) + .finally(() => setLoading(false)); + } + + useEffect(() => { + poll(); + const id = setInterval(poll, 1500); + return () => clearInterval(id); + }, []); + + async function start() { await gql(START_DOWNLOADER).catch(console.error); poll(); } + async function stop() { await gql(STOP_DOWNLOADER).catch(console.error); poll(); } + async function clear() { await gql(CLEAR_DOWNLOADER).catch(console.error); poll(); } + async function dequeue(chapterId: number) { + await gql(DEQUEUE_DOWNLOAD, { chapterId }).catch(console.error); + poll(); + } + + const queue = status?.queue ?? []; + const isRunning = status?.state === "STARTED"; + + function pagesDownloaded(progress: number, pageCount: number): number { + return Math.round(progress * pageCount); + } + + return ( +
+
+

Downloads

+
+ {isRunning ? ( + + ) : ( + + )} + +
+
+ +
+
+ {isRunning ? "Downloading" : "Paused"} + {queue.length} queued +
+ + {loading ? ( +
+ +
+ ) : queue.length === 0 ? ( +
Queue is empty.
+ ) : ( +
+ {queue.map((item, i) => { + const isActive = i === 0 && isRunning; + const pages = item.chapter.pageCount ?? 0; + const done = pagesDownloaded(item.progress, pages); + const manga = item.chapter.manga; + + return ( +
+ {manga?.thumbnailUrl && ( +
+ {manga.title} +
+ )} + +
+ {manga?.title && ( + {manga.title} + )} + {item.chapter.name} + + {pages > 0 && ( + + {isActive ? `${done} / ${pages} pages` : `${pages} pages`} + + )} + + {isActive && ( +
+
+
+ )} +
+ +
+ {item.state} + {!isActive && ( + + )} +
+
+ ); + })} +
+ )} +
+ ); +} \ No newline at end of file diff --git a/src/components/extensions/ExtensionList.module.css b/src/components/extensions/ExtensionList.module.css new file mode 100644 index 0000000..eb52a43 --- /dev/null +++ b/src/components/extensions/ExtensionList.module.css @@ -0,0 +1,172 @@ +.root { + display: flex; flex-direction: column; height: 100%; + overflow: hidden; animation: fadeIn 0.14s ease both; +} +.header { + display: flex; align-items: center; justify-content: space-between; + padding: var(--sp-5) var(--sp-6) var(--sp-3); flex-shrink: 0; +} +.heading { + font-family: var(--font-ui); font-size: var(--text-xs); font-weight: var(--weight-normal); + color: var(--text-faint); letter-spacing: var(--tracking-wider); text-transform: uppercase; +} +.headerActions { display: flex; gap: var(--sp-1); } +.iconBtn { + display: flex; align-items: center; justify-content: center; + width: 28px; height: 28px; border-radius: var(--radius-md); + color: var(--text-muted); transition: color var(--t-base), background var(--t-base); +} +.iconBtn:hover:not(:disabled) { color: var(--text-primary); background: var(--bg-raised); } +.iconBtn:disabled { opacity: 0.4; } + +.externalRow { + display: flex; gap: var(--sp-2); padding: 0 var(--sp-6) var(--sp-3); flex-shrink: 0; +} +.externalInput { + flex: 1; background: var(--bg-raised); border: 1px solid var(--border-strong); + border-radius: var(--radius-md); padding: 6px var(--sp-3); + color: var(--text-primary); font-size: var(--text-sm); outline: none; +} +.externalInput:focus { border-color: var(--border-focus); } +.installBtn { + font-family: var(--font-ui); font-size: var(--text-xs); letter-spacing: var(--tracking-wide); + padding: 6px 14px; border-radius: var(--radius-md); + background: var(--accent-muted); color: var(--accent-fg); + border: 1px solid var(--accent-dim); cursor: pointer; +} +.installBtn:hover { filter: brightness(1.1); } + +.controls { + display: flex; align-items: center; justify-content: space-between; + padding: 0 var(--sp-6) var(--sp-3); gap: var(--sp-3); flex-shrink: 0; +} +.tabs { display: flex; gap: 2px; } +.tab { + font-family: var(--font-ui); font-size: var(--text-xs); letter-spacing: var(--tracking-wide); + padding: 4px 10px; border-radius: var(--radius-md); border: none; + background: none; color: var(--text-muted); cursor: pointer; + transition: background var(--t-base), color var(--t-base); +} +.tab:hover { background: var(--bg-raised); color: var(--text-secondary); } +.tabActive { background: var(--accent-muted); color: var(--accent-fg); } +.tabActive:hover { background: var(--accent-muted); color: var(--accent-fg); } + +.searchWrap { position: relative; display: flex; align-items: center; } +.searchIcon { position: absolute; left: 9px; color: var(--text-faint); pointer-events: none; } +.search { + background: var(--bg-raised); border: 1px solid var(--border-dim); + border-radius: var(--radius-md); padding: 5px 10px 5px 26px; + color: var(--text-primary); font-size: var(--text-sm); width: 160px; outline: none; + transition: border-color var(--t-base); +} +.search::placeholder { color: var(--text-faint); } +.search:focus { border-color: var(--border-strong); } + +.list { flex: 1; overflow-y: auto; padding: 0 var(--sp-4) var(--sp-4); display: flex; flex-direction: column; gap: 1px; } + +.group { display: flex; flex-direction: column; } + +.row { + display: flex; align-items: center; gap: var(--sp-3); + padding: 8px var(--sp-3); border-radius: var(--radius-md); + border: 1px solid transparent; + transition: background var(--t-fast), border-color var(--t-fast); +} +.row:hover { background: var(--bg-raised); border-color: var(--border-dim); } + +.icon { + width: 32px; height: 32px; border-radius: var(--radius-md); + object-fit: cover; flex-shrink: 0; background: var(--bg-raised); +} +.info { flex: 1; display: flex; flex-direction: column; gap: 2px; overflow: hidden; min-width: 0; } +.name { + font-size: var(--text-base); font-weight: var(--weight-medium); + color: var(--text-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; +} +.meta { + display: flex; align-items: center; gap: var(--sp-2); + font-family: var(--font-ui); font-size: var(--text-2xs); color: var(--text-faint); + letter-spacing: var(--tracking-wide); +} + +.langTag { + background: var(--bg-overlay); border: 1px solid var(--border-dim); + border-radius: var(--radius-sm); padding: 1px 5px; + font-family: var(--font-ui); font-size: var(--text-2xs); + color: var(--text-muted); letter-spacing: var(--tracking-wider); +} +.nsfwTag { + background: transparent; border: 1px solid var(--color-error); + border-radius: var(--radius-sm); padding: 1px 5px; + font-family: var(--font-ui); font-size: var(--text-2xs); + color: var(--color-error); letter-spacing: var(--tracking-wider); +} +.updateBadge { + font-family: var(--font-ui); font-size: var(--text-2xs); letter-spacing: var(--tracking-wide); + background: var(--accent-muted); color: var(--accent-fg); + border: 1px solid var(--accent-dim); border-radius: var(--radius-sm); + padding: 2px 6px; flex-shrink: 0; +} +.updateBadgeSmall { + font-family: var(--font-ui); font-size: var(--text-2xs); + color: var(--accent-fg); flex-shrink: 0; +} + +.rowActions { display: flex; gap: var(--sp-1); flex-shrink: 0; } +.actionBtn { + font-family: var(--font-ui); font-size: var(--text-xs); letter-spacing: var(--tracking-wide); + padding: 4px 10px; border-radius: var(--radius-md); + background: var(--accent-muted); color: var(--accent-fg); + border: 1px solid var(--accent-dim); cursor: pointer; flex-shrink: 0; + transition: filter var(--t-base); +} +.actionBtn:hover { filter: brightness(1.1); } +.actionBtnDim { + font-family: var(--font-ui); font-size: var(--text-xs); letter-spacing: var(--tracking-wide); + padding: 4px 10px; border-radius: var(--radius-md); + background: none; color: var(--text-faint); + border: 1px solid var(--border-dim); cursor: pointer; flex-shrink: 0; + transition: color var(--t-base), border-color var(--t-base); +} +.actionBtnDim:hover { color: var(--color-error); border-color: var(--color-error); } + +.expandBtn { + display: flex; align-items: center; gap: 3px; + padding: 4px 6px; border-radius: var(--radius-sm); + color: var(--text-faint); flex-shrink: 0; + transition: color var(--t-base), background var(--t-base); +} +.expandBtn:hover { color: var(--text-muted); background: var(--bg-overlay); } +.expandCount { + font-family: var(--font-ui); font-size: var(--text-2xs); + letter-spacing: var(--tracking-wide); +} + +.variants { + display: flex; flex-direction: column; gap: 1px; + margin: 1px 0 2px calc(32px + var(--sp-3) + var(--sp-3)); + padding-left: var(--sp-3); + border-left: 1px solid var(--border-dim); + animation: fadeIn 0.1s ease both; +} +.variantRow { + display: flex; align-items: center; gap: var(--sp-2); + padding: 5px var(--sp-2); border-radius: var(--radius-md); + transition: background var(--t-fast); +} +.variantRow:hover { background: var(--bg-raised); } +.variantName { + flex: 1; font-size: var(--text-sm); color: var(--text-muted); + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; +} +.variantVersion { + font-family: var(--font-ui); font-size: var(--text-2xs); + color: var(--text-faint); letter-spacing: var(--tracking-wide); flex-shrink: 0; +} +.variantActions { flex-shrink: 0; } + +.empty { + display: flex; align-items: center; justify-content: center; + flex: 1; color: var(--text-faint); + font-family: var(--font-ui); font-size: var(--text-xs); letter-spacing: var(--tracking-wide); +} \ No newline at end of file diff --git a/src/components/extensions/ExtensionList.tsx b/src/components/extensions/ExtensionList.tsx new file mode 100644 index 0000000..721ec97 --- /dev/null +++ b/src/components/extensions/ExtensionList.tsx @@ -0,0 +1,225 @@ +import { useEffect, useState, useMemo } from "react"; +import { MagnifyingGlass, ArrowsClockwise, Plus, CircleNotch, CaretRight, CaretDown } from "@phosphor-icons/react"; +import { gql, thumbUrl } from "../../lib/client"; +import { + GET_EXTENSIONS, FETCH_EXTENSIONS, UPDATE_EXTENSION, INSTALL_EXTERNAL_EXTENSION, +} from "../../lib/queries"; +import { useStore } from "../../store"; +import type { Extension } from "../../lib/types"; +import s from "./ExtensionList.module.css"; + +type Filter = "installed" | "available" | "updates" | "all"; + +// Strip language tag suffix e.g. "MangaDex (EN)" → "MangaDex" +function baseName(name: string): string { + return name.replace(/\s*\([A-Z0-9-]{2,10}\)\s*$/, "").trim(); +} + +interface ExtGroup { + base: string; + primary: Extension; + variants: Extension[]; // all variants excluding primary +} + +export default function ExtensionList() { + const [extensions, setExtensions] = useState([]); + const [loading, setLoading] = useState(true); + const [refreshing, setRefreshing] = useState(false); + const [filter, setFilter] = useState("installed"); + const [search, setSearch] = useState(""); + const [working, setWorking] = useState>(new Set()); + const [expanded, setExpanded] = useState>(new Set()); + const [externalUrl, setExternalUrl] = useState(""); + const [showExternal, setShowExternal] = useState(false); + const preferredLang = useStore((s) => s.settings.preferredExtensionLang); + + async function load() { + return gql<{ extensions: { nodes: Extension[] } }>(GET_EXTENSIONS) + .then((d) => setExtensions(d.extensions.nodes)) + .catch(console.error); + } + + async function fetchFromRepo() { + setRefreshing(true); + return gql<{ fetchExtensions: { extensions: Extension[] } }>(FETCH_EXTENSIONS) + .then((d) => setExtensions(d.fetchExtensions.extensions)) + .catch(console.error) + .finally(() => setRefreshing(false)); + } + + const mutate = async (fn: () => Promise, pkgName: string) => { + setWorking((p) => new Set(p).add(pkgName)); + await fn().catch(console.error); + await load(); + setWorking((p) => { const n = new Set(p); n.delete(pkgName); return n; }); + }; + + async function installExternal() { + if (!externalUrl.trim()) return; + await gql(INSTALL_EXTERNAL_EXTENSION, { url: externalUrl.trim() }).catch(console.error); + setExternalUrl(""); + setShowExternal(false); + await load(); + } + + useEffect(() => { + fetchFromRepo().finally(() => setLoading(false)); + }, []); + + const filtered = extensions.filter((e) => { + const q = search.toLowerCase(); + const matchSearch = e.name.toLowerCase().includes(q) || e.lang.toLowerCase().includes(q); + const matchFilter = + filter === "installed" ? e.isInstalled : + filter === "available" ? !e.isInstalled : + filter === "updates" ? e.hasUpdate : true; + return matchSearch && matchFilter; + }); + + // Group by base name. Primary is the preferred/en/first variant. + // variants contains only the non-primary ones for the expanded list. + const groups = useMemo(() => { + const map = new Map(); + for (const ext of filtered) { + const key = baseName(ext.name); + if (!map.has(key)) map.set(key, []); + map.get(key)!.push(ext); + } + return Array.from(map.entries()).map(([base, all]) => { + const primary = + all.find((v) => v.lang === preferredLang) ?? + all.find((v) => v.lang === "en") ?? + all[0]; + const variants = all.filter((v) => v.pkgName !== primary.pkgName); + return { base, primary, variants }; + }); + }, [filtered, preferredLang]); + + const updateCount = extensions.filter((e) => e.hasUpdate).length; + + const FILTERS: { id: Filter; label: string }[] = [ + { id: "installed", label: "Installed" }, + { id: "available", label: "Available" }, + { id: "updates", label: updateCount > 0 ? `Updates (${updateCount})` : "Updates" }, + { id: "all", label: "All" }, + ]; + + function toggleExpand(base: string) { + setExpanded((p) => { + const n = new Set(p); + n.has(base) ? n.delete(base) : n.add(base); + return n; + }); + } + + function renderActions(ext: Extension) { + if (working.has(ext.pkgName)) + return ; + if (ext.hasUpdate) return ( +
+ + +
+ ); + if (ext.isInstalled) + return ; + return ; + } + + return ( +
+
+

Extensions

+
+ + +
+
+ + {showExternal && ( +
+ setExternalUrl(e.target.value)} + onKeyDown={(e) => e.key === "Enter" && installExternal()} autoFocus /> + +
+ )} + +
+
+ {FILTERS.map((f) => ( + + ))} +
+
+ + setSearch(e.target.value)} /> +
+
+ + {loading ? ( +
+ +
+ ) : groups.length === 0 ? ( +
No extensions found.
+ ) : ( +
+ {groups.map(({ base, primary, variants }) => { + const isExpanded = expanded.has(base); + const hasVariants = variants.length > 0; + + return ( +
+
+ {primary.name} { (e.target as HTMLImageElement).style.display = "none"; }} /> +
+ {base} + + {primary.lang.toUpperCase()} + {" "}v{primary.versionName} + +
+ {primary.hasUpdate && Update} + {renderActions(primary)} + {hasVariants && ( + + )} +
+ + {isExpanded && hasVariants && ( +
+ {variants.map((v) => ( +
+ {v.lang.toUpperCase()} + {v.name} + v{v.versionName} + {v.hasUpdate && } +
{renderActions(v)}
+
+ ))} +
+ )} +
+ ); + })} +
+ )} +
+ ); +} \ No newline at end of file diff --git a/src/components/layout/Layout.module.css b/src/components/layout/Layout.module.css new file mode 100644 index 0000000..54f9f16 --- /dev/null +++ b/src/components/layout/Layout.module.css @@ -0,0 +1,15 @@ +.root { + display: flex; + height: 100%; + background: var(--bg-base); + overflow: hidden; +} + +.main { + flex: 1; + overflow: hidden; + background: var(--bg-surface); + /* GPU layer for main content area */ + transform: translateZ(0); + contain: layout style; +} \ No newline at end of file diff --git a/src/components/layout/Layout.tsx b/src/components/layout/Layout.tsx new file mode 100644 index 0000000..142e2d7 --- /dev/null +++ b/src/components/layout/Layout.tsx @@ -0,0 +1,38 @@ +import { useStore } from "../../store"; +import Sidebar from "./Sidebar"; +import Library from "../pages/Library"; +import SeriesDetail from "../pages/SeriesDetail"; +import History from "../pages/History"; +import Search from "../pages/Search"; +import SourceList from "../sources/SourceList"; +import SourceBrowse from "../sources/SourceBrowse"; +import DownloadQueue from "../downloads/DownloadQueue"; +import ExtensionList from "../extensions/ExtensionList"; +import s from "./Layout.module.css"; + +export default function Layout() { + const navPage = useStore((s) => s.navPage); + const activeManga = useStore((s) => s.activeManga); + const activeSource = useStore((s) => s.activeSource); + + function renderContent() { + if (navPage === "library" && activeManga) return ; + if (navPage === "sources" && activeSource) return ; + switch (navPage) { + case "library": return ; + case "search": return ; + case "history": return ; + case "sources": return ; + case "downloads": return ; + case "extensions": return ; + default: return ; + } + } + + return ( +
+ +
{renderContent()}
+
+ ); +} \ No newline at end of file diff --git a/src/components/layout/Sidebar.module.css b/src/components/layout/Sidebar.module.css new file mode 100644 index 0000000..409c2bc --- /dev/null +++ b/src/components/layout/Sidebar.module.css @@ -0,0 +1,112 @@ +.root { + width: var(--sidebar-width); + flex-shrink: 0; + background: var(--bg-void); + display: flex; + flex-direction: column; + align-items: center; + padding: var(--sp-4) 0; + gap: 0; +} + +.logo { + /* Logo set to 80px */ + width: 80px; + height: 80px; + display: flex; + align-items: center; + justify-content: center; + + /* MARGIN REMOVED */ + margin-bottom: 0; + + /* Allows the logo to overflow the sidebar width if the sidebar is smaller than 80px */ + overflow: visible; +} + +.logoIcon { + /* Icon set to 80px */ + width: 80px; + height: 80px; + + /* Apply your UI accent green */ + background-color: var(--accent); + + /* SVG Mask Logic using Moku-Icon.svg */ + mask-image: url("../../assets/Moku-Icon.svg"); + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + + -webkit-mask-image: url("../../assets/Moku-Icon.svg"); + -webkit-mask-repeat: no-repeat; + -webkit-mask-position: center; + -webkit-mask-size: contain; + + /* Prominent glow for the large logo */ + filter: drop-shadow(0 0 12px rgba(107, 143, 107, 0.4)); +} + +.nav { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + gap: var(--sp-1); + width: 100%; + padding: 0 var(--sp-2); +} + +.tab { + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + border-radius: var(--radius-md); + color: var(--text-faint); + transition: color var(--t-base), background var(--t-base); +} + +.tab:hover { + color: var(--text-muted); + background: var(--bg-raised); +} + +.tabActive { + color: var(--accent-fg); + background: var(--accent-muted); +} + +.tabActive:hover { + color: var(--accent-fg); + background: var(--accent-muted); +} + +/* ── Bottom section ── */ +.bottom { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + padding: var(--sp-3) var(--sp-2) 0; + border-top: 1px solid var(--border-dim); + margin-top: var(--sp-3); +} + +.settingsBtn { + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + border-radius: var(--radius-md); + color: var(--text-faint); + transition: color var(--t-base), background var(--t-base), transform var(--t-slow); +} + +.settingsBtn:hover { + color: var(--text-muted); + background: var(--bg-raised); + transform: rotate(30deg); +} \ No newline at end of file diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx new file mode 100644 index 0000000..98893be --- /dev/null +++ b/src/components/layout/Sidebar.tsx @@ -0,0 +1,49 @@ +import { + Books, DownloadSimple, PuzzlePiece, Compass, + GearSix, ClockCounterClockwise, MagnifyingGlass, +} from "@phosphor-icons/react"; +import { useStore, type NavPage } from "../../store"; +import s from "./Sidebar.module.css"; + +const TABS: { id: NavPage; icon: React.ReactNode; label: string }[] = [ + { id: "library", icon: , label: "Library" }, + { id: "search", icon: , label: "Search" }, + { id: "history", icon: , label: "History" }, + { id: "sources", icon: , label: "Sources" }, + { id: "downloads", icon: , label: "Downloads" }, + { id: "extensions", icon: , label: "Extensions" }, +]; + +export default function Sidebar() { + const navPage = useStore((state) => state.navPage); + const setNavPage = useStore((state) => state.setNavPage); + const setActiveSource = useStore((state) => state.setActiveSource); + const openSettings = useStore((state) => state.openSettings); + + function navigate(id: NavPage) { + setNavPage(id); + if (id !== "sources") setActiveSource(null); + } + + return ( + + ); +} \ No newline at end of file diff --git a/src/components/layout/TitleBar.module.css b/src/components/layout/TitleBar.module.css new file mode 100644 index 0000000..dffe802 --- /dev/null +++ b/src/components/layout/TitleBar.module.css @@ -0,0 +1,55 @@ +.bar { + display: flex; + align-items: center; + justify-content: space-between; + height: 32px; + padding: 0 var(--sp-3) 0 var(--sp-4); + background: var(--bg-void); + border-bottom: 1px solid var(--border-dim); + flex-shrink: 0; + user-select: none; + /* Drag region covers the whole bar */ + -webkit-app-region: drag; +} + +.title { + font-family: var(--font-ui); + font-size: var(--text-2xs); + color: var(--text-faint); + letter-spacing: var(--tracking-wider); + text-transform: uppercase; + -webkit-app-region: drag; +} + +.controls { + display: flex; + align-items: center; + gap: 2px; + /* Controls must NOT be draggable */ + -webkit-app-region: no-drag; +} + +.btn { + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border-radius: var(--radius-sm); + color: var(--text-faint); + transition: color var(--t-base), background var(--t-base); + border: none; + background: none; + cursor: pointer; + -webkit-app-region: no-drag; +} + +.btn:hover { + color: var(--text-muted); + background: var(--bg-raised); +} + +.btnClose:hover { + color: #fff; + background: #c0392b; +} \ No newline at end of file diff --git a/src/components/layout/TitleBar.tsx b/src/components/layout/TitleBar.tsx new file mode 100644 index 0000000..7aba7c0 --- /dev/null +++ b/src/components/layout/TitleBar.tsx @@ -0,0 +1,46 @@ +import { getCurrentWindow } from "@tauri-apps/api/window"; +import s from "./TitleBar.module.css"; + +const win = getCurrentWindow(); + +export default function TitleBar() { + return ( +
+ Moku +
+ + + +
+
+ ); +} \ No newline at end of file diff --git a/src/components/pages/History.module.css b/src/components/pages/History.module.css new file mode 100644 index 0000000..9d8dd26 --- /dev/null +++ b/src/components/pages/History.module.css @@ -0,0 +1,84 @@ +.root { + display: flex; flex-direction: column; height: 100%; + overflow: hidden; animation: fadeIn 0.14s ease both; +} +.header { + display: flex; align-items: center; justify-content: space-between; + padding: var(--sp-5) var(--sp-6) var(--sp-3); flex-shrink: 0; + border-bottom: 1px solid var(--border-dim); +} +.heading { + font-family: var(--font-ui); font-size: var(--text-xs); font-weight: var(--weight-normal); + color: var(--text-faint); letter-spacing: var(--tracking-wider); text-transform: uppercase; +} +.headerRight { display: flex; align-items: center; gap: var(--sp-2); } +.searchWrap { position: relative; display: flex; align-items: center; } +.searchIcon { position: absolute; left: 9px; color: var(--text-faint); pointer-events: none; } +.search { + background: var(--bg-raised); border: 1px solid var(--border-dim); + border-radius: var(--radius-md); padding: 5px 10px 5px 26px; + color: var(--text-primary); font-size: var(--text-sm); width: 180px; outline: none; + transition: border-color var(--t-base); +} +.search::placeholder { color: var(--text-faint); } +.search:focus { border-color: var(--border-strong); } +.clearBtn { + display: flex; align-items: center; justify-content: center; + width: 28px; height: 28px; border-radius: var(--radius-md); + color: var(--text-faint); transition: color var(--t-base), background var(--t-base); +} +.clearBtn:hover { color: var(--color-error); background: var(--color-error-bg); } + +.list { flex: 1; overflow-y: auto; padding: var(--sp-4) var(--sp-6); } + +.group { margin-bottom: var(--sp-5); } +.groupLabel { + font-family: var(--font-ui); font-size: var(--text-2xs); color: var(--text-faint); + letter-spacing: var(--tracking-wider); text-transform: uppercase; + margin-bottom: var(--sp-2); padding: 0 var(--sp-1); +} + +.row { + display: flex; align-items: center; gap: var(--sp-3); + width: 100%; padding: 8px var(--sp-2); border-radius: var(--radius-md); + border: 1px solid transparent; background: none; text-align: left; cursor: pointer; + transition: background var(--t-fast), border-color var(--t-fast); +} +.row:hover { background: var(--bg-raised); border-color: var(--border-dim); } +.row:hover .playIcon { opacity: 1; } + +.thumb { + width: 36px; height: 52px; border-radius: var(--radius-sm); + object-fit: cover; flex-shrink: 0; background: var(--bg-raised); + border: 1px solid var(--border-dim); +} +.info { flex: 1; display: flex; flex-direction: column; gap: 3px; overflow: hidden; min-width: 0; } +.mangaTitle { + font-size: var(--text-base); font-weight: var(--weight-medium); + color: var(--text-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; +} +.chapterName { + font-size: var(--text-sm); color: var(--text-muted); + display: flex; align-items: center; gap: var(--sp-2); +} +.pageBadge { + font-family: var(--font-ui); font-size: var(--text-2xs); + color: var(--text-faint); letter-spacing: var(--tracking-wide); +} +.time { + font-family: var(--font-ui); font-size: var(--text-xs); + color: var(--text-faint); letter-spacing: var(--tracking-wide); + flex-shrink: 0; white-space: nowrap; +} +.playIcon { + color: var(--text-faint); flex-shrink: 0; + opacity: 0; transition: opacity var(--t-base); +} + +.empty { + flex: 1; display: flex; flex-direction: column; + align-items: center; justify-content: center; gap: var(--sp-2); +} +.emptyIcon { color: var(--text-faint); } +.emptyText { font-size: var(--text-base); color: var(--text-muted); } +.emptyHint { font-size: var(--text-sm); color: var(--text-faint); } \ No newline at end of file diff --git a/src/components/pages/History.tsx b/src/components/pages/History.tsx new file mode 100644 index 0000000..4f630d9 --- /dev/null +++ b/src/components/pages/History.tsx @@ -0,0 +1,123 @@ +import { useMemo, useState } from "react"; +import { ClockCounterClockwise, Trash, MagnifyingGlass, Play } from "@phosphor-icons/react"; +import { thumbUrl } from "../../lib/client"; +import { useStore, type HistoryEntry } from "../../store"; +import s from "./History.module.css"; + +function timeAgo(ts: number): string { + const diff = Date.now() - ts; + const m = Math.floor(diff / 60000); + if (m < 1) return "Just now"; + if (m < 60) return `${m}m ago`; + const h = Math.floor(m / 60); + if (h < 24) return `${h}h ago`; + const d = Math.floor(h / 24); + if (d < 7) return `${d}d ago`; + return new Date(ts).toLocaleDateString("en-US", { month: "short", day: "numeric" }); +} + +// Group entries by day +function groupByDay(entries: HistoryEntry[]): { label: string; items: HistoryEntry[] }[] { + const groups = new Map(); + for (const e of entries) { + const d = new Date(e.readAt); + const now = new Date(); + let label: string; + if (d.toDateString() === now.toDateString()) label = "Today"; + else { + const yesterday = new Date(now); + yesterday.setDate(now.getDate() - 1); + if (d.toDateString() === yesterday.toDateString()) label = "Yesterday"; + else label = d.toLocaleDateString("en-US", { weekday: "long", month: "long", day: "numeric" }); + } + if (!groups.has(label)) groups.set(label, []); + groups.get(label)!.push(e); + } + return Array.from(groups.entries()).map(([label, items]) => ({ label, items })); +} + +export default function History() { + const history = useStore((s) => s.history); + const clearHistory = useStore((s) => s.clearHistory); + const setActiveManga = useStore((s) => s.setActiveManga); + const setNavPage = useStore((s) => s.setNavPage); + const [search, setSearch] = useState(""); + + const filtered = useMemo(() => + search.trim() + ? history.filter((e) => + e.mangaTitle.toLowerCase().includes(search.toLowerCase()) || + e.chapterName.toLowerCase().includes(search.toLowerCase())) + : history, + [history, search] + ); + + const groups = useMemo(() => groupByDay(filtered), [filtered]); + + function resumeReading(entry: HistoryEntry) { + // Navigate to manga detail — user can continue from there + setActiveManga({ + id: entry.mangaId, + title: entry.mangaTitle, + thumbnailUrl: entry.thumbnailUrl, + } as any); + setNavPage("library"); + } + + return ( +
+
+

History

+
+
+ + setSearch(e.target.value)} /> +
+ {history.length > 0 && ( + + )} +
+
+ + {history.length === 0 ? ( +
+ +

No reading history yet.

+

Chapters you read will appear here.

+
+ ) : filtered.length === 0 ? ( +
+

No results for "{search}"

+
+ ) : ( +
+ {groups.map(({ label, items }) => ( +
+

{label}

+ {items.map((entry) => ( + + ))} +
+ ))} +
+ )} +
+ ); +} \ No newline at end of file diff --git a/src/components/pages/Library.module.css b/src/components/pages/Library.module.css new file mode 100644 index 0000000..b7de032 --- /dev/null +++ b/src/components/pages/Library.module.css @@ -0,0 +1,272 @@ +.root { + padding: var(--sp-6); + overflow-y: auto; + height: 100%; + animation: fadeIn 0.14s ease both; + /* GPU acceleration for smooth scrolling */ + will-change: scroll-position; + -webkit-overflow-scrolling: touch; +} + +.header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--sp-5); + gap: var(--sp-4); + flex-wrap: wrap; +} + +.headerLeft { + display: flex; + align-items: center; + gap: var(--sp-4); + flex-wrap: wrap; +} + +.heading { + font-family: var(--font-ui); + font-size: var(--text-xs); + font-weight: var(--weight-normal); + color: var(--text-faint); + letter-spacing: var(--tracking-wider); + text-transform: uppercase; + flex-shrink: 0; +} + +/* Filter tabs */ +.tabs { + display: flex; + gap: 2px; + background: var(--bg-raised); + border: 1px solid var(--border-dim); + border-radius: var(--radius-md); + padding: 2px; +} + +.tab { + display: flex; + align-items: center; + gap: 5px; + font-family: var(--font-ui); + font-size: var(--text-2xs); + letter-spacing: var(--tracking-wide); + text-transform: uppercase; + padding: 4px 10px; + border-radius: var(--radius-sm); + border: none; + background: none; + color: var(--text-faint); + cursor: pointer; + transition: background var(--t-base), color var(--t-base); + white-space: nowrap; +} + +.tab:hover { color: var(--text-muted); } + +.tabActive { + background: var(--accent-muted); + color: var(--accent-fg); + border: 1px solid var(--accent-dim); +} + +.tabActive:hover { color: var(--accent-fg); } + +.tabCount { + font-size: var(--text-2xs); + color: inherit; + opacity: 0.6; +} + +/* Search */ +.searchWrap { + position: relative; + display: flex; + align-items: center; +} + +.searchIcon { + position: absolute; + left: 10px; + color: var(--text-faint); + pointer-events: none; +} + +.search { + background: var(--bg-raised); + border: 1px solid var(--border-dim); + border-radius: var(--radius-md); + padding: 5px 10px 5px 28px; + color: var(--text-primary); + font-size: var(--text-sm); + width: 180px; + outline: none; + transition: border-color var(--t-base); +} + +.search::placeholder { color: var(--text-faint); } +.search:focus { border-color: var(--border-strong); } + +/* Grid */ +.grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); + gap: var(--sp-4); + /* Contain stacking contexts for GPU layers */ + contain: layout style; +} + +.card { + background: none; + border: none; + padding: 0; + cursor: pointer; + text-align: left; + /* Promote to own GPU layer on hover only */ +} + +.card:hover .cover { filter: brightness(1.06); } +.card:hover .title { color: var(--text-primary); } + +.coverWrap { + position: relative; + aspect-ratio: 2 / 3; + overflow: hidden; + border-radius: var(--radius-md); + background: var(--bg-raised); + border: 1px solid var(--border-dim); + /* GPU-accelerated compositing */ + transform: translateZ(0); +} + +.cover { + width: 100%; + height: 100%; + object-fit: cover; + transition: filter var(--t-base); + /* Hint to compositor */ + will-change: filter; +} + +.downloadedBadge { + position: absolute; + bottom: var(--sp-1); + right: var(--sp-1); + width: 18px; + height: 18px; + display: flex; + align-items: center; + justify-content: center; + font-size: 10px; + font-weight: bold; + background: var(--accent-dim); + color: var(--accent-fg); + border-radius: var(--radius-sm); + border: 1px solid var(--accent-muted); +} + +.title { + margin-top: var(--sp-2); + font-size: var(--text-sm); + color: var(--text-secondary); + line-height: var(--leading-snug); + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + transition: color var(--t-base); +} + +/* Show more */ +.showMore { + display: flex; + justify-content: center; + padding: var(--sp-6) 0 var(--sp-4); +} + +.showMoreBtn { + display: flex; + align-items: center; + gap: var(--sp-3); + font-family: var(--font-ui); + font-size: var(--text-xs); + letter-spacing: var(--tracking-wide); + color: var(--text-muted); + border: 1px solid var(--border-base); + border-radius: var(--radius-md); + padding: 7px 20px; + background: var(--bg-raised); + cursor: pointer; + transition: color var(--t-base), border-color var(--t-base), background var(--t-base); +} + +.showMoreBtn:hover { + color: var(--text-primary); + border-color: var(--border-strong); + background: var(--bg-overlay); +} + +.showMoreCount { + color: var(--text-faint); + font-size: var(--text-2xs); +} + +/* Skeleton */ +.cardSkeleton { padding: 0; } + +.coverSkeletonWrap { + aspect-ratio: 2 / 3; + border-radius: var(--radius-md); +} + +.titleSkeleton { + height: 12px; + margin-top: var(--sp-2); + width: 80%; +} + +.center { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 60%; + color: var(--text-muted); + font-size: var(--text-sm); + gap: var(--sp-2); + text-align: center; + line-height: var(--leading-base); +} + +.errorMsg { color: var(--color-error); font-size: var(--text-base); } +.errorDetail { color: var(--text-faint); font-size: var(--text-sm); } +/* ── Tag filter ── */ +.tagPanel { + display: flex; flex-wrap: wrap; gap: var(--sp-1); + padding: 0 var(--sp-6) var(--sp-3); + flex-shrink: 0; +} + +.tagChip { + font-family: var(--font-ui); font-size: var(--text-2xs); + letter-spacing: var(--tracking-wide); padding: 3px 8px; + border-radius: var(--radius-sm); border: 1px solid var(--border-dim); + background: none; color: var(--text-faint); cursor: pointer; + transition: color var(--t-base), border-color var(--t-base), background var(--t-base); +} +.tagChip:hover { color: var(--text-muted); border-color: var(--border-strong); } +.tagChipActive { + background: var(--accent-muted); border-color: var(--accent-dim); + color: var(--accent-fg); +} +.tagChipActive:hover { background: var(--accent-muted); color: var(--accent-fg); } + +.tagClear { + display: flex; align-items: center; gap: 4px; + font-family: var(--font-ui); font-size: var(--text-2xs); + letter-spacing: var(--tracking-wide); padding: 3px 8px; + border-radius: var(--radius-sm); border: 1px solid var(--color-error); + background: none; color: var(--color-error); cursor: pointer; + transition: background var(--t-base); +} +.tagClear:hover { background: var(--color-error-bg); } \ No newline at end of file diff --git a/src/components/pages/Library.tsx b/src/components/pages/Library.tsx new file mode 100644 index 0000000..81df873 --- /dev/null +++ b/src/components/pages/Library.tsx @@ -0,0 +1,230 @@ +import { useEffect, useState, useMemo, useCallback, memo } from "react"; +import { MagnifyingGlass, Books, DownloadSimple, X } from "@phosphor-icons/react"; +import { gql, thumbUrl } from "../../lib/client"; +import { GET_LIBRARY, GET_ALL_MANGA } from "../../lib/queries"; +import { useStore } from "../../store"; +import type { LibraryFilter } from "../../store"; +import type { Manga } from "../../lib/types"; +import s from "./Library.module.css"; + +const INITIAL_PAGE_SIZE = 48; +const PAGE_INCREMENT = 48; + +// Memoized card to prevent re-renders when siblings change +const MangaCard = memo(function MangaCard({ + manga, + onClick, + cropCovers, +}: { + manga: Manga; + onClick: () => void; + cropCovers: boolean; +}) { + return ( + + ); +}); + +export default function Library() { + const [allManga, setAllManga] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [search, setSearch] = useState(""); + const [visibleCount, setVisibleCount] = useState(INITIAL_PAGE_SIZE); + + const setActiveManga = useStore((state) => state.setActiveManga); + const libraryFilter = useStore((state) => state.libraryFilter); + const setLibraryFilter = useStore((state) => state.setLibraryFilter); + const settings = useStore((state) => state.settings); + const libraryTagFilter = useStore((state) => state.libraryTagFilter); + const setLibraryTagFilter = useStore((state) => state.setLibraryTagFilter); + + useEffect(() => { + // Fetch all manga (for downloaded filter on non-library entries) and + // library manga (for unreadCount/chapter progress). Merge: library wins. + Promise.all([ + gql<{ mangas: { nodes: Manga[] } }>(GET_ALL_MANGA), + gql<{ mangas: { nodes: Manga[] } }>(GET_LIBRARY), + ]) + .then(([all, lib]) => { + const libMap = new Map(lib.mangas.nodes.map((m) => [m.id, m])); + setAllManga(all.mangas.nodes.map((m) => libMap.get(m.id) ?? m)); + }) + .catch((e) => setError(e.message)) + .finally(() => setLoading(false)); + }, []); + + // Reset visible count when filter/search changes + useEffect(() => { setVisibleCount(INITIAL_PAGE_SIZE); }, [libraryFilter, search]); + + const filtered = useMemo(() => { + let items = allManga; + + // Apply filter tab + if (libraryFilter === "library") { + items = items.filter((m) => m.inLibrary); + } else if (libraryFilter === "downloaded") { + items = items.filter((m) => (m.downloadCount ?? 0) > 0); + } + + // Apply search + if (search.trim()) { + const q = search.toLowerCase(); + items = items.filter((m) => m.title.toLowerCase().includes(q)); + } + + return items; + }, [allManga, libraryFilter, search]); + + const visible = filtered.slice(0, visibleCount); + const hasMore = visibleCount < filtered.length; + + const handleCardClick = useCallback( + (m: Manga) => () => setActiveManga(m), + [setActiveManga] + ); + + // All genres present in current library + const allTags = useMemo(() => { + const tagSet = new Set(); + allManga.filter((m) => m.inLibrary).forEach((m) => (m.genre ?? []).forEach((g) => tagSet.add(g))); + return Array.from(tagSet).sort(); + }, [allManga]); + + const counts = useMemo(() => ({ + all: allManga.length, + library: allManga.filter((m) => m.inLibrary).length, + downloaded: allManga.filter((m) => (m.downloadCount ?? 0) > 0).length, + }), [allManga]); + + if (error) return ( +
+

Could not reach Suwayomi

+

{error}

+
+ ); + + return ( +
+
+
+

Library

+
+ {(["library", "downloaded", "all"] as LibraryFilter[]).map((f) => ( + + ))} +
+
+
+ + setSearch(e.target.value)} + /> +
+
+ + + {/* Tag filter panel */} + {allTags.length > 0 && ( +
+ {libraryTagFilter.length > 0 && ( + + )} + {allTags.map((tag) => { + const active = libraryTagFilter.includes(tag); + return ( + + ); + })} +
+ )} + + {loading ? ( +
+ {Array.from({ length: 12 }).map((_, i) => ( +
+
+
+
+ ))} +
+ ) : filtered.length === 0 ? ( +
+ {libraryFilter === "library" + ? "No manga saved to library. Browse sources to add some." + : libraryFilter === "downloaded" + ? "No downloaded manga." + : "No manga found."} +
+ ) : ( + <> +
+ {visible.map((m) => ( + + ))} +
+ {hasMore && ( +
+ +
+ )} + + )} +
+ ); +} \ No newline at end of file diff --git a/src/components/pages/Reader.module.css b/src/components/pages/Reader.module.css new file mode 100644 index 0000000..e4783b8 --- /dev/null +++ b/src/components/pages/Reader.module.css @@ -0,0 +1,173 @@ +.root { + position: fixed; inset: 0; + background: #000; + display: flex; flex-direction: column; + z-index: var(--z-reader); + transform: translateZ(0); will-change: transform; +} + +/* ── Topbar ── */ +.topbar { + display: flex; align-items: center; gap: var(--sp-1); + padding: 0 var(--sp-3); height: 40px; + background: var(--bg-void); border-bottom: 1px solid var(--border-dim); + flex-shrink: 0; overflow: hidden; +} + +.iconBtn { + display: flex; align-items: center; justify-content: center; + width: 28px; height: 28px; border-radius: var(--radius-sm); + color: var(--text-muted); flex-shrink: 0; + transition: color var(--t-base), background var(--t-base); +} +.iconBtn:hover:not(:disabled) { color: var(--text-primary); background: var(--bg-raised); } +.iconBtn:disabled { opacity: 0.2; cursor: default; } + +.chLabel { + flex: 1; display: flex; align-items: center; gap: var(--sp-2); + font-size: var(--text-sm); color: var(--text-muted); + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; +} +.chTitle { color: var(--text-secondary); font-weight: var(--weight-medium); } +.chSep { color: var(--text-faint); } + +.pageLabel { + font-family: var(--font-ui); font-size: var(--text-xs); + color: var(--text-muted); letter-spacing: var(--tracking-wide); + flex-shrink: 0; +} + +.topSep { + width: 1px; height: 16px; + background: var(--border-dim); flex-shrink: 0; margin: 0 var(--sp-1); +} + +.modeBtn { + display: flex; align-items: center; gap: 4px; + padding: 4px var(--sp-2); border-radius: var(--radius-sm); + color: var(--text-muted); flex-shrink: 0; + font-family: var(--font-ui); font-size: var(--text-2xs); + letter-spacing: var(--tracking-wide); + transition: color var(--t-base), background var(--t-base); +} +.modeBtn:hover { color: var(--text-primary); background: var(--bg-raised); } +.modeBtnActive { color: var(--accent-fg); background: var(--accent-muted); } +.modeBtnActive:hover { color: var(--accent-fg); background: var(--accent-muted); } +.modeBtnLabel { text-transform: capitalize; } + +.zoomBtn { + font-family: var(--font-ui); font-size: var(--text-2xs); + letter-spacing: var(--tracking-wide); color: var(--text-faint); + padding: 4px var(--sp-2); border-radius: var(--radius-sm); + flex-shrink: 0; min-width: 36px; text-align: center; + transition: color var(--t-base), background var(--t-base); +} +.zoomBtn:hover { color: var(--text-secondary); background: var(--bg-raised); } + +/* ── Viewer ── */ +.viewer { + flex: 1; overflow-y: auto; overflow-x: hidden; + display: flex; flex-direction: column; + align-items: center; justify-content: center; + -webkit-overflow-scrolling: touch; +} + +.viewerStrip { + justify-content: flex-start; + padding: var(--sp-4) 0; +} + +/* ── Images ── */ +.img { + display: block; user-select: none; + image-rendering: auto; +} +.img.optimizeContrast { image-rendering: -webkit-optimize-contrast; } + +/* Fit modes */ +.fitWidth { max-width: var(--max-page-width); width: 100%; height: auto; } +.fitHeight { max-height: calc(100vh - 80px); width: auto; max-width: 100%; } +.fitScreen { max-width: 100%; max-height: calc(100vh - 80px); object-fit: contain; } +.fitOriginal { max-width: none; width: auto; height: auto; } + +/* Longstrip */ +.stripGap { margin-bottom: 8px; } + +/* ── Double page ── */ +.doubleWrap { + display: flex; align-items: flex-start; justify-content: center; + max-width: calc(var(--max-page-width) * 2); + width: 100%; +} +.pageHalf { flex: 1; min-width: 0; object-fit: contain; } +.gapLeft { margin-right: 2px; } +.gapRight { margin-left: 2px; } + +/* ── Bottom nav ── */ +.bottombar { + display: flex; align-items: center; justify-content: center; gap: var(--sp-4); + padding: var(--sp-3); border-top: 1px solid var(--border-dim); + background: var(--bg-void); flex-shrink: 0; +} + +.navBtn { + display: flex; align-items: center; justify-content: center; + width: 34px; height: 34px; border-radius: var(--radius-md); + border: 1px solid var(--border-strong); color: var(--text-muted); + transition: background var(--t-base), color var(--t-base); +} +.navBtn:hover:not(:disabled) { background: var(--bg-raised); color: var(--text-primary); } +.navBtn:disabled { opacity: 0.25; cursor: default; } + +/* ── States ── */ +.center { + display: flex; flex-direction: column; align-items: center; justify-content: center; + position: fixed; inset: 0; background: #000; +} +.errorMsg { color: var(--color-error); font-size: var(--text-base); } + +/* ── Download modal ── */ +.dlBackdrop { + position: fixed; inset: 0; + z-index: calc(var(--z-reader) + 10); + display: flex; align-items: flex-start; justify-content: flex-end; + padding: 48px var(--sp-4) 0; +} + +.dlModal { + background: var(--bg-raised); border: 1px solid var(--border-base); + border-radius: var(--radius-xl); padding: var(--sp-3); + min-width: 210px; display: flex; flex-direction: column; gap: var(--sp-1); + box-shadow: 0 8px 32px rgba(0,0,0,0.6); + animation: scaleIn 0.12s ease both; transform-origin: top right; +} + +.dlTitle { + font-family: var(--font-ui); font-size: var(--text-2xs); + color: var(--text-faint); letter-spacing: var(--tracking-wider); + text-transform: uppercase; padding: 2px var(--sp-2) var(--sp-2); + border-bottom: 1px solid var(--border-dim); margin-bottom: var(--sp-1); +} + +.dlOption { + display: flex; flex-direction: column; align-items: flex-start; gap: 2px; + width: 100%; padding: 7px var(--sp-3); border-radius: var(--radius-md); + font-size: var(--text-sm); color: var(--text-secondary); + background: none; border: none; cursor: pointer; text-align: left; + transition: background var(--t-fast), color var(--t-fast); +} +.dlOption:hover:not(:disabled) { background: var(--bg-overlay); color: var(--text-primary); } +.dlOption:disabled { opacity: 0.3; cursor: default; } + +.dlSub { font-size: var(--text-xs); color: var(--text-faint); } + +.dlRow { display: flex; align-items: center; gap: var(--sp-2); } + +.dlInput { + width: 48px; padding: 4px var(--sp-2); + background: var(--bg-overlay); border: 1px solid var(--border-strong); + border-radius: var(--radius-sm); color: var(--text-secondary); + font-family: var(--font-ui); font-size: var(--text-xs); + text-align: center; outline: none; +} +.dlInput:focus { border-color: var(--border-focus); } \ No newline at end of file diff --git a/src/components/pages/Reader.tsx b/src/components/pages/Reader.tsx new file mode 100644 index 0000000..4762741 --- /dev/null +++ b/src/components/pages/Reader.tsx @@ -0,0 +1,500 @@ +import { useEffect, useRef, useCallback, useState, useMemo } from "react"; +import { + X, CaretLeft, CaretRight, ArrowLeft, ArrowRight, + Square, Columns, Rows, Download, ArrowsLeftRight, + ArrowsIn, ArrowsOut, ArrowsVertical, CircleNotch, +} from "@phosphor-icons/react"; +import { gql, thumbUrl } from "../../lib/client"; +import { + FETCH_CHAPTER_PAGES, MARK_CHAPTER_READ, + ENQUEUE_DOWNLOAD, ENQUEUE_CHAPTERS_DOWNLOAD, +} from "../../lib/queries"; +import { useStore, type FitMode } from "../../store"; +import { matchesKeybind } from "../../lib/keybinds"; +import s from "./Reader.module.css"; + +function preloadImage(url: string) { + const img = new Image(); img.src = url; +} + +// Returns aspect ratio once image loads; wide (>1.2 w:h) = likely double spread +function measureAspect(url: string): Promise { + return new Promise((res) => { + const img = new Image(); + img.onload = () => res(img.naturalWidth / img.naturalHeight); + img.onerror = () => res(0.67); + img.src = url; + }); +} + +// ── Download modal ──────────────────────────────────────────────────────────── +function DownloadModal({ + chapter, + remaining, + onClose, +}: { + chapter: { id: number; name: string }; + remaining: { id: number }[]; + onClose: () => void; +}) { + const [nextN, setNextN] = useState(5); + const [busy, setBusy] = useState(false); + + const run = async (fn: () => Promise) => { + setBusy(true); + await fn().catch(console.error); + setBusy(false); + onClose(); + }; + + return ( +
+
e.stopPropagation()}> +

Download

+ +
+ + setNextN(Math.max(1, Number(e.target.value)))} + onClick={(e) => e.stopPropagation()} /> +
+ +
+
+ ); +} + +// ── Reader ──────────────────────────────────────────────────────────────────── +export default function Reader() { + const containerRef = useRef(null); + const rafRef = useRef(0); + const pageNumRef = useRef(1); + const pageCache = useRef>(new Map()); + const aspectCache = useRef>(new Map()); + + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [dlOpen, setDlOpen] = useState(false); + const [markedRead, setMarkedRead] = useState>(new Set()); + const [pageGroups, setPageGroups] = useState([]); + + const { + activeManga, activeChapter, activeChapterList, + pageUrls, pageNumber, settings, + setPageUrls, setPageNumber, closeReader, openReader, openSettings, + updateSettings, addHistory, + } = useStore(); + + const kb = settings.keybinds; + const rtl = settings.readingDirection === "rtl"; + const fit = settings.fitMode ?? "width"; + const style = settings.pageStyle ?? "single"; + const maxW = settings.maxPageWidth ?? 900; + + useEffect(() => { pageNumRef.current = pageNumber; }, [pageNumber]); + + // ── Load pages ────────────────────────────────────────────────────────────── + useEffect(() => { + if (!activeChapter) return; + setLoading(true); setError(null); setPageGroups([]); + const cached = pageCache.current.get(activeChapter.id); + if (cached) { setPageUrls(cached); setLoading(false); return; } + gql<{ fetchChapterPages: { pages: string[] } }>(FETCH_CHAPTER_PAGES, { chapterId: activeChapter.id }) + .then((d) => { + const urls = d.fetchChapterPages.pages.map(thumbUrl); + pageCache.current.set(activeChapter.id, urls); + setPageUrls(urls); + }) + .catch((e) => setError(e.message)) + .finally(() => setLoading(false)); + }, [activeChapter?.id]); + + // ── Double-page grouping ───────────────────────────────────────────────────── + // Rule: page 1 (cover) always solo. Wide pages (aspect>1.2) always solo. + // Normal portrait pages pair with next portrait page. + useEffect(() => { + if (style !== "double" || !pageUrls.length) { setPageGroups([]); return; } + let cancelled = false; + (async () => { + const aspects: number[] = []; + for (const url of pageUrls) { + if (aspectCache.current.has(url)) { + aspects.push(aspectCache.current.get(url)!); + } else { + const a = await measureAspect(url); + aspectCache.current.set(url, a); + aspects.push(a); + } + } + if (cancelled) return; + const groups: number[][] = []; + // Page 1 always solo (cover) + groups.push([1]); + let i = 2; + while (i <= pageUrls.length) { + const a = aspects[i - 1]; + if (a > 1.2 || i === pageUrls.length) { + // Wide or last page — solo + groups.push([i]); i++; + } else { + const next = aspects[i]; // aspects[i] = page i+1 (0-indexed) + if (next !== undefined && next <= 1.2) { + groups.push([i, i + 1]); i += 2; + } else { + groups.push([i]); i++; + } + } + } + setPageGroups(groups); + })(); + return () => { cancelled = true; }; + }, [pageUrls, style, settings.offsetDoubleSpreads]); + + const currentGroup = useMemo(() => { + if (style !== "double" || !pageGroups.length) return null; + return pageGroups.find((g) => g.includes(pageNumber)) ?? null; + }, [pageGroups, pageNumber, style]); + + // ── Preload ───────────────────────────────────────────────────────────────── + useEffect(() => { + for (let i = 1; i <= (settings.preloadPages ?? 3); i++) { + const url = pageUrls[pageNumber - 1 + i]; + if (url) preloadImage(url); + } + }, [pageNumber, pageUrls, settings.preloadPages]); + + // ── Adjacent chapters ──────────────────────────────────────────────────────── + const adjacent = useMemo(() => { + if (!activeChapter || !activeChapterList.length) + return { prev: null, next: null, remaining: [] }; + const idx = activeChapterList.findIndex((c) => c.id === activeChapter.id); + return { + prev: idx > 0 ? activeChapterList[idx - 1] : null, + next: idx < activeChapterList.length - 1 ? activeChapterList[idx + 1] : null, + remaining: activeChapterList.slice(idx + 1), + }; + }, [activeChapter, activeChapterList]); + + useEffect(() => { + const preload = (id: number) => { + if (pageCache.current.has(id)) return; + gql<{ fetchChapterPages: { pages: string[] } }>(FETCH_CHAPTER_PAGES, { chapterId: id }) + .then((d) => { + const urls = d.fetchChapterPages.pages.map(thumbUrl); + pageCache.current.set(id, urls); + urls.slice(0, 2).forEach(preloadImage); + }).catch(() => {}); + }; + if (adjacent.next) preload(adjacent.next.id); + if (adjacent.prev) preload(adjacent.prev.id); + }, [adjacent.next?.id, adjacent.prev?.id]); + + const lastPage = pageUrls.length; + + // ── Auto-mark read + history ───────────────────────────────────────────────── + useEffect(() => { + if (!activeChapter || !lastPage) return; + if (activeManga) { + addHistory({ + mangaId: activeManga.id, mangaTitle: activeManga.title, + thumbnailUrl: activeManga.thumbnailUrl, chapterId: activeChapter.id, + chapterName: activeChapter.name, pageNumber, readAt: Date.now(), + }); + } + if (settings.autoMarkRead && pageNumber === lastPage && !markedRead.has(activeChapter.id)) { + setMarkedRead((p) => new Set(p).add(activeChapter.id)); + gql(MARK_CHAPTER_READ, { id: activeChapter.id, isRead: true }).catch(console.error); + } + }, [pageNumber, lastPage, activeChapter?.id]); + + // ── Navigation ────────────────────────────────────────────────────────────── + const advanceGroup = useCallback((forward: boolean) => { + if (!pageGroups.length) return; + const gi = pageGroups.findIndex((g) => g.includes(pageNumber)); + if (forward) { + if (gi < pageGroups.length - 1) setPageNumber(pageGroups[gi + 1][0]); + else if (adjacent.next) openReader(adjacent.next, activeChapterList); + else closeReader(); + } else { + if (gi > 0) setPageNumber(pageGroups[gi - 1][0]); + else if (adjacent.prev) openReader(adjacent.prev, activeChapterList); + } + }, [pageGroups, pageNumber, adjacent, activeChapterList]); + + const goForward = useCallback(() => { + if (style === "double" && pageGroups.length) { advanceGroup(true); return; } + if (pageNumber < lastPage) setPageNumber(pageNumber + 1); + else if (adjacent.next) openReader(adjacent.next, activeChapterList); + else closeReader(); + }, [pageNumber, lastPage, adjacent, activeChapterList, style, pageGroups, advanceGroup]); + + const goBack = useCallback(() => { + if (style === "double" && pageGroups.length) { advanceGroup(false); return; } + if (pageNumber > 1) setPageNumber(pageNumber - 1); + else if (adjacent.prev) openReader(adjacent.prev, activeChapterList); + }, [pageNumber, adjacent, activeChapterList, style, pageGroups, advanceGroup]); + + const goNext = rtl ? goBack : goForward; + const goPrev = rtl ? goForward : goBack; + + function cycleStyle() { + const cycle = ["single", "double", "longstrip"] as const; + const next = cycle[(cycle.indexOf(style as any) + 1) % cycle.length]; + updateSettings({ pageStyle: next }); + } + + function cycleFit() { + const cycle: FitMode[] = ["width", "height", "screen", "original"]; + updateSettings({ fitMode: cycle[(cycle.indexOf(fit) + 1) % cycle.length] }); + } + + // Ctrl+scroll → zoom maxPageWidth + useEffect(() => { + const onWheel = (e: WheelEvent) => { + if (!e.ctrlKey) return; + e.preventDefault(); + const delta = e.deltaY < 0 ? 50 : -50; + updateSettings({ maxPageWidth: Math.min(2400, Math.max(200, maxW + delta)) }); + }; + window.addEventListener("wheel", onWheel, { passive: false }); + return () => window.removeEventListener("wheel", onWheel); + }, [maxW]); + + // Keybinds + useEffect(() => { + const onKey = (e: KeyboardEvent) => { + if ((e.target as HTMLElement).tagName === "INPUT") return; + if (matchesKeybind(e, kb.pageRight)) { e.preventDefault(); goForward(); } + else if (matchesKeybind(e, kb.pageLeft)) { e.preventDefault(); goBack(); } + else if (matchesKeybind(e, kb.firstPage)) { e.preventDefault(); setPageNumber(1); } + else if (matchesKeybind(e, kb.lastPage)) { e.preventDefault(); setPageNumber(lastPage); } + else if (matchesKeybind(e, kb.chapterRight)) { e.preventDefault(); if (adjacent.next) openReader(adjacent.next, activeChapterList); } + else if (matchesKeybind(e, kb.chapterLeft)) { e.preventDefault(); if (adjacent.prev) openReader(adjacent.prev, activeChapterList); } + else if (matchesKeybind(e, kb.exitReader)) { e.preventDefault(); closeReader(); } + else if (matchesKeybind(e, kb.togglePageStyle)) { e.preventDefault(); cycleStyle(); } + else if (matchesKeybind(e, kb.toggleReadingDirection)){ e.preventDefault(); updateSettings({ readingDirection: rtl ? "ltr" : "rtl" }); } + else if (matchesKeybind(e, kb.openSettings)) { e.preventDefault(); openSettings(); } + }; + window.addEventListener("keydown", onKey); + return () => window.removeEventListener("keydown", onKey); + }, [goForward, goBack, kb, style, rtl, lastPage, adjacent, activeChapterList]); + + // Longstrip scroll — rAF throttled, no flushSync + useEffect(() => { + const el = containerRef.current; + if (!el || style !== "longstrip") return; + const onScroll = () => { + cancelAnimationFrame(rafRef.current); + rafRef.current = requestAnimationFrame(() => { + if (!el) return; + const midY = el.scrollTop + el.clientHeight * 0.5; + let cumH = 0; + const children = Array.from(el.children) as HTMLElement[]; + for (let i = 0; i < children.length; i++) { + cumH += children[i].clientHeight; + if (cumH >= midY) { + const n = i + 1; + if (n !== pageNumRef.current) setPageNumber(n); + break; + } + } + }); + }; + el.addEventListener("scroll", onScroll, { passive: true }); + return () => { el.removeEventListener("scroll", onScroll); cancelAnimationFrame(rafRef.current); }; + }, [style]); + + useEffect(() => { + if (style !== "longstrip" && containerRef.current) containerRef.current.scrollTop = 0; + }, [pageNumber, style]); + + function handleTap(e: React.MouseEvent) { + if (style === "longstrip") return; + const x = e.clientX / window.innerWidth; + if (!rtl) { if (x > 0.6) goForward(); else if (x < 0.4) goBack(); } + else { if (x < 0.4) goForward(); else if (x > 0.6) goBack(); } + } + + // ── CSS vars ───────────────────────────────────────────────────────────────── + const cssVars = { "--max-page-width": `${maxW}px` } as React.CSSProperties; + + const imgCls = [ + s.img, + fit === "width" && s.fitWidth, + fit === "height" && s.fitHeight, + fit === "screen" && s.fitScreen, + fit === "original" && s.fitOriginal, + settings.optimizeContrast && s.optimizeContrast, + ].filter(Boolean).join(" "); + + // ── Double page render ──────────────────────────────────────────────────────── + function renderDouble() { + if (!currentGroup) { + return {`Page; + } + const ordered = rtl ? [...currentGroup].reverse() : currentGroup; + const [left, right] = ordered; + return ( +
+ {`Page + {right && ( + {`Page + )} +
+ ); + } + + // ── Icons ──────────────────────────────────────────────────────────────────── + const fitIcon = + fit === "width" ? : + fit === "height" ? : + fit === "screen" ? : + ; + + const fitLabel = { width: "Fit W", height: "Fit H", screen: "Fit Screen", original: "1:1" }[fit]; + + const styleIcon = + style === "single" ? : + style === "double" ? : + ; + + if (loading) return ( +
+ +
+ ); + + if (error) return ( +

{error}

+ ); + + return ( +
+ {/* ── Topbar ── */} +
+ + + + {activeManga?.title} + / + {activeChapter?.name} + + {pageNumber} / {lastPage || "…"} + + +
+ + {/* Fit mode */} + + + {/* Zoom — click resets */} + + + {/* RTL */} + + + {/* Page style */} + + + {/* Page gap toggle — only meaningful in double/longstrip */} + {style !== "single" && ( + + )} + + {/* Download */} + +
+ + {/* ── Viewer ── */} +
+ {style === "longstrip" ? ( + pageUrls.map((url, i) => ( + {`Page + )) + ) : style === "double" ? ( + renderDouble() + ) : ( + {`Page + )} +
+ + {/* ── Bottom nav ── */} +
+ + +
+ + {dlOpen && activeChapter && ( + setDlOpen(false)} + /> + )} +
+ ); +} \ No newline at end of file diff --git a/src/components/pages/Search.module.css b/src/components/pages/Search.module.css new file mode 100644 index 0000000..a447408 --- /dev/null +++ b/src/components/pages/Search.module.css @@ -0,0 +1,91 @@ +.root { + display: flex; flex-direction: column; height: 100%; + overflow: hidden; animation: fadeIn 0.14s ease both; +} + +.header { + display: flex; align-items: center; gap: var(--sp-4); + padding: var(--sp-4) var(--sp-6); border-bottom: 1px solid var(--border-dim); flex-shrink: 0; +} +.heading { + font-family: var(--font-ui); font-size: var(--text-xs); + color: var(--text-faint); letter-spacing: var(--tracking-wider); + text-transform: uppercase; flex-shrink: 0; +} +.searchBar { + flex: 1; display: flex; align-items: center; gap: var(--sp-2); + background: var(--bg-raised); border: 1px solid var(--border-dim); + border-radius: var(--radius-lg); padding: 0 var(--sp-3) 0 var(--sp-2); + transition: border-color var(--t-base); +} +.searchBar:focus-within { border-color: var(--border-strong); } +.searchIcon { color: var(--text-faint); flex-shrink: 0; } +.searchInput { + flex: 1; background: none; border: none; outline: none; + color: var(--text-primary); font-size: var(--text-sm); padding: 8px 0; +} +.searchInput::placeholder { color: var(--text-faint); } +.searchBtn { + font-family: var(--font-ui); font-size: var(--text-xs); letter-spacing: var(--tracking-wide); + padding: 5px 12px; border-radius: var(--radius-md); flex-shrink: 0; + background: var(--accent-muted); color: var(--accent-fg); + border: 1px solid var(--accent-dim); cursor: pointer; + transition: filter var(--t-base); display: flex; align-items: center; gap: var(--sp-2); +} +.searchBtn:hover:not(:disabled) { filter: brightness(1.1); } +.searchBtn:disabled { opacity: 0.4; cursor: default; } + +.results { flex: 1; overflow-y: auto; padding: var(--sp-5) var(--sp-6); display: flex; flex-direction: column; gap: var(--sp-6); } + +.sourceSection { display: flex; flex-direction: column; gap: var(--sp-3); } + +.sourceHeader { + display: flex; align-items: center; gap: var(--sp-2); +} +.sourceIcon { width: 18px; height: 18px; border-radius: 4px; object-fit: cover; flex-shrink: 0; } +.sourceName { font-size: var(--text-sm); font-weight: var(--weight-medium); color: var(--text-secondary); } +.resultCount { + font-family: var(--font-ui); font-size: var(--text-2xs); color: var(--text-faint); + letter-spacing: var(--tracking-wide); margin-left: auto; +} +.sourceError { font-size: var(--text-xs); color: var(--color-error); padding: 0 var(--sp-1); } + +.sourceRow { + display: flex; gap: var(--sp-3); overflow-x: auto; + padding-bottom: var(--sp-2); + scrollbar-width: thin; +} +.card { + flex-shrink: 0; width: 110px; background: none; border: none; padding: 0; + cursor: pointer; text-align: left; +} +.card:hover .cover { filter: brightness(1.06); } +.coverWrap { + position: relative; aspect-ratio: 2/3; border-radius: var(--radius-md); + overflow: hidden; background: var(--bg-raised); border: 1px solid var(--border-dim); +} +.cover { width: 100%; height: 100%; object-fit: cover; transition: filter var(--t-base); } +.inLibBadge { + position: absolute; bottom: var(--sp-1); left: var(--sp-1); + font-family: var(--font-ui); font-size: var(--text-2xs); letter-spacing: var(--tracking-wide); + text-transform: uppercase; background: var(--accent-muted); color: var(--accent-fg); + border: 1px solid var(--accent-dim); padding: 2px 5px; border-radius: var(--radius-sm); +} +.cardTitle { + margin-top: var(--sp-1); font-size: var(--text-xs); color: var(--text-muted); + display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; + line-height: var(--leading-snug); +} + +/* Skeletons */ +.skCard { flex-shrink: 0; width: 110px; } +.skCover { aspect-ratio: 2/3; border-radius: var(--radius-md); } +.skTitle { height: 11px; margin-top: var(--sp-1); width: 75%; } + +.empty { + flex: 1; display: flex; flex-direction: column; + align-items: center; justify-content: center; gap: var(--sp-2); +} +.emptyIcon { color: var(--text-faint); } +.emptyText { font-size: var(--text-base); color: var(--text-muted); } +.emptyHint { font-size: var(--text-sm); color: var(--text-faint); } \ No newline at end of file diff --git a/src/components/pages/Search.tsx b/src/components/pages/Search.tsx new file mode 100644 index 0000000..dc3f943 --- /dev/null +++ b/src/components/pages/Search.tsx @@ -0,0 +1,168 @@ +import { useState, useRef, useCallback } from "react"; +import { MagnifyingGlass, CircleNotch } from "@phosphor-icons/react"; +import { gql, thumbUrl } from "../../lib/client"; +import { GET_SOURCES, FETCH_SOURCE_MANGA } from "../../lib/queries"; +import { useStore } from "../../store"; +import type { Manga, Source } from "../../lib/types"; +import s from "./Search.module.css"; + +interface SourceResult { + source: Source; + mangas: Manga[]; + loading: boolean; + error: string | null; +} + +export default function Search() { + const [query, setQuery] = useState(""); + const [submitted, setSubmitted] = useState(""); + const [results, setResults] = useState([]); + const [sources, setSources] = useState([]); + const [loadingSources, setLoadingSources] = useState(false); + const inputRef = useRef(null); + + const setActiveManga = useStore((s) => s.setActiveManga); + const setNavPage = useStore((s) => s.setNavPage); + + const loadSources = useCallback(async () => { + if (sources.length) return sources; + setLoadingSources(true); + const data = await gql<{ sources: { nodes: Source[] } }>(GET_SOURCES) + .finally(() => setLoadingSources(false)); + const nodes = data.sources.nodes.filter((s) => s.id !== "0"); + setSources(nodes); + return nodes; + }, [sources]); + + async function runSearch() { + const q = query.trim(); + if (!q) return; + setSubmitted(q); + + const srcs = await loadSources(); + // Initialise loading state for each source + setResults(srcs.map((src) => ({ source: src, mangas: [], loading: true, error: null }))); + + // Fire all source queries in parallel, update each independently + srcs.forEach((src) => { + gql<{ fetchSourceManga: { mangas: Manga[] } }>(FETCH_SOURCE_MANGA, { + source: src.id, type: "SEARCH", page: 1, query: q, + }) + .then((d) => { + setResults((prev) => prev.map((r) => + r.source.id === src.id + ? { ...r, mangas: d.fetchSourceManga.mangas, loading: false } + : r + )); + }) + .catch((e) => { + setResults((prev) => prev.map((r) => + r.source.id === src.id + ? { ...r, loading: false, error: e.message } + : r + )); + }); + }); + } + + function openManga(m: Manga) { + setActiveManga(m); + setNavPage("library"); + } + + const hasResults = results.some((r) => r.mangas.length > 0); + const allDone = results.every((r) => !r.loading); + + return ( +
+ {/* ── Search bar ── */} +
+

Search

+
+ + setQuery(e.target.value)} + onKeyDown={(e) => e.key === "Enter" && runSearch()} + autoFocus /> + +
+
+ + {/* ── Empty state ── */} + {!submitted && ( +
+ +

Search across all installed sources at once

+

Results from each source appear as they load.

+
+ )} + + {/* ── Results ── */} + {submitted && ( +
+ {results.length === 0 && ( +
+ +
+ )} + + {results + .filter((r) => r.mangas.length > 0 || r.loading || r.error) + .map(({ source, mangas, loading, error }) => ( +
+
+ {source.displayName} { (e.target as HTMLImageElement).style.display = "none"; }} /> + {source.displayName} + {loading && } + {!loading && mangas.length > 0 && ( + {mangas.length} results + )} +
+ + {error ? ( +

{error}

+ ) : loading ? ( +
+ {Array.from({ length: 4 }).map((_, i) => ( +
+
+
+
+ ))} +
+ ) : mangas.length > 0 ? ( +
+ {mangas.slice(0, 8).map((m) => ( + + ))} +
+ ) : null} +
+ ))} + + {allDone && !hasResults && submitted && ( +
+

No results for "{submitted}"

+
+ )} +
+ )} +
+ ); +} \ No newline at end of file diff --git a/src/components/pages/SeriesDetail.module.css b/src/components/pages/SeriesDetail.module.css new file mode 100644 index 0000000..a91483c --- /dev/null +++ b/src/components/pages/SeriesDetail.module.css @@ -0,0 +1,430 @@ +.root { + display: flex; + height: 100%; + overflow: hidden; + animation: fadeIn 0.14s ease both; +} + +/* ── Sidebar ── */ +.sidebar { + width: 200px; + flex-shrink: 0; + padding: var(--sp-5); + border-right: 1px solid var(--border-dim); + overflow-y: auto; + display: flex; + flex-direction: column; + gap: var(--sp-4); + background: var(--bg-base); +} + +.back { + display: flex; + align-items: center; + gap: var(--sp-2); + color: var(--text-muted); + font-size: var(--text-xs); + font-family: var(--font-ui); + letter-spacing: var(--tracking-wide); + text-transform: uppercase; + transition: color var(--t-base); +} +.back:hover { color: var(--text-secondary); } + +.coverWrap { + width: 100%; + aspect-ratio: 2 / 3; + border-radius: var(--radius-md); + overflow: hidden; + background: var(--bg-raised); + border: 1px solid var(--border-dim); + flex-shrink: 0; +} + +.cover { width: 100%; height: 100%; object-fit: cover; } + +.metaSkeleton { display: flex; flex-direction: column; gap: var(--sp-2); } +.skLine { border-radius: var(--radius-sm); } + +.meta { display: flex; flex-direction: column; gap: var(--sp-3); } + +.title { + font-size: var(--text-base); + font-weight: var(--weight-medium); + color: var(--text-primary); + line-height: var(--leading-snug); + letter-spacing: var(--tracking-tight); +} + +.byline { + font-size: var(--text-xs); + color: var(--text-muted); + font-family: var(--font-ui); +} + +.statusBadge { + display: inline-block; + font-family: var(--font-ui); + font-size: var(--text-2xs); + letter-spacing: var(--tracking-wider); + text-transform: uppercase; + padding: 2px 7px; + border-radius: var(--radius-sm); + width: fit-content; +} + +.statusOngoing { + background: var(--accent-muted); + color: var(--accent-fg); + border: 1px solid var(--accent-dim); +} + +.statusEnded { + background: var(--bg-raised); + color: var(--text-faint); + border: 1px solid var(--border-dim); +} + +.genres { display: flex; flex-wrap: wrap; gap: var(--sp-1); } + +.genre { + font-size: var(--text-2xs); + font-family: var(--font-ui); + color: var(--text-faint); + background: var(--bg-raised); + border: 1px solid var(--border-dim); + border-radius: var(--radius-sm); + padding: 1px 6px; + letter-spacing: var(--tracking-wide); +} + +.sourceLabel { + font-family: var(--font-ui); + font-size: var(--text-2xs); + color: var(--text-faint); + letter-spacing: var(--tracking-wide); + text-transform: uppercase; +} + +.description { + font-size: var(--text-xs); + color: var(--text-muted); + line-height: var(--leading-base); + display: -webkit-box; + -webkit-line-clamp: 8; + -webkit-box-orient: vertical; + overflow: hidden; +} + +/* ── Progress ── */ +.progressSection { + display: flex; + flex-direction: column; + gap: var(--sp-1); +} + +.progressHeader { + display: flex; + justify-content: space-between; + align-items: center; +} + +.progressLabel { + font-family: var(--font-ui); + font-size: var(--text-2xs); + color: var(--text-faint); + letter-spacing: var(--tracking-wide); +} + +.progressPct { + font-family: var(--font-ui); + font-size: var(--text-2xs); + color: var(--accent-fg); + letter-spacing: var(--tracking-wide); +} + +.progressTrack { + height: 3px; + background: var(--border-base); + border-radius: var(--radius-full); + overflow: hidden; +} + +.progressFill { + height: 100%; + background: var(--accent); + border-radius: var(--radius-full); + transition: width 0.4s ease; +} + +/* ── Actions ── */ +.actions { + display: flex; + align-items: center; + gap: var(--sp-2); +} + +.libraryBtn { + display: flex; + align-items: center; + gap: var(--sp-2); + font-size: var(--text-xs); + font-family: var(--font-ui); + letter-spacing: var(--tracking-wide); + padding: 5px 10px; + border-radius: var(--radius-md); + border: 1px solid var(--border-strong); + color: var(--text-muted); + background: var(--bg-raised); + transition: border-color var(--t-base), color var(--t-base), background var(--t-base); + flex: 1; +} +.libraryBtn:hover { border-color: var(--accent); color: var(--accent-fg); } +.libraryBtn:disabled { opacity: 0.4; cursor: default; } +.libraryBtnActive { + background: var(--accent-muted); + border-color: var(--accent-dim); + color: var(--accent-fg); +} + +.externalLink { + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border-radius: var(--radius-md); + border: 1px solid var(--border-dim); + color: var(--text-faint); + flex-shrink: 0; + transition: color var(--t-base), border-color var(--t-base); +} +.externalLink:hover { color: var(--text-muted); border-color: var(--border-strong); } + +/* ── Start/Continue reading button ── */ +.readBtn { + display: flex; + align-items: center; + justify-content: center; + gap: var(--sp-2); + width: 100%; + padding: 8px var(--sp-3); + border-radius: var(--radius-md); + background: var(--accent-dim); + border: 1px solid var(--accent); + color: var(--accent-fg); + font-size: var(--text-xs); + font-family: var(--font-ui); + letter-spacing: var(--tracking-wide); + cursor: pointer; + transition: background var(--t-base), border-color var(--t-base); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.readBtn:hover { background: var(--accent-muted); border-color: var(--accent-bright); } + +.chapterCount { + font-family: var(--font-ui); + font-size: var(--text-2xs); + color: var(--text-faint); + letter-spacing: var(--tracking-wide); + text-transform: uppercase; + margin-top: auto; + padding-top: var(--sp-2); +} + +/* ── Chapter list ── */ +.listWrap { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.listHeader { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--sp-3) var(--sp-4); + border-bottom: 1px solid var(--border-dim); + flex-shrink: 0; +} + +.sortBtn { + display: flex; + align-items: center; + gap: var(--sp-2); + font-family: var(--font-ui); + font-size: var(--text-xs); + color: var(--text-muted); + letter-spacing: var(--tracking-wide); + padding: 4px 8px; + border-radius: var(--radius-md); + transition: background var(--t-base), color var(--t-base); +} +.sortBtn:hover { background: var(--bg-raised); color: var(--text-secondary); } + +.pagination { + display: flex; + align-items: center; + gap: var(--sp-2); +} + +.paginationBottom { + display: flex; + align-items: center; + justify-content: center; + gap: var(--sp-3); + padding: var(--sp-3) var(--sp-4); + border-top: 1px solid var(--border-dim); + flex-shrink: 0; +} + +.pageBtn { + font-family: var(--font-ui); + font-size: var(--text-xs); + color: var(--text-muted); + letter-spacing: var(--tracking-wide); + padding: 3px 8px; + border-radius: var(--radius-sm); + border: 1px solid var(--border-dim); + background: none; + cursor: pointer; + transition: background var(--t-base), color var(--t-base), border-color var(--t-base); +} +.pageBtn:hover:not(:disabled) { background: var(--bg-raised); color: var(--text-secondary); border-color: var(--border-strong); } +.pageBtn:disabled { opacity: 0.3; cursor: default; } + +.pageNum { + font-family: var(--font-ui); + font-size: var(--text-xs); + color: var(--text-faint); + letter-spacing: var(--tracking-wide); + min-width: 40px; + text-align: center; +} + +.list { + flex: 1; + overflow-y: auto; + padding: var(--sp-2) var(--sp-4); + display: flex; + flex-direction: column; + gap: 1px; +} + +.rowSkeleton { + display: flex; + flex-direction: column; + gap: var(--sp-2); + padding: 12px var(--sp-3); + border-radius: var(--radius-md); + background: var(--bg-raised); + margin-bottom: 1px; +} + +.row { + display: flex; + justify-content: space-between; + align-items: center; + background: none; + border: none; + border-radius: var(--radius-md); + padding: 10px var(--sp-3); + cursor: pointer; + text-align: left; + width: 100%; + color: var(--text-primary); + transition: background var(--t-fast); +} +.row:hover { background: var(--bg-raised); } +.rowRead .chName { color: var(--text-faint); } + +.chLeft { + display: flex; + flex-direction: column; + gap: 3px; + overflow: hidden; + flex: 1; + min-width: 0; +} + +.chName { + font-size: var(--text-base); + color: var(--text-secondary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + transition: color var(--t-fast); +} +.row:hover .chName { color: var(--text-primary); } + +.chMeta { display: flex; align-items: center; gap: var(--sp-3); } + +.chMetaItem { + font-family: var(--font-ui); + font-size: var(--text-2xs); + color: var(--text-faint); + letter-spacing: var(--tracking-wide); +} + +.chRight { + display: flex; + align-items: center; + gap: var(--sp-2); + flex-shrink: 0; + margin-left: var(--sp-3); +} + +.bookmarkIcon { color: var(--accent); } +.readIcon { color: var(--text-faint); } +.downloadedIcon { color: var(--accent-fg); } +.enqueuingIcon { color: var(--text-faint); } + +.dlBtn { + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border-radius: var(--radius-sm); + color: var(--text-faint); + transition: color var(--t-base), background var(--t-base); +} +.dlBtn:hover { color: var(--text-muted); background: var(--bg-overlay); } +/* ── Download section ── */ +.downloadSection { + position: relative; margin-top: var(--sp-2); +} + +.downloadToggle { + display: flex; align-items: center; gap: var(--sp-2); + width: 100%; padding: 7px var(--sp-3); + border-radius: var(--radius-md); + border: 1px solid var(--border-dim); + background: none; color: var(--text-muted); + font-size: var(--text-sm); cursor: pointer; + transition: background var(--t-base), color var(--t-base), border-color var(--t-base); +} +.downloadToggle:hover { background: var(--bg-raised); color: var(--text-secondary); border-color: var(--border-strong); } + +.downloadMenu { + margin-top: var(--sp-1); + background: var(--bg-raised); border: 1px solid var(--border-base); + border-radius: var(--radius-lg); padding: var(--sp-1); + display: flex; flex-direction: column; gap: 1px; + box-shadow: 0 4px 16px rgba(0,0,0,0.4); + animation: fadeIn 0.1s ease both; +} + +.dlItem { + display: flex; flex-direction: column; align-items: flex-start; gap: 2px; + width: 100%; padding: 7px var(--sp-3); border-radius: var(--radius-md); + font-size: var(--text-sm); color: var(--text-secondary); + background: none; border: none; cursor: pointer; text-align: left; + transition: background var(--t-fast), color var(--t-fast); +} +.dlItem:hover { background: var(--bg-overlay); color: var(--text-primary); } + +.dlItemSub { font-size: var(--text-xs); color: var(--text-faint); } \ No newline at end of file diff --git a/src/components/pages/SeriesDetail.tsx b/src/components/pages/SeriesDetail.tsx new file mode 100644 index 0000000..ad3613f --- /dev/null +++ b/src/components/pages/SeriesDetail.tsx @@ -0,0 +1,468 @@ +import { useEffect, useState, useMemo, useCallback } from "react"; +import { + ArrowLeft, BookmarkSimple, Download, CheckCircle, + ArrowSquareOut, BookOpen, CircleNotch, Play, + SortAscending, SortDescending, +} from "@phosphor-icons/react"; +import { gql, thumbUrl } from "../../lib/client"; +import { + GET_MANGA, GET_CHAPTERS, FETCH_CHAPTERS, ENQUEUE_DOWNLOAD, + UPDATE_MANGA, MARK_CHAPTER_READ, MARK_CHAPTERS_READ, DELETE_DOWNLOADED_CHAPTERS, + ENQUEUE_CHAPTERS_DOWNLOAD, +} from "../../lib/queries"; +import { useStore } from "../../store"; +import ContextMenu, { type ContextMenuEntry } from "../context/ContextMenu"; +import type { Manga, Chapter } from "../../lib/types"; +import s from "./SeriesDetail.module.css"; + +function formatDate(ts: string | null | undefined): string { + if (!ts) return ""; + const n = Number(ts); + const d = new Date(n > 1e10 ? n : n * 1000); + return d.toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric" }); +} + +interface CtxState { + x: number; + y: number; + chapter: Chapter; + indexInSorted: number; +} + +const CHAPTERS_PER_PAGE = 25; + +export default function SeriesDetail() { + const activeManga = useStore((state) => state.activeManga); + const setActiveManga = useStore((state) => state.setActiveManga); + const openReader = useStore((state) => state.openReader); + const settings = useStore((state) => state.settings); + const updateSettings = useStore((state) => state.updateSettings); + + const [manga, setManga] = useState(activeManga); + const [chapters, setChapters] = useState([]); + const [loadingManga, setLoadingManga] = useState(true); + const [loadingChapters, setLoadingChapters] = useState(true); + const [enqueueing, setEnqueueing] = useState>(new Set()); + const [dlOpen, setDlOpen] = useState(false); + const [togglingLibrary, setTogglingLibrary] = useState(false); + const [chapterPage, setChapterPage] = useState(1); + const [ctx, setCtx] = useState(null); + + const sortDir = settings.chapterSortDir; + + useEffect(() => { + if (!activeManga) return; + setLoadingManga(true); + gql<{ manga: Manga }>(GET_MANGA, { id: activeManga.id }) + .then((data) => setManga(data.manga)) + .catch(console.error) + .finally(() => setLoadingManga(false)); + }, [activeManga?.id]); + + const loadChapters = useCallback((mangaId: number) => { + return gql<{ chapters: { nodes: Chapter[] } }>(GET_CHAPTERS, { mangaId }) + .then((data) => { + // Always store in natural order (ascending sourceOrder), sort in render + const sorted = [...data.chapters.nodes].sort((a, b) => a.sourceOrder - b.sourceOrder); + setChapters(sorted); + return sorted; + }); + }, []); + + useEffect(() => { + if (!activeManga) return; + setLoadingChapters(true); + setChapters([]); + setChapterPage(1); + + loadChapters(activeManga.id) + .catch(console.error) + .finally(() => setLoadingChapters(false)); + + // Fetch from source in background + gql(FETCH_CHAPTERS, { mangaId: activeManga.id }) + .then(() => loadChapters(activeManga.id)) + .catch(console.error); + }, [activeManga?.id]); + + // Sorted chapters based on setting + const sortedChapters = useMemo(() => + sortDir === "desc" + ? [...chapters].reverse() + : [...chapters], + [chapters, sortDir] + ); + + const totalPages = Math.ceil(sortedChapters.length / CHAPTERS_PER_PAGE); + const pageChapters = sortedChapters.slice( + (chapterPage - 1) * CHAPTERS_PER_PAGE, + chapterPage * CHAPTERS_PER_PAGE + ); + + // Progress stats + const readCount = chapters.filter((c) => c.isRead).length; + const totalCount = chapters.length; + const progressPct = totalCount > 0 ? (readCount / totalCount) * 100 : 0; + + // Start / Continue reading logic + const continueChapter = useMemo(() => { + if (!chapters.length) return null; + // Find first unread chapter (in ascending order) + const asc = [...chapters].sort((a, b) => a.sourceOrder - b.sourceOrder); + const inProgress = asc.find((c) => !c.isRead && (c.lastPageRead ?? 0) > 0); + if (inProgress) return { chapter: inProgress, type: "continue" as const }; + const firstUnread = asc.find((c) => !c.isRead); + if (firstUnread) return { chapter: firstUnread, type: "start" as const }; + return { chapter: asc[0], type: "reread" as const }; + }, [chapters]); + + async function toggleLibrary() { + if (!manga) return; + setTogglingLibrary(true); + const next = !manga.inLibrary; + await gql(UPDATE_MANGA, { id: manga.id, inLibrary: next }).catch(console.error); + setManga((prev) => prev ? { ...prev, inLibrary: next } : prev); + setTogglingLibrary(false); + } + + async function enqueue(chapter: Chapter, e: React.MouseEvent) { + e.stopPropagation(); + setEnqueueing((prev) => new Set(prev).add(chapter.id)); + await gql(ENQUEUE_DOWNLOAD, { chapterId: chapter.id }).catch(console.error); + setEnqueueing((prev) => { const n = new Set(prev); n.delete(chapter.id); return n; }); + if (activeManga) loadChapters(activeManga.id); + } + + async function markRead(chapterId: number, isRead: boolean) { + await gql(MARK_CHAPTER_READ, { id: chapterId, isRead }).catch(console.error); + setChapters((prev) => prev.map((c) => c.id === chapterId ? { ...c, isRead } : c)); + } + + async function markAllAboveRead(indexInSorted: number) { + // "above" = all chapters that appear before this one in the current sort + const targets = sortedChapters.slice(0, indexInSorted + 1); + const ids = targets.filter((c) => !c.isRead).map((c) => c.id); + if (!ids.length) return; + await gql(MARK_CHAPTERS_READ, { ids, isRead: true }).catch(console.error); + setChapters((prev) => prev.map((c) => ids.includes(c.id) ? { ...c, isRead: true } : c)); + } + + async function deleteDownloaded(chapterId: number) { + await gql(DELETE_DOWNLOADED_CHAPTERS, { ids: [chapterId] }).catch(console.error); + setChapters((prev) => prev.map((c) => c.id === chapterId ? { ...c, isDownloaded: false } : c)); + } + + async function enqueueMultiple(chapterIds: number[]) { + await gql(ENQUEUE_CHAPTERS_DOWNLOAD, { chapterIds }).catch(console.error); + if (activeManga) loadChapters(activeManga.id); + } + + function openContextMenu(e: React.MouseEvent, chapter: Chapter, indexInSorted: number) { + e.preventDefault(); + setCtx({ x: e.clientX, y: e.clientY, chapter, indexInSorted }); + } + + function buildCtxItems(ch: Chapter, indexInSorted: number): ContextMenuEntry[] { + return [ + { + label: ch.isRead ? "Mark as unread" : "Mark as read", + onClick: () => markRead(ch.id, !ch.isRead), + }, + { + label: "Mark all above as read", + onClick: () => markAllAboveRead(indexInSorted), + disabled: indexInSorted === 0, + }, + { separator: true }, + { + label: ch.isDownloaded ? "Delete download" : "Download", + onClick: () => ch.isDownloaded + ? deleteDownloaded(ch.id) + : gql(ENQUEUE_DOWNLOAD, { chapterId: ch.id }).catch(console.error), + danger: ch.isDownloaded, + }, + { separator: true }, + { + label: "Download all from here", + onClick: () => { + const fromHere = sortedChapters + .slice(indexInSorted) + .filter((c) => !c.isDownloaded) + .map((c) => c.id); + enqueueMultiple(fromHere); + }, + }, + ]; + } + + if (!activeManga) return null; + + const statusLabel = manga?.status + ? manga.status.charAt(0) + manga.status.slice(1).toLowerCase() + : null; + + return ( +
e.preventDefault()}> + {/* ── Sidebar ── */} +
+ + +
+ {activeManga.title} +
+ + {loadingManga ? ( +
+
+
+
+ ) : ( +
+

{manga?.title}

+ + {(manga?.author || manga?.artist) && ( +

+ {[manga.author, manga.artist] + .filter(Boolean) + .filter((v, i, a) => a.indexOf(v) === i) + .join(" · ")} +

+ )} + + {statusLabel && ( + + {statusLabel} + + )} + + {manga?.genre && manga.genre.length > 0 && ( +
+ {manga.genre.map((g) => {g})} +
+ )} + + {manga?.source &&

{manga.source.displayName}

} + {manga?.description &&

{manga.description}

} +
+ )} + + {/* Progress bar */} + {totalCount > 0 && ( +
+
+ {readCount} / {totalCount} read + {Math.round(progressPct)}% +
+
+
+
+
+ )} + +
+ + + {manga?.realUrl && ( + + + + )} +
+ + {/* Start / Continue reading button */} + {continueChapter && ( + + )} + + + {/* ── Download panel ── */} + {chapters.length > 0 && ( +
+ + {dlOpen && ( +
+ {continueChapter && ( + + )} + + +
+ )} +
+ )} + +

+ {totalCount} {totalCount === 1 ? "chapter" : "chapters"} +

+
+ + {/* ── Chapter list ── */} +
+ {/* List header with sort + pagination */} +
+ + + {totalPages > 1 && ( +
+ + {chapterPage} / {totalPages} + +
+ )} +
+ +
+ {loadingChapters && chapters.length === 0 ? ( + Array.from({ length: 8 }).map((_, i) => ( +
+
+
+
+ )) + ) : ( + pageChapters.map((ch) => { + const idxInSorted = sortedChapters.indexOf(ch); + return ( + + )} +
+ + ); + }) + )} +
+ + {/* Bottom pagination */} + {totalPages > 1 && ( +
+ + {chapterPage} / {totalPages} + +
+ )} +
+ + {/* Context menu */} + {ctx && ( + setCtx(null)} + /> + )} +
+ ); +} \ No newline at end of file diff --git a/src/components/settings/Settings.module.css b/src/components/settings/Settings.module.css new file mode 100644 index 0000000..1a8000c --- /dev/null +++ b/src/components/settings/Settings.module.css @@ -0,0 +1,271 @@ +/* ─── Backdrop ── */ +.backdrop { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.72); + z-index: var(--z-settings); + display: flex; + align-items: center; + justify-content: center; + animation: fadeIn 0.12s ease both; + backdrop-filter: blur(4px); + -webkit-backdrop-filter: blur(4px); +} + +/* ─── Modal shell ── */ +.modal { + width: min(720px, calc(100vw - 48px)); + height: min(520px, calc(100vh - 80px)); + display: flex; + background: var(--bg-surface); + border: 1px solid var(--border-base); + border-radius: var(--radius-xl); + overflow: hidden; + animation: scaleIn 0.16s ease both; + box-shadow: 0 0 0 1px var(--border-dim), 0 24px 64px rgba(0,0,0,0.6), 0 8px 24px rgba(0,0,0,0.4); +} + +/* ─── Sidebar ── */ +.sidebar { + width: 152px; + flex-shrink: 0; + background: var(--bg-raised); + border-right: 1px solid var(--border-dim); + display: flex; + flex-direction: column; + padding: var(--sp-5) var(--sp-3); + gap: var(--sp-1); +} + +.modalTitle { + font-family: var(--font-ui); + font-size: var(--text-2xs); + color: var(--text-faint); + letter-spacing: var(--tracking-wider); + text-transform: uppercase; + padding: 0 var(--sp-2) var(--sp-3); +} + +.nav { display: flex; flex-direction: column; gap: 1px; } + +.navItem { + display: flex; + align-items: center; + gap: var(--sp-2); + padding: 7px var(--sp-2); + border-radius: var(--radius-md); + font-size: var(--text-sm); + color: var(--text-muted); + background: none; + border: none; + cursor: pointer; + text-align: left; + width: 100%; + transition: background var(--t-fast), color var(--t-fast); +} +.navItem:hover { background: var(--bg-overlay); color: var(--text-secondary); } +.navActive { background: var(--accent-muted); color: var(--accent-fg); border: 1px solid var(--accent-dim); } +.navActive:hover { background: var(--accent-muted); color: var(--accent-fg); } + +/* ─── Content ── */ +.content { flex: 1; display: flex; flex-direction: column; overflow: hidden; } + +.contentHeader { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--sp-5) var(--sp-6) var(--sp-4); + border-bottom: 1px solid var(--border-dim); + flex-shrink: 0; +} + +.contentTitle { + font-size: var(--text-md); + font-weight: var(--weight-medium); + color: var(--text-secondary); + letter-spacing: var(--tracking-tight); +} + +.closeBtn { + display: flex; align-items: center; justify-content: center; + width: 26px; height: 26px; border-radius: var(--radius-sm); + color: var(--text-faint); + transition: color var(--t-base), background var(--t-base); +} +.closeBtn:hover { color: var(--text-muted); background: var(--bg-raised); } + +.contentBody { flex: 1; overflow-y: auto; padding: var(--sp-5) var(--sp-6); } + +/* ─── Panel / Section ── */ +.panel { display: flex; flex-direction: column; gap: var(--sp-6); } +.section { display: flex; flex-direction: column; gap: 1px; } + +.sectionTitle { + font-family: var(--font-ui); + font-size: var(--text-2xs); + color: var(--text-faint); + letter-spacing: var(--tracking-wider); + text-transform: uppercase; + margin-bottom: var(--sp-2); +} + +/* ─── Toggle ── */ +.toggleRow { + display: flex; align-items: center; justify-content: space-between; + gap: var(--sp-4); padding: 10px var(--sp-3); border-radius: var(--radius-md); + cursor: pointer; transition: background var(--t-fast); +} +.toggleRow:hover { background: var(--bg-raised); } + +.toggleInfo { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; } +.toggleLabel { font-size: var(--text-sm); color: var(--text-secondary); line-height: var(--leading-tight); } +.toggleDesc { font-size: var(--text-xs); color: var(--text-muted); line-height: var(--leading-snug); } + +.toggle { + position: relative; width: 34px; height: 18px; border-radius: var(--radius-full); + background: var(--bg-subtle); border: 1px solid var(--border-strong); flex-shrink: 0; + cursor: pointer; transition: background var(--t-base), border-color var(--t-base); +} +.toggleOn { background: var(--accent-dim); border-color: var(--accent); } +.toggleThumb { + position: absolute; top: 2px; left: 2px; width: 12px; height: 12px; + border-radius: 50%; background: var(--text-faint); + transition: transform var(--t-base), background var(--t-base); +} +.toggleOn .toggleThumb { transform: translateX(16px); background: var(--accent-fg); } + +/* ─── Stepper ── */ +.stepRow { + display: flex; align-items: center; justify-content: space-between; + gap: var(--sp-4); padding: 10px var(--sp-3); border-radius: var(--radius-md); + transition: background var(--t-fast); +} +.stepRow:hover { background: var(--bg-raised); } + +.stepControls { display: flex; align-items: center; gap: var(--sp-2); flex-shrink: 0; } + +.stepBtn { + display: flex; align-items: center; justify-content: center; + width: 24px; height: 24px; border-radius: var(--radius-sm); + border: 1px solid var(--border-strong); font-size: var(--text-base); + color: var(--text-muted); transition: background var(--t-base), color var(--t-base); + line-height: 1; +} +.stepBtn:hover:not(:disabled) { background: var(--bg-overlay); color: var(--text-primary); } +.stepBtn:disabled { opacity: 0.25; cursor: default; } + +.stepVal { + font-family: var(--font-ui); font-size: var(--text-sm); color: var(--text-secondary); + min-width: 28px; text-align: center; letter-spacing: var(--tracking-wide); +} + +/* ─── Select ── */ +.select { + background: var(--bg-raised); border: 1px solid var(--border-strong); + border-radius: var(--radius-md); padding: 5px 10px; color: var(--text-secondary); + font-family: var(--font-ui); font-size: var(--text-xs); letter-spacing: var(--tracking-wide); + outline: none; cursor: pointer; flex-shrink: 0; transition: border-color var(--t-base); + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' fill='none'%3E%3Cpath d='M0 0l5 6 5-6' fill='%23888'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 8px center; + padding-right: 24px; +} +.select:focus { border-color: var(--border-focus); } +.select option { background: var(--bg-raised); color: var(--text-secondary); } + +/* ─── Scale ── */ +.scaleRow { + display: flex; align-items: center; gap: var(--sp-3); + padding: 10px var(--sp-3); border-radius: var(--radius-md); +} +.scaleSlider { flex: 1; } +.scaleVal { + font-family: var(--font-ui); font-size: var(--text-sm); color: var(--text-secondary); + min-width: 36px; text-align: right; letter-spacing: var(--tracking-wide); +} +.scaleHint { + display: flex; flex-wrap: wrap; gap: var(--sp-1); + padding: 0 var(--sp-3) var(--sp-2); +} +.scalePreset { + font-family: var(--font-ui); font-size: var(--text-2xs); letter-spacing: var(--tracking-wide); + padding: 3px 8px; border-radius: var(--radius-sm); border: 1px solid var(--border-dim); + background: none; color: var(--text-faint); cursor: pointer; + transition: color var(--t-base), border-color var(--t-base), background var(--t-base); +} +.scalePreset:hover { color: var(--text-muted); border-color: var(--border-strong); } +.scalePresetActive { + background: var(--accent-muted); border-color: var(--accent-dim); color: var(--accent-fg); +} + +/* ─── Text input ── */ +.textInput { + background: var(--bg-raised); border: 1px solid var(--border-strong); + border-radius: var(--radius-md); padding: 5px 10px; color: var(--text-secondary); + font-family: var(--font-ui); font-size: var(--text-xs); letter-spacing: var(--tracking-wide); + outline: none; flex-shrink: 0; width: 180px; + transition: border-color var(--t-base); +} +.textInput:focus { border-color: var(--border-focus); } + +/* ─── Keybinds ── */ +.kbHeader { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--sp-2); } +.kbHint { font-size: var(--text-xs); color: var(--text-faint); padding: 0 var(--sp-3) var(--sp-3); } +.resetAllBtn { + font-family: var(--font-ui); font-size: var(--text-xs); color: var(--text-faint); + letter-spacing: var(--tracking-wide); padding: 3px 8px; border-radius: var(--radius-sm); + border: 1px solid var(--border-dim); background: none; cursor: pointer; + transition: color var(--t-base), border-color var(--t-base); +} +.resetAllBtn:hover { color: var(--color-error); border-color: var(--color-error); } + +.kbList { display: flex; flex-direction: column; gap: 1px; } + +.kbRow { + display: flex; align-items: center; justify-content: space-between; + gap: var(--sp-4); padding: 8px var(--sp-3); border-radius: var(--radius-md); + transition: background var(--t-fast); +} +.kbRow:hover { background: var(--bg-raised); } + +.kbLabel { font-size: var(--text-sm); color: var(--text-secondary); flex: 1; } +.kbRight { display: flex; align-items: center; gap: var(--sp-2); flex-shrink: 0; } + +.kbBind { + font-family: var(--font-ui); font-size: var(--text-xs); letter-spacing: var(--tracking-wide); + padding: 4px 12px; border-radius: var(--radius-sm); + border: 1px solid var(--border-strong); background: var(--bg-overlay); + color: var(--text-secondary); cursor: pointer; min-width: 100px; text-align: center; + transition: border-color var(--t-base), background var(--t-base), color var(--t-base); +} +.kbBind:hover { border-color: var(--accent); color: var(--accent-fg); } +.kbBindListening { + border-color: var(--accent); background: var(--accent-muted); color: var(--accent-fg); + animation: pulse 1s ease infinite; +} + +.kbReset { + font-size: var(--text-base); color: var(--text-faint); width: 22px; height: 22px; + border-radius: var(--radius-sm); border: 1px solid transparent; background: none; + cursor: pointer; display: flex; align-items: center; justify-content: center; + transition: color var(--t-base), border-color var(--t-base); +} +.kbReset:hover:not(:disabled) { color: var(--text-muted); border-color: var(--border-dim); } +.kbReset:disabled { opacity: 0.2; cursor: default; } + +/* ─── About ── */ +.aboutBlock { + padding: var(--sp-3); background: var(--bg-raised); border-radius: var(--radius-md); + border: 1px solid var(--border-dim); +} +.aboutLine { font-size: var(--text-sm); color: var(--text-secondary); line-height: var(--leading-base); } +.dangerBtn { + font-family: var(--font-ui); font-size: var(--text-xs); letter-spacing: var(--tracking-wide); + padding: 5px 12px; border-radius: var(--radius-md); + background: none; border: 1px solid var(--color-error); + color: var(--color-error); cursor: pointer; flex-shrink: 0; + transition: background var(--t-base); +} +.dangerBtn:hover:not(:disabled) { background: var(--color-error-bg); } +.dangerBtn:disabled { opacity: 0.3; cursor: default; } \ No newline at end of file diff --git a/src/components/settings/Settings.tsx b/src/components/settings/Settings.tsx new file mode 100644 index 0000000..ea58e21 --- /dev/null +++ b/src/components/settings/Settings.tsx @@ -0,0 +1,435 @@ +import { useEffect, useRef, useState, useCallback } from "react"; +import { X, Book, Image, Sliders, Info, Keyboard, Gear } from "@phosphor-icons/react"; +import { useStore } from "../../store"; +import { KEYBIND_LABELS, DEFAULT_KEYBINDS, eventToKeybind, type Keybinds } from "../../lib/keybinds"; +import type { Settings, FitMode } from "../../store"; +import s from "./Settings.module.css"; + +type Tab = "general" | "reader" | "library" | "performance" | "keybinds" | "about"; + +const TABS: { id: Tab; label: string; icon: React.ReactNode }[] = [ + { id: "general", label: "General", icon: }, + { id: "reader", label: "Reader", icon: }, + { id: "library", label: "Library", icon: }, + { id: "performance", label: "Performance", icon: }, + { id: "keybinds", label: "Keybinds", icon: }, + { id: "about", label: "About", icon: }, +]; + +// ── Primitives ──────────────────────────────────────────────────────────────── + +function Toggle({ checked, onChange, label, description }: { + checked: boolean; onChange: (v: boolean) => void; label: string; description?: string; +}) { + return ( + + ); +} + +function Stepper({ value, onChange, min, max, step = 1, label, description }: { + value: number; onChange: (v: number) => void; + min: number; max: number; step?: number; label: string; description?: string; +}) { + return ( +
+
+ {label} + {description && {description}} +
+
+ + {value} + +
+
+ ); +} + +function SelectRow({ value, options, onChange, label, description }: { + value: string; + options: { value: string; label: string }[]; + onChange: (v: string) => void; + label: string; + description?: string; +}) { + return ( +
+
+ {label} + {description && {description}} +
+ +
+ ); +} + +function TextRow({ value, onChange, label, description, placeholder }: { + value: string; onChange: (v: string) => void; + label: string; description?: string; placeholder?: string; +}) { + return ( +
+
+ {label} + {description && {description}} +
+ onChange(e.target.value)} + placeholder={placeholder} spellCheck={false} /> +
+ ); +} + +// ── Tabs ────────────────────────────────────────────────────────────────────── + +function GeneralTab({ settings, update }: { settings: Settings; update: (p: Partial) => void }) { + return ( +
+
+

Interface Scale

+
+ update({ uiScale: Number(e.target.value) })} + className={s.scaleSlider} /> + {settings.uiScale}% + +
+

+ {[70, 80, 90, 100, 110, 125, 150].map((v) => ( + + ))} +

+
+
+

Server

+ update({ serverUrl: v })} + placeholder="http://localhost:4567" /> + update({ serverBinary: v })} + placeholder="tachidesk-server" /> + update({ autoStartServer: v })} /> +
+
+ ); +} + +function ReaderTab({ settings, update }: { settings: Settings; update: (p: Partial) => void }) { + return ( +
+
+

Page Layout

+ update({ pageStyle: v as Settings["pageStyle"] })} /> + update({ readingDirection: v as Settings["readingDirection"] })} /> + update({ offsetDoubleSpreads: v })} /> + update({ pageGap: v })} /> +
+ +
+

Fit & Zoom

+ update({ fitMode: v as FitMode })} /> +
+
+ Max page width + Pixel cap for fit-width mode. Ctrl+scroll in reader to adjust live. +
+
+ + {settings.maxPageWidth ?? 900}px + +
+
+ update({ optimizeContrast: v })} /> +
+ +
+

Behaviour

+ update({ autoMarkRead: v })} /> + update({ preloadPages: v })} /> +
+
+ ); +} + +function LibraryTab({ settings, update }: { settings: Settings; update: (p: Partial) => void }) { + const clearHistory = useStore((s) => s.clearHistory); + const historyLen = useStore((s) => s.history.length); + return ( +
+
+

Display

+ update({ libraryCropCovers: v })} /> + update({ showNsfw: v })} /> + update({ libraryPageSize: v })} /> +
+
+

Chapters

+ update({ chapterSortDir: v as Settings["chapterSortDir"] })} /> + update({ chapterPageSize: v })} /> +
+
+

Extensions

+ update({ preferredExtensionLang: v })} /> +
+
+

History

+
+
+ Reading history + {historyLen} entries stored +
+ +
+
+
+ ); +} + +function PerformanceTab({ settings, update }: { settings: Settings; update: (p: Partial) => void }) { + return ( +
+
+

Rendering

+ update({ gpuAcceleration: v })} /> +
+
+

Interface

+ update({ compactSidebar: v })} /> +
+
+ ); +} + +function KeybindsTab({ settings, update, reset }: { + settings: Settings; update: (p: Partial) => void; reset: () => void; +}) { + const [listening, setListening] = useState(null); + + useEffect(() => { + if (!listening) return; + function onKey(e: KeyboardEvent) { + e.preventDefault(); e.stopPropagation(); + const bind = eventToKeybind(e); + if (!bind) return; + update({ keybinds: { ...settings.keybinds, [listening!]: bind } }); + setListening(null); + } + window.addEventListener("keydown", onKey, true); + return () => window.removeEventListener("keydown", onKey, true); + }, [listening, settings.keybinds]); + + return ( +
+
+
+

Keyboard shortcuts

+ +
+

Click a key to rebind, then press the new combination.

+
+ {(Object.keys(KEYBIND_LABELS) as (keyof Keybinds)[]).map((key) => { + const isListening = listening === key; + const isDefault = settings.keybinds[key] === DEFAULT_KEYBINDS[key]; + return ( +
+ {KEYBIND_LABELS[key]} +
+ + +
+
+ ); + })} +
+
+
+ ); +} + +function AboutTab() { + return ( +
+
+

Moku

+
+

A manga reader frontend for Suwayomi / Tachidesk.

+

+ Built with Tauri + React. Connects to tachidesk-server. +

+
+
+
+ ); +} + +// ── Modal ───────────────────────────────────────────────────────────────────── + +export default function SettingsModal() { + const [tab, setTab] = useState("general"); + const closeSettings = useStore((s) => s.closeSettings); + const settings = useStore((s) => s.settings); + const updateSettings = useStore((s) => s.updateSettings); + const resetKeybinds = useStore((s) => s.resetKeybinds); + const backdropRef = useRef(null); + + const handleBackdrop = useCallback( + (e: React.MouseEvent) => { if (e.target === backdropRef.current) closeSettings(); }, + [closeSettings] + ); + + useEffect(() => { + const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") closeSettings(); }; + window.addEventListener("keydown", onKey); + return () => window.removeEventListener("keydown", onKey); + }, [closeSettings]); + + return ( +
+
+
+

Settings

+ +
+
+
+

{TABS.find((t) => t.id === tab)?.label}

+ +
+
+ {tab === "general" && } + {tab === "reader" && } + {tab === "library" && } + {tab === "performance" && } + {tab === "keybinds" && } + {tab === "about" && } +
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/sources/SourceBrowse.module.css b/src/components/sources/SourceBrowse.module.css new file mode 100644 index 0000000..dd0f274 --- /dev/null +++ b/src/components/sources/SourceBrowse.module.css @@ -0,0 +1,243 @@ +.root { + display: flex; + flex-direction: column; + height: 100%; + overflow: hidden; + animation: fadeIn 0.14s ease both; +} + +.header { + display: flex; + align-items: center; + gap: var(--sp-3); + padding: var(--sp-4) var(--sp-6); + border-bottom: 1px solid var(--border-dim); + flex-shrink: 0; +} + +.back { + display: flex; + align-items: center; + gap: var(--sp-2); + color: var(--text-muted); + font-size: var(--text-xs); + font-family: var(--font-ui); + letter-spacing: var(--tracking-wide); + text-transform: uppercase; + transition: color var(--t-base); + flex-shrink: 0; +} + +.back:hover { color: var(--text-secondary); } + +.sourceName { + font-size: var(--text-base); + font-weight: var(--weight-medium); + color: var(--text-secondary); + letter-spacing: var(--tracking-tight); +} + +.toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--sp-3); + padding: var(--sp-3) var(--sp-6); + border-bottom: 1px solid var(--border-dim); + flex-shrink: 0; + flex-wrap: wrap; +} + +.tabs { display: flex; gap: 2px; } + +.tab { + font-family: var(--font-ui); + font-size: var(--text-xs); + letter-spacing: var(--tracking-wide); + padding: 4px 10px; + border-radius: var(--radius-md); + border: none; + background: none; + color: var(--text-muted); + cursor: pointer; + transition: background var(--t-base), color var(--t-base); +} + +.tab:hover { background: var(--bg-raised); color: var(--text-secondary); } +.tabActive { background: var(--accent-muted); color: var(--accent-fg); } +.tabActive:hover { background: var(--accent-muted); color: var(--accent-fg); } + +.searchWrap { + position: relative; + display: flex; + align-items: center; +} + +.searchIcon { + position: absolute; + left: 9px; + color: var(--text-faint); + pointer-events: none; +} + +.search { + background: var(--bg-raised); + border: 1px solid var(--border-dim); + border-radius: var(--radius-md); + padding: 5px 10px 5px 26px; + color: var(--text-primary); + font-size: var(--text-sm); + width: 200px; + outline: none; + transition: border-color var(--t-base); +} + +.search::placeholder { color: var(--text-faint); } +.search:focus { border-color: var(--border-strong); } + +/* ─── Responsive grid ─────────────────────────────────────────────────────── */ +/* + Adapts to screen width: + - narrow (< ~640px): 2 columns + - default (~640-900px): auto-fill ~120px → 4–6 cols + - wide (> ~900px): more columns, stays readable +*/ +.grid, .loadingGrid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(clamp(100px, 14vw, 140px), 1fr)); + gap: var(--sp-4); + padding: var(--sp-5) var(--sp-6); + overflow-y: auto; + flex: 1; + align-content: start; + /* GPU for smooth scroll */ + will-change: scroll-position; + -webkit-overflow-scrolling: touch; + contain: layout style; +} + +.card { + background: none; + border: none; + padding: 0; + cursor: pointer; + text-align: left; +} + +.card:hover .cover { filter: brightness(1.06); } +.card:hover .title { color: var(--text-primary); } + +.coverWrap { + position: relative; + aspect-ratio: 2 / 3; + overflow: hidden; + border-radius: var(--radius-md); + background: var(--bg-raised); + border: 1px solid var(--border-dim); + transform: translateZ(0); +} + +.cover { + width: 100%; + height: 100%; + object-fit: cover; + transition: filter var(--t-base); + will-change: filter; +} + +.inLibraryBadge { + position: absolute; + bottom: var(--sp-1); + left: var(--sp-1); + font-family: var(--font-ui); + font-size: var(--text-2xs); + letter-spacing: var(--tracking-wide); + text-transform: uppercase; + background: var(--accent-muted); + color: var(--accent-fg); + border: 1px solid var(--accent-dim); + padding: 2px 5px; + border-radius: var(--radius-sm); +} + +.title { + margin-top: var(--sp-2); + font-size: var(--text-sm); + /* Use secondary not muted - readable against dark bg */ + color: var(--text-secondary); + line-height: var(--leading-snug); + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + transition: color var(--t-base); +} + +/* Skeleton */ +.cardSkeleton { padding: 0; } + +.coverSkeleton { + aspect-ratio: 2 / 3; + border-radius: var(--radius-md); +} + +.titleSkeleton { + height: 11px; + margin-top: var(--sp-2); + width: 75%; +} + +/* Pagination */ +.pagination { + display: flex; + align-items: center; + justify-content: center; + gap: var(--sp-4); + padding: var(--sp-4); + border-top: 1px solid var(--border-dim); + flex-shrink: 0; +} + +.pageBtn { + display: flex; + align-items: center; + gap: var(--sp-2); + font-family: var(--font-ui); + font-size: var(--text-xs); + letter-spacing: var(--tracking-wide); + color: var(--text-muted); + border: 1px solid var(--border-dim); + border-radius: var(--radius-md); + padding: 5px 12px; + background: none; + cursor: pointer; + transition: color var(--t-base), border-color var(--t-base), background var(--t-base); +} + +.pageBtn:hover:not(:disabled) { + color: var(--text-primary); + border-color: var(--border-strong); + background: var(--bg-raised); +} + +.pageBtn:disabled { opacity: 0.3; cursor: default; } + +.pageNum { + font-family: var(--font-ui); + font-size: var(--text-xs); + color: var(--text-muted); + letter-spacing: var(--tracking-wider); + min-width: 24px; + text-align: center; +} + +.empty { + display: flex; + align-items: center; + justify-content: center; + flex: 1; + color: var(--text-muted); + font-family: var(--font-ui); + font-size: var(--text-xs); + letter-spacing: var(--tracking-wide); +} \ No newline at end of file diff --git a/src/components/sources/SourceBrowse.tsx b/src/components/sources/SourceBrowse.tsx new file mode 100644 index 0000000..5c90981 --- /dev/null +++ b/src/components/sources/SourceBrowse.tsx @@ -0,0 +1,157 @@ +import { useEffect, useState, useRef } from "react"; +import { ArrowLeft, MagnifyingGlass, ArrowLeft as Prev, ArrowRight as Next } from "@phosphor-icons/react"; +import { gql, thumbUrl } from "../../lib/client"; +import { FETCH_SOURCE_MANGA } from "../../lib/queries"; +import { useStore } from "../../store"; +import type { Manga } from "../../lib/types"; +import s from "./SourceBrowse.module.css"; + +type BrowseType = "POPULAR" | "LATEST" | "SEARCH"; + +export default function SourceBrowse() { + const activeSource = useStore((state) => state.activeSource); + const setActiveSource = useStore((state) => state.setActiveSource); + const setActiveManga = useStore((state) => state.setActiveManga); + const setNavPage = useStore((state) => state.setNavPage); + + const [mangas, setMangas] = useState([]); + const [loading, setLoading] = useState(true); + const [page, setPage] = useState(1); + const [hasNextPage, setHasNextPage] = useState(false); + const [browseType, setBrowseType] = useState("POPULAR"); + const [search, setSearch] = useState(""); + const [searchInput, setSearchInput] = useState(""); + const searchRef = useRef(null); + + async function fetch(type: BrowseType, p: number, q: string) { + if (!activeSource) return; + setLoading(true); + setMangas([]); + gql<{ fetchSourceManga: { mangas: Manga[]; hasNextPage: boolean } }>( + FETCH_SOURCE_MANGA, + { source: activeSource.id, type, page: p, query: q || null } + ) + .then((d) => { + setMangas(d.fetchSourceManga.mangas); + setHasNextPage(d.fetchSourceManga.hasNextPage); + }) + .catch(console.error) + .finally(() => setLoading(false)); + } + + useEffect(() => { + fetch(browseType, page, search); + }, [activeSource?.id, browseType, page, search]); + + function submitSearch() { + const q = searchInput.trim(); + setSearch(q); + setBrowseType("SEARCH"); + setPage(1); + } + + function setMode(mode: BrowseType) { + if (mode === browseType) return; + setBrowseType(mode); + setSearch(""); + setSearchInput(""); + setPage(1); + } + + function openManga(m: Manga) { + setActiveManga(m); + setNavPage("library"); + } + + if (!activeSource) return null; + + return ( +
+
+ + {activeSource.displayName} +
+ +
+
+ {(["POPULAR", "LATEST"] as BrowseType[]).map((mode) => ( + + ))} + {search && ( + + )} +
+ +
+ + setSearchInput(e.target.value)} + onKeyDown={(e) => e.key === "Enter" && submitSearch()} + /> +
+
+ + {loading ? ( +
+ {Array.from({ length: 18 }).map((_, i) => ( +
+
+
+
+ ))} +
+ ) : mangas.length === 0 ? ( +
No results.
+ ) : ( +
+ {mangas.map((m) => ( + + ))} +
+ )} + + {!loading && (page > 1 || hasNextPage) && ( +
+ + {page} + +
+ )} +
+ ); +} \ No newline at end of file diff --git a/src/components/sources/SourceList.module.css b/src/components/sources/SourceList.module.css new file mode 100644 index 0000000..40a7e81 --- /dev/null +++ b/src/components/sources/SourceList.module.css @@ -0,0 +1,150 @@ +.root { + padding: var(--sp-6); + overflow-y: auto; + height: 100%; + animation: fadeIn 0.14s ease both; +} + +.header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--sp-5); +} + +.heading { + font-family: var(--font-ui); + font-size: var(--text-xs); + font-weight: var(--weight-normal); + color: var(--text-faint); + letter-spacing: var(--tracking-wider); + text-transform: uppercase; +} + +.searchWrap { + position: relative; + display: flex; + align-items: center; +} + +.searchIcon { + position: absolute; + left: 9px; + color: var(--text-faint); + pointer-events: none; +} + +.search { + background: var(--bg-raised); + border: 1px solid var(--border-dim); + border-radius: var(--radius-md); + padding: 5px 10px 5px 26px; + color: var(--text-primary); + font-size: var(--text-sm); + width: 180px; + outline: none; + transition: border-color var(--t-base); +} + +.search::placeholder { color: var(--text-faint); } +.search:focus { border-color: var(--border-strong); } + +.langRow { + display: flex; + flex-wrap: wrap; + gap: var(--sp-1); + margin-bottom: var(--sp-4); +} + +.langBtn { + font-family: var(--font-ui); + font-size: var(--text-2xs); + letter-spacing: var(--tracking-wider); + padding: 3px 8px; + border-radius: var(--radius-sm); + border: 1px solid var(--border-dim); + background: none; + color: var(--text-faint); + cursor: pointer; + transition: color var(--t-base), border-color var(--t-base), background var(--t-base); +} + +.langBtn:hover { color: var(--text-muted); border-color: var(--border-strong); } + +.langBtnActive { + background: var(--accent-muted); + border-color: var(--accent-dim); + color: var(--accent-fg); +} + +.langBtnActive:hover { + background: var(--accent-muted); + color: var(--accent-fg); +} + +.list { display: flex; flex-direction: column; gap: 1px; } + +.row { + display: flex; + align-items: center; + gap: var(--sp-3); + padding: 9px var(--sp-3); + border-radius: var(--radius-md); + border: 1px solid transparent; + background: none; + text-align: left; + width: 100%; + cursor: pointer; + transition: background var(--t-fast), border-color var(--t-fast); +} + +.row:hover { background: var(--bg-raised); border-color: var(--border-dim); } + +.icon { + width: 32px; + height: 32px; + border-radius: var(--radius-md); + object-fit: cover; + flex-shrink: 0; + background: var(--bg-raised); +} + +.info { flex: 1; display: flex; flex-direction: column; gap: 2px; overflow: hidden; } + +.name { + font-size: var(--text-base); + font-weight: var(--weight-medium); + color: var(--text-secondary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.meta { + font-family: var(--font-ui); + font-size: var(--text-2xs); + color: var(--text-faint); + letter-spacing: var(--tracking-wide); +} + +.arrow { + font-family: var(--font-ui); + font-size: var(--text-xs); + color: var(--text-faint); + flex-shrink: 0; + opacity: 0; + transition: opacity var(--t-base); +} + +.row:hover .arrow { opacity: 1; } + +.empty { + display: flex; + align-items: center; + justify-content: center; + height: 160px; + color: var(--text-faint); + font-family: var(--font-ui); + font-size: var(--text-xs); + letter-spacing: var(--tracking-wide); +} \ No newline at end of file diff --git a/src/components/sources/SourceList.tsx b/src/components/sources/SourceList.tsx new file mode 100644 index 0000000..524ef65 --- /dev/null +++ b/src/components/sources/SourceList.tsx @@ -0,0 +1,92 @@ +import { useEffect, useState } from "react"; +import { MagnifyingGlass, CircleNotch } from "@phosphor-icons/react"; +import { gql, thumbUrl } from "../../lib/client"; +import { GET_SOURCES } from "../../lib/queries"; +import { useStore } from "../../store"; +import type { Source } from "../../lib/types"; +import s from "./SourceList.module.css"; + +export default function SourceList() { + const [sources, setSources] = useState([]); + const [loading, setLoading] = useState(true); + const [lang, setLang] = useState("all"); + const [search, setSearch] = useState(""); + const setActiveSource = useStore((state) => state.setActiveSource); + + useEffect(() => { + gql<{ sources: { nodes: Source[] } }>(GET_SOURCES) + .then((d) => setSources(d.sources.nodes)) + .catch(console.error) + .finally(() => setLoading(false)); + }, []); + + const langs = ["all", ...Array.from(new Set(sources.map((s) => s.lang))).sort()]; + + const filtered = sources.filter((src) => { + if (src.id === "0") return false; // hide local source + const matchLang = lang === "all" || src.lang === lang; + const matchSearch = + src.name.toLowerCase().includes(search.toLowerCase()) || + src.displayName.toLowerCase().includes(search.toLowerCase()); + return matchLang && matchSearch; + }); + + return ( +
+
+

Sources

+
+ + setSearch(e.target.value)} + /> +
+
+ +
+ {langs.map((l) => ( + + ))} +
+ + {loading ? ( +
+ +
+ ) : filtered.length === 0 ? ( +
No sources found.
+ ) : ( +
+ {filtered.map((src) => ( + + ))} +
+ )} +
+ ); +} \ No newline at end of file diff --git a/src/lib/client.ts b/src/lib/client.ts new file mode 100644 index 0000000..53599e7 --- /dev/null +++ b/src/lib/client.ts @@ -0,0 +1,48 @@ +const SUWAYOMI = "http://127.0.0.1:4567"; +const GQL = `${SUWAYOMI}/api/graphql`; + +export function thumbUrl(path: string): string { + return `${SUWAYOMI}${path}`; +} + +interface GQLResponse { + data: T; + errors?: { message: string }[]; +} + +// Retry with exponential backoff — Suwayomi may not be ready on first load +async function fetchWithRetry(url: string, init: RequestInit, retries = 8, delayMs = 500): Promise { + for (let i = 0; i < retries; i++) { + try { + const res = await fetch(url, init); + return res; + } catch (e) { + if (i === retries - 1) throw e; + await new Promise((r) => setTimeout(r, delayMs * Math.pow(1.5, i))); + } + } + throw new Error("unreachable"); +} + +export async function gql( + query: string, + variables?: Record +): Promise { + const res = await fetchWithRetry(GQL, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ query, variables }), + }); + + if (!res.ok) { + throw new Error(`Suwayomi HTTP ${res.status}`); + } + + const json: GQLResponse = await res.json(); + + if (json.errors?.length) { + throw new Error(json.errors[0].message); + } + + return json.data; +} \ No newline at end of file diff --git a/src/lib/keybinds.ts b/src/lib/keybinds.ts new file mode 100644 index 0000000..62f05db --- /dev/null +++ b/src/lib/keybinds.ts @@ -0,0 +1,65 @@ +export interface Keybinds { + pageRight: string; + pageLeft: string; + firstPage: string; + lastPage: string; + chapterRight: string; + chapterLeft: string; + exitReader: string; + close: string; + toggleReadingDirection: string; + togglePageStyle: string; + toggleOffsetDoubleSpreads: string; + toggleFullscreen: string; + openSettings: string; + toggleSidebar: string; +} + +export const DEFAULT_KEYBINDS: Keybinds = { + pageRight: "ArrowRight", + pageLeft: "ArrowLeft", + firstPage: "ctrl+ArrowLeft", + lastPage: "ctrl+ArrowRight", + chapterRight: "]", + chapterLeft: "[", + exitReader: "Backspace", + close: "Escape", + toggleReadingDirection: "d", + togglePageStyle: "q", + toggleOffsetDoubleSpreads: "u", + toggleFullscreen: "f", + openSettings: "o", + toggleSidebar: "s", +}; + +export const KEYBIND_LABELS: Record = { + pageRight: "Turn page right", + pageLeft: "Turn page left", + firstPage: "First page", + lastPage: "Last page", + chapterRight: "Change chapter right", + chapterLeft: "Change chapter left", + exitReader: "Exit reader", + close: "Close", + toggleReadingDirection: "Toggle reading direction", + togglePageStyle: "Toggle page style", + toggleOffsetDoubleSpreads: "Toggle double page offset", + toggleFullscreen: "Toggle fullscreen", + openSettings: "Show settings menu", + toggleSidebar: "Toggle sidebar", +}; + +export function eventToKeybind(e: KeyboardEvent): string { + if (["Control", "Alt", "Shift", "Meta"].includes(e.key)) return ""; + const parts: string[] = []; + if (e.ctrlKey) parts.push("ctrl"); + if (e.altKey) parts.push("alt"); + if (e.shiftKey) parts.push("shift"); + if (e.metaKey) parts.push("meta"); + parts.push(e.key); + return parts.join("+"); +} + +export function matchesKeybind(e: KeyboardEvent, bind: string): boolean { + return eventToKeybind(e) === bind; +} \ No newline at end of file diff --git a/src/lib/queries.ts b/src/lib/queries.ts new file mode 100644 index 0000000..eb542fe --- /dev/null +++ b/src/lib/queries.ts @@ -0,0 +1,350 @@ +// ── Library ────────────────────────────────────────────────────────────────── + +// Full library query with chapter progress — only used for inLibrary manga +export const GET_LIBRARY = ` + query GetLibrary { + mangas(condition: { inLibrary: true }) { + nodes { + id + title + thumbnailUrl + inLibrary + downloadCount + unreadCount + chapters { + totalCount + } + } + } + } +`; + +// Lightweight query for browse/search (no progress needed) +export const GET_ALL_MANGA = ` + query GetAllManga { + mangas { + nodes { + id + title + thumbnailUrl + inLibrary + downloadCount + } + } + } +`; + +export const GET_MANGA = ` + query GetManga($id: Int!) { + manga(id: $id) { + id + title + description + thumbnailUrl + status + author + artist + genre + inLibrary + realUrl + source { + id + name + displayName + } + } + } +`; + +export const GET_CHAPTERS = ` + query GetChapters($mangaId: Int!) { + chapters(condition: { mangaId: $mangaId }) { + nodes { + id + name + chapterNumber + sourceOrder + isRead + isDownloaded + isBookmarked + pageCount + mangaId + uploadDate + realUrl + lastPageRead + scanlator + } + } + } +`; + +export const FETCH_CHAPTERS = ` + mutation FetchChapters($mangaId: Int!) { + fetchChapters(input: { mangaId: $mangaId }) { + chapters { + id + name + chapterNumber + sourceOrder + isRead + isDownloaded + isBookmarked + pageCount + mangaId + uploadDate + realUrl + lastPageRead + scanlator + } + } + } +`; + +export const FETCH_CHAPTER_PAGES = ` + mutation FetchChapterPages($chapterId: Int!) { + fetchChapterPages(input: { chapterId: $chapterId }) { + pages + } + } +`; + +export const UPDATE_MANGA = ` + mutation UpdateManga($id: Int!, $inLibrary: Boolean) { + updateManga(input: { id: $id, patch: { inLibrary: $inLibrary } }) { + manga { + id + inLibrary + } + } + } +`; + +export const MARK_CHAPTER_READ = ` + mutation MarkChapterRead($id: Int!, $isRead: Boolean!) { + updateChapter(input: { id: $id, patch: { isRead: $isRead } }) { + chapter { + id + isRead + } + } + } +`; + +export const MARK_CHAPTERS_READ = ` + mutation MarkChaptersRead($ids: [Int!]!, $isRead: Boolean!) { + updateChapters(input: { ids: $ids, patch: { isRead: $isRead } }) { + chapters { + id + isRead + } + } + } +`; + +export const DELETE_DOWNLOADED_CHAPTERS = ` + mutation DeleteDownloadedChapters($ids: [Int!]!) { + deleteDownloadedChapters(input: { ids: $ids }) { + chapters { + id + isDownloaded + } + } + } +`; + +// ── Downloads ───────────────────────────────────────────────────────────────── +// Updated to include manga title, thumbnail, and pageCount + +export const GET_DOWNLOAD_STATUS = ` + query GetDownloadStatus { + downloadStatus { + state + queue { + progress + state + chapter { + id + name + pageCount + mangaId + manga { + id + title + thumbnailUrl + } + } + } + } + } +`; + +export const ENQUEUE_DOWNLOAD = ` + mutation EnqueueDownload($chapterId: Int!) { + enqueueChapterDownload(input: { id: $chapterId }) { + downloadStatus { + state + queue { + progress + state + chapter { + id + name + pageCount + mangaId + manga { id title thumbnailUrl } + } + } + } + } + } +`; + +export const ENQUEUE_CHAPTERS_DOWNLOAD = ` + mutation EnqueueChaptersDownload($chapterIds: [Int!]!) { + enqueueChapterDownloads(input: { ids: $chapterIds }) { + downloadStatus { + state + } + } + } +`; + +export const DEQUEUE_DOWNLOAD = ` + mutation DequeueDownload($chapterId: Int!) { + dequeueChapterDownload(input: { id: $chapterId }) { + downloadStatus { + state + } + } + } +`; + +export const START_DOWNLOADER = ` + mutation StartDownloader { + startDownloader { + downloadStatus { state } + } + } +`; + +export const STOP_DOWNLOADER = ` + mutation StopDownloader { + stopDownloader { + downloadStatus { state } + } + } +`; + +export const CLEAR_DOWNLOADER = ` + mutation ClearDownloader { + clearDownloader { + downloadStatus { + state + queue { + progress + state + chapter { + id name pageCount mangaId + manga { id title thumbnailUrl } + } + } + } + } + } +`; + +// ── Sources ─────────────────────────────────────────────────────────────────── + +export const GET_SOURCES = ` + query GetSources { + sources { + nodes { + id + name + lang + displayName + iconUrl + isNsfw + } + } + } +`; + +export const FETCH_SOURCE_MANGA = ` + mutation FetchSourceManga($source: LongString!, $type: FetchSourceMangaType!, $page: Int!, $query: String) { + fetchSourceManga(input: { source: $source, type: $type, page: $page, query: $query }) { + mangas { + id + title + thumbnailUrl + inLibrary + } + hasNextPage + } + } +`; + +// ── Extensions ──────────────────────────────────────────────────────────────── + +export const GET_EXTENSIONS = ` + query GetExtensions { + extensions { + nodes { + apkName + pkgName + name + lang + versionName + isInstalled + isObsolete + hasUpdate + iconUrl + } + } + } +`; + +export const FETCH_EXTENSIONS = ` + mutation FetchExtensions { + fetchExtensions(input: {}) { + extensions { + apkName + pkgName + name + lang + versionName + isInstalled + isObsolete + hasUpdate + iconUrl + } + } + } +`; + +export const UPDATE_EXTENSION = ` + mutation UpdateExtension($id: String!, $install: Boolean, $uninstall: Boolean, $update: Boolean) { + updateExtension(input: { id: $id, patch: { install: $install, uninstall: $uninstall, update: $update } }) { + extension { + apkName + pkgName + name + isInstalled + hasUpdate + } + } + } +`; + +export const INSTALL_EXTERNAL_EXTENSION = ` + mutation InstallExternalExtension($url: String!) { + installExternalExtension(input: { extensionUrl: $url }) { + extension { + apkName + pkgName + name + isInstalled + } + } + } +`; \ No newline at end of file diff --git a/src/lib/types.ts b/src/lib/types.ts new file mode 100644 index 0000000..f29ca1b --- /dev/null +++ b/src/lib/types.ts @@ -0,0 +1,89 @@ +export interface Manga { + id: number; + title: string; + thumbnailUrl: string; + inLibrary: boolean; + downloadCount?: number; + unreadCount?: number; + description?: string | null; + status?: string | null; + author?: string | null; + artist?: string | null; + genre?: string[]; + realUrl?: string | null; + source?: { + id: string; + name: string; + displayName: string; + } | null; +} + +export interface Chapter { + id: number; + name: string; + chapterNumber: number; + sourceOrder: number; + isRead: boolean; + isDownloaded: boolean; + isBookmarked: boolean; + pageCount: number; + mangaId: number; + uploadDate?: string | null; + realUrl?: string | null; + lastPageRead?: number; + scanlator?: string | null; +} + +export interface MangaDetail extends Manga { + description: string | null; + author: string | null; + artist: string | null; + status: string | null; + genre: string[]; +} + +export interface Source { + id: string; + name: string; + lang: string; + displayName: string; + iconUrl: string; + isNsfw: boolean; +} + +export interface Extension { + apkName: string; + pkgName: string; + name: string; + lang: string; + versionName: string; + isInstalled: boolean; + isObsolete: boolean; + hasUpdate: boolean; + iconUrl: string; +} + +export interface DownloadQueueItem { + progress: number; + state: "QUEUED" | "DOWNLOADING" | "FINISHED" | "ERROR"; + chapter: { + id: number; + name: string; + mangaId: number; + pageCount: number; + manga: { + id: number; + title: string; + thumbnailUrl: string; + } | null; + }; +} + +export interface DownloadStatus { + state: "STARTED" | "STOPPED"; + queue: DownloadQueueItem[]; +} + +export interface Connection { + nodes: T[]; +} \ No newline at end of file diff --git a/src/lib/util.ts b/src/lib/util.ts new file mode 100644 index 0000000..db01269 --- /dev/null +++ b/src/lib/util.ts @@ -0,0 +1,5 @@ +import { clsx, type ClassValue } from "clsx"; + +export function cn(...inputs: ClassValue[]) { + return clsx(inputs); +} \ No newline at end of file diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..a5b9a95 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; + +ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( + + + +); \ No newline at end of file diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 0000000..4ca39fe --- /dev/null +++ b/src/store/index.ts @@ -0,0 +1,174 @@ +import { create } from "zustand"; +import { persist } from "zustand/middleware"; +import type { Manga, Chapter, Source } from "../lib/types"; +import { DEFAULT_KEYBINDS, type Keybinds } from "../lib/keybinds"; + +export type PageStyle = "single" | "double" | "longstrip"; +export type FitMode = "width" | "height" | "screen" | "original"; +export type LibraryFilter = "all" | "library" | "downloaded"; +export type NavPage = "library" | "sources" | "downloads" | "extensions" | "history" | "search"; +export type ReadingDirection = "ltr" | "rtl"; +export type ChapterSortDir = "desc" | "asc"; + +export interface HistoryEntry { + mangaId: number; + mangaTitle: string; + thumbnailUrl: string; + chapterId: number; + chapterName: string; + pageNumber: number; + readAt: number; +} + +export interface ActiveDownload { + chapterId: number; + mangaId: number; + progress: number; +} + +export interface Settings { + pageStyle: PageStyle; + readingDirection: ReadingDirection; + fitMode: FitMode; + maxPageWidth: number; + pageGap: boolean; + optimizeContrast: boolean; + offsetDoubleSpreads: boolean; + preloadPages: number; + autoMarkRead: boolean; + libraryCropCovers: boolean; + libraryPageSize: number; + showNsfw: boolean; + chapterSortDir: ChapterSortDir; + chapterPageSize: number; + uiScale: number; + compactSidebar: boolean; + gpuAcceleration: boolean; + serverUrl: string; + serverBinary: string; + autoStartServer: boolean; + preferredExtensionLang: string; + keybinds: Keybinds; +} + +export const DEFAULT_SETTINGS: Settings = { + pageStyle: "single", + readingDirection: "ltr", + fitMode: "width", + maxPageWidth: 900, + pageGap: true, + optimizeContrast: false, + offsetDoubleSpreads: false, + preloadPages: 3, + autoMarkRead: true, + libraryCropCovers: true, + libraryPageSize: 48, + showNsfw: false, + chapterSortDir: "desc", + chapterPageSize: 25, + uiScale: 100, + compactSidebar: false, + gpuAcceleration: true, + serverUrl: "http://localhost:4567", + serverBinary: "tachidesk-server", + autoStartServer: true, + preferredExtensionLang: "en", + keybinds: DEFAULT_KEYBINDS, +}; + +interface Store { + navPage: NavPage; + setNavPage: (page: NavPage) => void; + activeManga: Manga | null; + setActiveManga: (manga: Manga | null) => void; + activeChapter: Chapter | null; + activeChapterList: Chapter[]; + openReader: (chapter: Chapter, chapterList: Chapter[]) => void; + closeReader: () => void; + activeSource: Source | null; + setActiveSource: (source: Source | null) => void; + pageUrls: string[]; + setPageUrls: (urls: string[]) => void; + pageNumber: number; + setPageNumber: (n: number) => void; + libraryFilter: LibraryFilter; + setLibraryFilter: (filter: LibraryFilter) => void; + libraryTagFilter: string[]; + setLibraryTagFilter: (tags: string[]) => void; + settingsOpen: boolean; + openSettings: () => void; + closeSettings: () => void; + activeDownloads: ActiveDownload[]; + setActiveDownloads: (items: ActiveDownload[]) => void; + history: HistoryEntry[]; + addHistory: (entry: HistoryEntry) => void; + clearHistory: () => void; + settings: Settings; + updateSettings: (patch: Partial) => void; + resetKeybinds: () => void; +} + +export const useStore = create()( + persist( + (set) => ({ + navPage: "library", + setNavPage: (navPage) => set({ navPage }), + activeManga: null, + setActiveManga: (activeManga) => set({ activeManga }), + activeChapter: null, + activeChapterList: [], + openReader: (chapter, chapterList) => + set({ activeChapter: chapter, activeChapterList: chapterList, pageUrls: [], pageNumber: 1 }), + closeReader: () => + set({ activeChapter: null, activeChapterList: [], pageUrls: [], pageNumber: 1 }), + activeSource: null, + setActiveSource: (activeSource) => set({ activeSource }), + pageUrls: [], + setPageUrls: (pageUrls) => set({ pageUrls }), + pageNumber: 1, + setPageNumber: (pageNumber) => set({ pageNumber }), + libraryFilter: "library", + setLibraryFilter: (libraryFilter) => set({ libraryFilter }), + libraryTagFilter: [], + setLibraryTagFilter: (libraryTagFilter) => set({ libraryTagFilter }), + settingsOpen: false, + openSettings: () => set({ settingsOpen: true }), + closeSettings: () => set({ settingsOpen: false }), + activeDownloads: [], + setActiveDownloads: (activeDownloads) => set({ activeDownloads }), + history: [], + addHistory: (entry) => + set((s) => { + const deduped = s.history.filter((h) => h.chapterId !== entry.chapterId); + return { history: [entry, ...deduped].slice(0, 300) }; + }), + clearHistory: () => set({ history: [] }), + settings: DEFAULT_SETTINGS, + updateSettings: (patch) => + set((s) => ({ settings: { ...s.settings, ...patch } })), + resetKeybinds: () => + set((s) => ({ settings: { ...s.settings, keybinds: DEFAULT_KEYBINDS } })), + }), + { + name: "moku-store", + partialize: (s) => ({ + settings: s.settings, + navPage: s.navPage, + libraryFilter: s.libraryFilter, + history: s.history, + }), + merge: (persisted: any, current) => ({ + ...current, + ...(persisted as object), + settings: { + ...DEFAULT_SETTINGS, + ...(persisted as any)?.settings, + keybinds: { + ...DEFAULT_KEYBINDS, + ...(persisted as any)?.settings?.keybinds, + }, + }, + }), + } + ) +); \ No newline at end of file diff --git a/src/styles/animations.css b/src/styles/animations.css new file mode 100644 index 0000000..8e75f29 --- /dev/null +++ b/src/styles/animations.css @@ -0,0 +1,59 @@ +/* ───────────────────────────────────────────── + Moku — Animations + ───────────────────────────────────────────── */ + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes fadeUp { + from { opacity: 0; transform: translateY(5px); } + to { opacity: 1; transform: translateY(0); } +} + +@keyframes fadeDown { + from { opacity: 0; transform: translateY(-5px); } + to { opacity: 1; transform: translateY(0); } +} + +@keyframes scaleIn { + from { opacity: 0; transform: scale(0.97); } + to { opacity: 1; transform: scale(1); } +} + +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.35; } +} + +@keyframes shimmer { + from { background-position: -200% 0; } + to { background-position: 200% 0; } +} + +/* Utility classes */ +.anim-fade-in { animation: fadeIn 0.14s ease both; } +.anim-fade-up { animation: fadeUp 0.18s ease both; } +.anim-fade-down { animation: fadeDown 0.18s ease both; } +.anim-scale-in { animation: scaleIn 0.14s ease both; } +.anim-pulse { animation: pulse 1.6s ease infinite; } +.anim-spin { animation: spin 0.7s linear infinite; } + +/* Skeleton shimmer */ +.skeleton { + background: linear-gradient( + 90deg, + var(--bg-raised) 25%, + var(--bg-overlay) 50%, + var(--bg-raised) 75% + ); + background-size: 200% 100%; + animation: shimmer 1.4s ease infinite; + border-radius: var(--radius-sm); +} \ No newline at end of file diff --git a/src/styles/global.css b/src/styles/global.css new file mode 100644 index 0000000..0d63d3b --- /dev/null +++ b/src/styles/global.css @@ -0,0 +1,105 @@ +/* ───────────────────────────────────────────── + Moku — Global Styles + ───────────────────────────────────────────── */ + +@import url("https://fonts.googleapis.com/css2?family=DM+Mono:ital,wght@0,300;0,400;0,500;1,400&family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;1,9..40,400&display=swap"); +@import "./tokens.css"; +@import "./animations.css"; + +*, *::before, *::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html, body, #root { + height: 100%; + overflow: hidden; +} + +body { + background: var(--bg-void); + color: var(--text-primary); + font-family: var(--font-sans); + font-size: var(--text-base); + line-height: var(--leading-base); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-feature-settings: "kern" 1, "liga" 1; + /* GPU: promote root to compositor layer for smooth compositing */ + transform: translateZ(0); +} + +/* Scrollbars */ +::-webkit-scrollbar { width: 4px; height: 4px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { + background: var(--border-strong); + border-radius: var(--radius-full); + transition: background var(--t-base); +} +::-webkit-scrollbar-thumb:hover { background: var(--text-faint); } + +/* Focus */ +:focus-visible { + outline: 1px solid var(--border-focus); + outline-offset: 2px; +} + +/* Selection */ +::selection { + background: var(--accent-dim); + color: var(--accent-fg); +} + +/* Base resets */ +button { + font-family: inherit; + cursor: pointer; + background: none; + border: none; + color: inherit; +} + +input, textarea { + font-family: inherit; + background: none; + border: none; + color: inherit; +} + +img { display: block; } +a { color: inherit; text-decoration: none; } + +/* Range — reader scrubber */ +input[type="range"] { + flex: 1; + appearance: none; + -webkit-appearance: none; + height: 2px; + background: var(--border-strong); + border-radius: var(--radius-full); + outline: none; + cursor: pointer; +} + +input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + width: 12px; + height: 12px; + border-radius: 50%; + background: var(--accent-fg); + cursor: pointer; + transition: background var(--t-base), transform var(--t-base); +} + +input[type="range"]::-webkit-slider-thumb:hover { + background: var(--accent-bright); + transform: scale(1.15); +} + +/* Monospace label utility */ +.mono { + font-family: var(--font-ui); + letter-spacing: var(--tracking-wide); +} \ No newline at end of file diff --git a/src/styles/tokens.css b/src/styles/tokens.css new file mode 100644 index 0000000..c61afa9 --- /dev/null +++ b/src/styles/tokens.css @@ -0,0 +1,109 @@ +/* ───────────────────────────────────────────── + Moku — Design Tokens + Surgical minimalism. One accent. No noise. + ───────────────────────────────────────────── */ + +:root { + /* Backgrounds — near-black, layered by elevation */ + --bg-void: #080808; + --bg-base: #0c0c0c; + --bg-surface: #101010; + --bg-raised: #151515; + --bg-overlay: #1a1a1a; + --bg-subtle: #202020; + + /* Borders */ + --border-dim: #1c1c1c; + --border-base: #242424; + --border-strong: #2e2e2e; + --border-focus: #4a5c4a; + + /* Text — cold whites, readable hierarchy */ + --text-primary: #f0efec; + --text-secondary: #c8c6c0; /* bumped up slightly from b8b6b0 for readability */ + --text-muted: #8a8880; /* bumped up slightly from 6e6c67 */ + --text-faint: #4e4d4a; /* bumped up slightly from 3e3d3a */ + --text-disabled: #2a2a28; + + /* Accent — desaturated sage green */ + --accent: #6b8f6b; + --accent-dim: #2a3d2a; + --accent-muted: #1a251a; + --accent-fg: #a8c4a8; + --accent-bright: #8fb88f; + + /* Semantic */ + --color-error: #c47a7a; + --color-error-bg: #1f1212; + --color-success: #7aab7a; + --color-info: #7a9ec4; + --color-info-bg: #121a1f; + --color-read: #2e2e2c; + + /* Status dots */ + --dot-active: var(--accent); + --dot-inactive: var(--text-faint); + + /* Typography */ + --font-ui: "DM Mono", "Fira Mono", ui-monospace, monospace; + --font-sans: "DM Sans", ui-sans-serif, system-ui, sans-serif; + + /* Type scale */ + --text-2xs: 10px; + --text-xs: 11px; + --text-sm: 12px; + --text-base: 13px; + --text-md: 14px; + --text-lg: 15px; + --text-xl: 17px; + --text-2xl: 20px; + --text-3xl: 24px; + + /* Weight */ + --weight-normal: 400; + --weight-medium: 500; + --weight-semi: 600; + + /* Leading */ + --leading-none: 1; + --leading-tight: 1.3; + --leading-snug: 1.45; + --leading-base: 1.6; + + /* Tracking */ + --tracking-tight: -0.02em; + --tracking-normal: 0; + --tracking-wide: 0.06em; + --tracking-wider: 0.1em; + + /* Spacing */ + --sp-1: 4px; + --sp-2: 8px; + --sp-3: 12px; + --sp-4: 16px; + --sp-5: 20px; + --sp-6: 24px; + --sp-8: 32px; + --sp-10: 40px; + + /* Radius */ + --radius-sm: 3px; + --radius-md: 5px; + --radius-lg: 7px; + --radius-xl: 10px; + --radius-2xl: 14px; + --radius-full: 9999px; + + /* Sidebar */ + --sidebar-width: 52px; + + /* Z-index */ + --z-reader: 50; + --z-modal: 100; + --z-settings: 150; + + /* Transitions */ + --t-fast: 0.08s ease; + --t-base: 0.14s ease; + --t-slow: 0.22s ease; +} \ No newline at end of file diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..d0104ed --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} \ No newline at end of file diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..099658c --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..e5c1b33 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +export default defineConfig({ + plugins: [react()], + clearScreen: false, + server: { + port: 1420, + strictPort: true, + watch: { + ignored: ["**/src-tauri/**"], + }, + }, +}); \ No newline at end of file