Compare commits

...
92 Commits
Author SHA1 Message Date
Lncvrt 3ee371998a Should fix linux/macos BIE support? 2026-03-24 19:01:44 -07:00
Lncvrt 5733c21fbb 1.8.0 2026-03-24 18:38:28 -07:00
Lncvrt 61b9d07fc6 Fix number formatting 2026-03-24 18:34:56 -07:00
Lncvrt 59cd625238 Finish mod manager & Count downloads for mods 2026-03-24 18:33:06 -07:00
Lncvrt 6bf2733cec Show that no mods are available to download/installed 2026-03-24 18:08:08 -07:00
Lncvrt 60f9f95be4 Finish mod installing and uninstalling 2026-03-24 18:01:24 -07:00
Lncvrt ea1c05d2d9 Add view mod info mode 2026-03-24 17:11:12 -07:00
Lncvrt 49c03e82b1 Pre-work on adding view mod info popup 2026-03-24 16:29:15 -07:00
Lncvrt 0c52169533 Add a refresh button 2026-03-24 16:12:44 -07:00
Lncvrt bf6da20fd8 Improvements here 2026-03-24 16:08:24 -07:00
Lncvrt c1f87580c7 Early WIP version of the mod manager 2026-03-24 15:57:33 -07:00
Lncvrt dd7d2b96e3 Fix potential issues here 2026-03-13 18:34:58 -07:00
Lncvrt 874f0627f7 Add downloading mod manager 2026-03-13 18:31:52 -07:00
Lncvrt e8dec1a1b2 Add types to downloads, will be for modding stuff 2026-03-13 18:11:58 -07:00
Lncvrt e1f0ce1290 wtf 2026-03-13 17:46:16 -07:00
Lncvrt bc5ea81170 Make remove button how it should be 2026-03-13 17:40:15 -07:00
Lncvrt 71b998f4bc Fix outdated page 2026-03-13 17:31:09 -07:00
Lncvrt cbac50753f Built in modding support wip 2026-03-13 17:10:11 -07:00
Lncvrt fb65348ba3 Fix this logic broken in 1.6.0 2026-03-12 23:20:29 -07:00
Lncvrt f23d1025d2 1.7.0 2026-03-10 21:43:24 -07:00
Lncvrt a8573c4b6a Improve that 2026-03-10 21:39:23 -07:00
Lncvrt 4054fdd06b Add download pausing, canceling and resuming 2026-03-10 21:22:24 -07:00
Lncvrt 0b4e639630 Add unzip status 2026-03-10 20:35:26 -07:00
Lncvrt c65fb5e1a5 Optimize crates & a import 2026-03-10 20:33:01 -07:00
Lncvrt 6486498c74 Readd progress bar status, but optimized 2026-03-10 19:56:55 -07:00
Lncvrt cf72d143f3 oop 2026-03-10 15:44:33 -07:00
Lncvrt ee7c50c6f5 Reset berry state on back 2026-03-10 15:44:01 -07:00
Lncvrt f7317c8232 Improve berry select popup 2026-03-10 15:22:49 -07:00
Lncvrt 9856cf2426 Remove progress bar status, causes so much issues 2026-03-10 15:17:06 -07:00
Lncvrt f46d9c67b8 1.6.5 2026-03-07 23:48:13 -07:00
Lncvrt 132616dc29 Fix issues with custom (not icon marketplace) icons in bd leaderboards 2026-03-07 23:46:21 -07:00
Lncvrt 8b22fdb648 Add .env 2026-03-04 20:52:11 -07:00
Lncvrt dc55c7549d Add taskbar status icon stuff 2026-02-28 20:10:10 -07:00
Lncvrt 9d9c6147c5 Fix issues with building 2026-02-28 14:43:13 -07:00
Lncvrt 48c8365b7c Nevermind 2026-02-28 13:09:39 -07:00
Lncvrt 8884d08c7c Switch to native-tls 2026-02-28 13:06:51 -07:00
Lncvrt b516a9cc43 1.6.4 2026-02-28 13:01:11 -07:00
Lncvrt 6b7b2b198a Add signature checking to requests 2026-02-28 12:57:10 -07:00
Lncvrt fbf87dceba Add verbose debugging 2026-02-28 12:47:48 -07:00
Lncvrt 9ae28690a7 Forgot about this again 2026-02-28 11:33:38 -07:00
Lncvrt f83cc118fe 1.6.3 2026-02-26 23:27:32 -07:00
Lncvrt 19788a8a3c Update launche to new API 2026-02-26 23:22:16 -07:00
Lncvrt e066dd0681 Update icon 2026-02-26 14:31:28 -07:00
Lncvrt 29b3c3c58b Flip this 2026-02-18 20:43:40 -07:00
Lncvrt a8390216d0 1.6.2 2026-02-17 00:41:31 -07:00
Lncvrt 513f1a0c16 Replace all .unwrap() statements with proper code with Claude Code 2026-02-17 00:39:20 -07:00
Lncvrt e7e624f042 Add overlay & custom bird color rendering to leaderboards 2026-02-16 23:44:56 -07:00
Lncvrt 654405e016 1.6.1 2026-02-14 23:33:58 -07:00
Lncvrt c461a0a459 Disable flags like reloading 2026-02-14 23:31:58 -07:00
Lncvrt 16ce04ba7a Make it so there is a topbar for windows if something fails to load 2026-02-14 19:04:16 -07:00
Lncvrt 1700d127c3 Improvements to download button, just makes more sense, and other stuff 2026-02-14 18:55:40 -07:00
Lncvrt 7c47b32c68 Merge view info & manage into one popup 2026-02-14 18:50:05 -07:00
Lncvrt 56781b5d1a Fix a issue with folders that don't exist not showing an error 2026-02-14 18:34:26 -07:00
Lncvrt 6ce7c2a8ae Improvements here 2026-02-14 18:17:19 -07:00
Lncvrt 15dd9ad013 Make logo look better on windows 2026-02-14 18:14:23 -07:00
Lncvrt 217222a22b Fix issues with leaderboards 2026-02-14 18:05:13 -07:00
Lncvrt 39da88a856 Fix linux and windows compiling issue 2026-02-14 16:07:57 -07:00
Lncvrt 29c90d2f06 1.6.0 2026-02-14 15:52:38 -07:00
Lncvrt 467dd1b0a1 Fix a issue with entries not clearing on back button press 2026-02-14 15:20:19 -07:00
Lncvrt a1a69c9ac5 Add name to games page title 2026-02-14 15:05:24 -07:00
Lncvrt 0163e09b02 Add berry dash leaderboards 2026-02-14 14:56:47 -07:00
Lncvrt fbb4b15971 Switch to ES2024 and webpack (for dev mode only) 2026-02-14 14:56:39 -07:00
Lncvrt 77933a2298 Move popups to their own files
Big improvement, needed to do this for a while even though it's so basic
2026-02-14 14:06:32 -07:00
Lncvrt 7209a36752 Make this have less code 2026-02-14 13:45:21 -07:00
Lncvrt f00b78996a Fix failed thing 2026-02-14 13:34:48 -07:00
Lncvrt 38779c1715 Rename this to remove, makes more sense 2026-02-14 13:31:36 -07:00
Lncvrt fa67d26fe9 Improvements again 2026-02-14 13:25:07 -07:00
Lncvrt 81d47ea71d Replace rust version of this with frontend logic 2026-02-14 13:03:31 -07:00
Lncvrt 70433b76ab Move some files around to where they should be 2026-02-14 12:49:57 -07:00
Lncvrt 105913503c Remove legacy interaction option 2026-02-14 12:46:01 -07:00
Lncvrt 2ad54f976b Update dependencies 2026-02-14 12:23:34 -07:00
Lncvrt d8d59febbb oops 2026-02-14 12:15:28 -07:00
Lncvrt a618c76292 Have Claude Code make downloads a proper queue system 2026-02-14 02:53:42 -07:00
Lncvrt 118fad49d8 Minimum macOS version is now 13 2026-02-14 02:27:53 -07:00
Lncvrt 62c0f84d14 1.5.8 2026-02-10 16:10:09 -07:00
Lncvrt 4a96911885 Make it so if a category is selected, it'll only show the versions from that category 2026-02-10 16:02:11 -07:00
Lncvrt 51cd3c3620 Fix being able to update while downloads are in progress 2026-02-10 15:58:38 -07:00
Lncvrt dfbfac33d3 Use system popup for showing changelog too 2026-02-10 15:02:23 -07:00
Lncvrt 03ec5a220a Use system popup for revision update warning 2026-02-10 14:58:53 -07:00
Lncvrt abebc0bb08 Add categories to sidebar 2026-02-10 14:25:02 -07:00
Lncvrt 3c7487f20f 1.5.7 2026-02-09 20:48:25 -07:00
Lncvrt 97231c6b38 Let the user approve revision updates before proceeding 2026-02-09 20:46:15 -07:00
Lncvrt a93e4ab521 Fix TS 2026-02-09 20:39:59 -07:00
Lncvrt e7c48b182f Downgrade eslint from 10.0.0 to 9.39.2 to fix issues 2026-02-09 20:38:52 -07:00
Lncvrt 16dd3fd613 Fixes here 2026-02-09 20:22:11 -07:00
Lncvrt c91303d194 Make reinstall button change popup to downloads popup stead 2026-02-09 20:13:55 -07:00
Lncvrt a76e81789b Make this show N/A if can't load 2026-02-09 19:28:22 -07:00
Lncvrt c909973aab Not sure why that was like that 2026-02-09 19:20:50 -07:00
Lncvrt 28f694e4e7 Update dependencies 2026-02-09 18:59:15 -07:00
Lncvrt 508e4d1640 1.5.6 2026-02-07 20:22:19 -07:00
Lncvrt 41a66eaf86 Make sure __MACOSX and .DS_Store don't get included 2026-02-07 20:19:20 -07:00
Lncvrt ac4b76fa46 Set permissions properly when unzipping 2026-02-07 20:18:55 -07:00
71 changed files with 3043 additions and 1483 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
NEXT_PUBLIC_PUBLIC_SIGNING_KEY=LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUNvd0JRWURLMlZ3QXlFQVJiK1N5c0VQemRYaGN4OWtyMy9rY3NNNURheDZRS1Q0VXZ3bW9PbWd2UHc9Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo=
-3
View File
@@ -30,9 +30,6 @@ yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
# vercel
.vercel
+129 -139
View File
@@ -5,36 +5,36 @@
"": {
"name": "lncvrt-games-launcher",
"dependencies": {
"@fortawesome/fontawesome-svg-core": "7.1.0",
"@fortawesome/free-brands-svg-icons": "7.1.0",
"@fortawesome/free-solid-svg-icons": "7.1.0",
"@fortawesome/react-fontawesome": "3.1.1",
"@fortawesome/fontawesome-svg-core": "7.2.0",
"@fortawesome/free-brands-svg-icons": "7.2.0",
"@fortawesome/free-solid-svg-icons": "7.2.0",
"@fortawesome/react-fontawesome": "3.3.0",
"@tauri-apps/api": "2.10.1",
"@tauri-apps/plugin-clipboard-manager": "2.3.2",
"@tauri-apps/plugin-dialog": "2.6.0",
"@tauri-apps/plugin-fs": "2.4.5",
"@tauri-apps/plugin-http": "2.5.7",
"@tauri-apps/plugin-notification": "2.3.3",
"@tauri-apps/plugin-opener": "2.5.3",
"@tauri-apps/plugin-os": "2.3.2",
"@tauri-apps/plugin-window-state": "2.4.1",
"axios": "1.13.4",
"next": "16.1.6",
"next": "16.2.1",
"pretty-bytes": "7.1.0",
"react": "19.2.4",
"react-dom": "19.2.4",
},
"devDependencies": {
"@eslint/eslintrc": "3.3.3",
"@tailwindcss/postcss": "4.1.18",
"@tauri-apps/cli": "2.10.0",
"@eslint/eslintrc": "3.3.5",
"@tailwindcss/postcss": "4.2.2",
"@tauri-apps/cli": "2.10.1",
"@types/crypto-js": "4.2.2",
"@types/node": "25.2.0",
"@types/react": "19.2.10",
"@types/node": "25.5.0",
"@types/react": "19.2.14",
"@types/react-dom": "19.2.3",
"crypto-js": "4.2.0",
"eslint": "9.39.2",
"eslint-config-next": "16.1.6",
"tailwindcss": "4.1.18",
"eslint": "9.39.4",
"eslint-config-next": "16.2.1",
"tailwindcss": "4.2.2",
"typescript": "5.9.3",
},
},
@@ -48,7 +48,7 @@
"@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@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.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="],
"@babel/generator": ["@babel/generator@7.29.0", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ=="],
"@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="],
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="],
@@ -64,9 +64,9 @@
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
"@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="],
"@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="],
"@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
"@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
"@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
@@ -74,39 +74,39 @@
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
"@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
"@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" } }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="],
"@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
"@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="],
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="],
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="],
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
"@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="],
"@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="],
"@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="],
"@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="],
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.3", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ=="],
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.5", "", { "dependencies": { "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" } }, "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg=="],
"@eslint/js": ["@eslint/js@9.39.2", "", {}, "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA=="],
"@eslint/js": ["@eslint/js@9.39.4", "", {}, "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw=="],
"@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
"@fortawesome/fontawesome-common-types": ["@fortawesome/fontawesome-common-types@7.1.0", "", {}, "sha512-l/BQM7fYntsCI//du+6sEnHOP6a74UixFyOYUyz2DLMXKx+6DEhfR3F2NYGE45XH1JJuIamacb4IZs9S0ZOWLA=="],
"@fortawesome/fontawesome-common-types": ["@fortawesome/fontawesome-common-types@7.2.0", "", {}, "sha512-IpR0bER9FY25p+e7BmFH25MZKEwFHTfRAfhOyJubgiDnoJNsSvJ7nigLraHtp4VOG/cy8D7uiV0dLkHOne5Fhw=="],
"@fortawesome/fontawesome-svg-core": ["@fortawesome/fontawesome-svg-core@7.1.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "7.1.0" } }, "sha512-fNxRUk1KhjSbnbuBxlWSnBLKLBNun52ZBTcs22H/xEEzM6Ap81ZFTQ4bZBxVQGQgVY0xugKGoRcCbaKjLQ3XZA=="],
"@fortawesome/fontawesome-svg-core": ["@fortawesome/fontawesome-svg-core@7.2.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "7.2.0" } }, "sha512-6639htZMjEkwskf3J+e6/iar+4cTNM9qhoWuRfj9F3eJD6r7iCzV1SWnQr2Mdv0QT0suuqU8BoJCZUyCtP9R4Q=="],
"@fortawesome/free-brands-svg-icons": ["@fortawesome/free-brands-svg-icons@7.1.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "7.1.0" } }, "sha512-9byUd9bgNfthsZAjBl6GxOu1VPHgBuRUP9juI7ZoM98h8xNPTCTagfwUFyYscdZq4Hr7gD1azMfM9s5tIWKZZA=="],
"@fortawesome/free-brands-svg-icons": ["@fortawesome/free-brands-svg-icons@7.2.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "7.2.0" } }, "sha512-VNG8xqOip1JuJcC3zsVsKRQ60oXG9+oYNDCosjoU/H9pgYmLTEwWw8pE0jhPz/JWdHeUuK6+NQ3qsM4gIbdbYQ=="],
"@fortawesome/free-solid-svg-icons": ["@fortawesome/free-solid-svg-icons@7.1.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "7.1.0" } }, "sha512-Udu3K7SzAo9N013qt7qmm22/wo2hADdheXtBfxFTecp+ogsc0caQNRKEb7pkvvagUGOpG9wJC1ViH6WXs8oXIA=="],
"@fortawesome/free-solid-svg-icons": ["@fortawesome/free-solid-svg-icons@7.2.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "7.2.0" } }, "sha512-YTVITFGN0/24PxzXrwqCgnyd7njDuzp5ZvaCx5nq/jg55kUYd94Nj8UTchBdBofi/L0nwRfjGOg0E41d2u9T1w=="],
"@fortawesome/react-fontawesome": ["@fortawesome/react-fontawesome@3.1.1", "", { "peerDependencies": { "@fortawesome/fontawesome-svg-core": "~6 || ~7", "react": "^18.0.0 || ^19.0.0" } }, "sha512-EDllr9hpodc21odmUywHS1alXNiCd4E8sp5GJ5s7wYINz8vSmMiNWpALTiuYODb865YyQ/NlyiN4mbXp7HCNqg=="],
"@fortawesome/react-fontawesome": ["@fortawesome/react-fontawesome@3.3.0", "", { "peerDependencies": { "@fortawesome/fontawesome-svg-core": "~6 || ~7", "react": "^18.0.0 || ^19.0.0" } }, "sha512-EHmHeTf8WgO29sdY3iX/7ekE3gNUdlc2RW6mm/FzELlHFKfTrA9S4MlyquRR+RRCRCn8+jXfLFpLGB2l7wCWyw=="],
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
@@ -116,7 +116,7 @@
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
"@img/colour": ["@img/colour@1.0.0", "", {}, "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw=="],
"@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="],
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="],
@@ -178,25 +178,25 @@
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="],
"@next/env": ["@next/env@16.1.6", "", {}, "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ=="],
"@next/env": ["@next/env@16.2.1", "", {}, "sha512-n8P/HCkIWW+gVal2Z8XqXJ6aB3J0tuM29OcHpCsobWlChH/SITBs1DFBk/HajgrwDkqqBXPbuUuzgDvUekREPg=="],
"@next/eslint-plugin-next": ["@next/eslint-plugin-next@16.1.6", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-/Qq3PTagA6+nYVfryAtQ7/9FEr/6YVyvOtl6rZnGsbReGLf0jZU6gkpr1FuChAQpvV46a78p4cmHOVP8mbfSMQ=="],
"@next/eslint-plugin-next": ["@next/eslint-plugin-next@16.2.1", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-r0epZGo24eT4g08jJlg2OEryBphXqO8aL18oajoTKLzHJ6jVr6P6FI58DLMug04MwD3j8Fj0YK0slyzneKVyzA=="],
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.1.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw=="],
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.2.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-BwZ8w8YTaSEr2HIuXLMLxIdElNMPvY9fLqb20LX9A9OMGtJilhHLbCL3ggyd0TwjmMcTxi0XXt+ur1vWUoxj2Q=="],
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.1.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ=="],
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.2.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-/vrcE6iQSJq3uL3VGVHiXeaKbn8Es10DGTGRJnRZlkNQQk3kaNtAJg8Y6xuAlrx/6INKVjkfi5rY0iEXorZ6uA=="],
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.1.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw=="],
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-uLn+0BK+C31LTVbQ/QU+UaVrV0rRSJQ8RfniQAHPghDdgE+SlroYqcmFnO5iNjNfVWCyKZHYrs3Nl0mUzWxbBw=="],
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.1.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ=="],
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-ssKq6iMRnHdnycGp9hCuGnXJZ0YPr4/wNwrfE5DbmvEcgl9+yv97/Kq3TPVDfYome1SW5geciLB9aiEqKXQjlQ=="],
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.1.6", "", { "os": "linux", "cpu": "x64" }, "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ=="],
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-HQm7SrHRELJ30T1TSmT706IWovFFSRGxfgUkyWJZF/RKBMdbdRWJuFrcpDdE5vy9UXjFOx6L3mRdqH04Mmx0hg=="],
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.1.6", "", { "os": "linux", "cpu": "x64" }, "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg=="],
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-aV2iUaC/5HGEpbBkE+4B8aHIudoOy5DYekAKOMSHoIYQ66y/wIVeaRx8MS2ZMdxe/HIXlMho4ubdZs/J8441Tg=="],
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.1.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw=="],
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.2.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-IXdNgiDHaSk0ZUJ+xp0OQTdTgnpx1RCfRTalhn3cjOP+IddTMINwA7DXZrwTmGDO8SUr5q2hdP/du4DcrB1GxA=="],
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.1.6", "", { "os": "win32", "cpu": "x64" }, "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A=="],
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.2.1", "", { "os": "win32", "cpu": "x64" }, "sha512-qvU+3a39Hay+ieIztkGSbF7+mccbbg1Tk25hc4JDylf8IHjYmY/Zm64Qq1602yPyQqvie+vf5T/uPwNxDNIoeg=="],
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
@@ -210,61 +210,61 @@
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
"@tailwindcss/node": ["@tailwindcss/node@4.1.18", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.18" } }, "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ=="],
"@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.18", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.18", "@tailwindcss/oxide-darwin-arm64": "4.1.18", "@tailwindcss/oxide-darwin-x64": "4.1.18", "@tailwindcss/oxide-freebsd-x64": "4.1.18", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", "@tailwindcss/oxide-linux-x64-musl": "4.1.18", "@tailwindcss/oxide-wasm32-wasi": "4.1.18", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" } }, "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.2", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.2", "@tailwindcss/oxide-darwin-arm64": "4.2.2", "@tailwindcss/oxide-darwin-x64": "4.2.2", "@tailwindcss/oxide-freebsd-x64": "4.2.2", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", "@tailwindcss/oxide-linux-x64-musl": "4.2.2", "@tailwindcss/oxide-wasm32-wasi": "4.2.2", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg=="],
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.18", "", { "os": "android", "cpu": "arm64" }, "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q=="],
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.2", "", { "os": "android", "cpu": "arm64" }, "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg=="],
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.18", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A=="],
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg=="],
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.18", "", { "os": "darwin", "cpu": "x64" }, "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw=="],
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw=="],
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.18", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA=="],
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ=="],
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18", "", { "os": "linux", "cpu": "arm" }, "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA=="],
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2", "", { "os": "linux", "cpu": "arm" }, "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ=="],
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw=="],
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw=="],
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg=="],
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag=="],
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g=="],
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg=="],
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ=="],
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ=="],
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.18", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.0", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA=="],
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.2", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q=="],
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.18", "", { "os": "win32", "cpu": "arm64" }, "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA=="],
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ=="],
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.18", "", { "os": "win32", "cpu": "x64" }, "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q=="],
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA=="],
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.18", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "postcss": "^8.4.41", "tailwindcss": "4.1.18" } }, "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g=="],
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.2.2", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.2.2", "@tailwindcss/oxide": "4.2.2", "postcss": "^8.5.6", "tailwindcss": "4.2.2" } }, "sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ=="],
"@tauri-apps/api": ["@tauri-apps/api@2.10.1", "", {}, "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw=="],
"@tauri-apps/cli": ["@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" }, "bin": { "tauri": "tauri.js" } }, "sha512-ZwT0T+7bw4+DPCSWzmviwq5XbXlM0cNoleDKOYPFYqcZqeKY31KlpoMW/MOON/tOFBPgi31a2v3w9gliqwL2+Q=="],
"@tauri-apps/cli": ["@tauri-apps/cli@2.10.1", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.10.1", "@tauri-apps/cli-darwin-x64": "2.10.1", "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.1", "@tauri-apps/cli-linux-arm64-gnu": "2.10.1", "@tauri-apps/cli-linux-arm64-musl": "2.10.1", "@tauri-apps/cli-linux-riscv64-gnu": "2.10.1", "@tauri-apps/cli-linux-x64-gnu": "2.10.1", "@tauri-apps/cli-linux-x64-musl": "2.10.1", "@tauri-apps/cli-win32-arm64-msvc": "2.10.1", "@tauri-apps/cli-win32-ia32-msvc": "2.10.1", "@tauri-apps/cli-win32-x64-msvc": "2.10.1" }, "bin": { "tauri": "tauri.js" } }, "sha512-jQNGF/5quwORdZSSLtTluyKQ+o6SMa/AUICfhf4egCGFdMHqWssApVgYSbg+jmrZoc8e1DscNvjTnXtlHLS11g=="],
"@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.10.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-avqHD4HRjrMamE/7R/kzJPcAJnZs0IIS+1nkDP5b+TNBn3py7N2aIo9LIpy+VQq0AkN8G5dDpZtOOBkmWt/zjA=="],
"@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.10.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Z2OjCXiZ+fbYZy7PmP3WRnOpM9+Fy+oonKDEmUE6MwN4IGaYqgceTjwHucc/kEEYZos5GICve35f7ZiizgqEnQ=="],
"@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.10.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-keDmlvJRStzVFjZTd0xYkBONLtgBC9eMTpmXnBXzsHuawV2q9PvDo2x6D5mhuoMVrJ9QWjgaPKBBCFks4dK71Q=="],
"@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.10.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-V/irQVvjPMGOTQqNj55PnQPVuH4VJP8vZCN7ajnj+ZS8Kom1tEM2hR3qbbIRoS3dBKs5mbG8yg1WC+97dq17Pw=="],
"@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.10.0", "", { "os": "linux", "cpu": "arm" }, "sha512-e5u0VfLZsMAC9iHaOEANumgl6lfnJx0Dtjkd8IJpysZ8jp0tJ6wrIkto2OzQgzcYyRCKgX72aKE0PFgZputA8g=="],
"@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.10.1", "", { "os": "linux", "cpu": "arm" }, "sha512-Hyzwsb4VnCWKGfTw+wSt15Z2pLw2f0JdFBfq2vHBOBhvg7oi6uhKiF87hmbXOBXUZaGkyRDkCHsdzJcIfoJC2w=="],
"@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.10.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-YrYYk2dfmBs5m+OIMCrb+JH/oo+4FtlpcrTCgiFYc7vcs6m3QDd1TTyWu0u01ewsCtK2kOdluhr/zKku+KP7HA=="],
"@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.10.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-OyOYs2t5GkBIvyWjA1+h4CZxTcdz1OZPCWAPz5DYEfB0cnWHERTnQ/SLayQzncrT0kwRoSfSz9KxenkyJoTelA=="],
"@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.10.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-GUoPdVJmrJRIXFfW3Rkt+eGK9ygOdyISACZfC/bCSfOnGt8kNdQIQr5WRH9QUaTVFIwxMlQyV3m+yXYP+xhSVA=="],
"@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.10.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-MIj78PDDGjkg3NqGptDOGgfXks7SYJwhiMh8SBoZS+vfdz7yP5jN18bNaLnDhsVIPARcAhE1TlsZe/8Yxo2zqg=="],
"@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.10.0", "", { "os": "linux", "cpu": "none" }, "sha512-JO7s3TlSxshwsoKNCDkyvsx5gw2QAs/Y2GbR5UE2d5kkU138ATKoPOtxn8G1fFT1aDW4LH0rYAAfBpGkDyJJnw=="],
"@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.10.1", "", { "os": "linux", "cpu": "none" }, "sha512-X0lvOVUg8PCVaoEtEAnpxmnkwlE1gcMDTqfhbefICKDnOTJ5Est3qL0SrWxizDackIOKBcvtpejrSiVpuJI1kw=="],
"@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.10.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Uvh4SUUp4A6DVRSMWjelww0GnZI3PlVy7VS+DRF5napKuIehVjGl9XD0uKoCoxwAQBLctvipyEK+pDXpJeoHng=="],
"@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.10.1", "", { "os": "linux", "cpu": "x64" }, "sha512-2/12bEzsJS9fAKybxgicCDFxYD1WEI9kO+tlDwX5znWG2GwMBaiWcmhGlZ8fi+DMe9CXlcVarMTYc0L3REIRxw=="],
"@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.10.0", "", { "os": "linux", "cpu": "x64" }, "sha512-AP0KRK6bJuTpQ8kMNWvhIpKUkQJfcPFeba7QshOQZjJ8wOS6emwTN4K5g/d3AbCMo0RRdnZWwu67MlmtJyxC1Q=="],
"@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.10.1", "", { "os": "linux", "cpu": "x64" }, "sha512-Y8J0ZzswPz50UcGOFuXGEMrxbjwKSPgXftx5qnkuMs2rmwQB5ssvLb6tn54wDSYxe7S6vlLob9vt0VKuNOaCIQ=="],
"@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.10.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-97DXVU3dJystrq7W41IX+82JEorLNY+3+ECYxvXWqkq7DBN6FsA08x/EFGE8N/b0LTOui9X2dvpGGoeZKKV08g=="],
"@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.10.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-iSt5B86jHYAPJa/IlYw++SXtFPGnWtFJriHn7X0NFBVunF6zu9+/zOn8OgqIWSl8RgzhLGXQEEtGBdR4wzpVgg=="],
"@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.10.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-EHyQ1iwrWy1CwMalEm9z2a6L5isQ121pe7FcA2xe4VWMJp+GHSDDGvbTv/OPdkt2Lyr7DAZBpZHM6nvlHXEc4A=="],
"@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.10.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-gXyxgEzsFegmnWywYU5pEBURkcFN/Oo45EAwvZrHMh+zUSEAvO5E8TXsgPADYm31d1u7OQU3O3HsYfVBf2moHw=="],
"@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.10.0", "", { "os": "win32", "cpu": "x64" }, "sha512-NTpyQxkpzGmU6ceWBTY2xRIEaS0ZLbVx1HE1zTA3TY/pV3+cPoPPOs+7YScr4IMzXMtOw7tLw5LEXo5oIG3qaQ=="],
"@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.10.1", "", { "os": "win32", "cpu": "x64" }, "sha512-6Cn7YpPFwzChy0ERz6djKEmUehWrYlM+xTaNzGPgZocw3BD7OfwfWHKVWxXzdjEW2KfKkHddfdxK1XXTYqBRLg=="],
"@tauri-apps/plugin-clipboard-manager": ["@tauri-apps/plugin-clipboard-manager@2.3.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-CUlb5Hqi2oZbcZf4VUyUH53XWPPdtpw43EUpCza5HWZJwxEoDowFzNUDt1tRUXA8Uq+XPn17Ysfptip33sG4eQ=="],
@@ -272,6 +272,8 @@
"@tauri-apps/plugin-fs": ["@tauri-apps/plugin-fs@2.4.5", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-dVxWWGE6VrOxC7/jlhyE+ON/Cc2REJlM35R3PJX3UvFw2XwYhLGQVAIyrehenDdKjotipjYEVc4YjOl3qq90fA=="],
"@tauri-apps/plugin-http": ["@tauri-apps/plugin-http@2.5.7", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-+F2lEH/c9b0zSsOXKq+5hZNcd9F4IIKCK1T17RqMwpCmVnx2aoqY8yIBccCd25HTYUb3j6NPVbRax/m00hKG8A=="],
"@tauri-apps/plugin-notification": ["@tauri-apps/plugin-notification@2.3.3", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-Zw+ZH18RJb41G4NrfHgIuofJiymusqN+q8fGUIIV7vyCH+5sSn5coqRv/MWB9qETsUs97vmU045q7OyseCV3Qg=="],
"@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.5.3", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-CCcUltXMOfUEArbf3db3kCE7Ggy1ExBEBl51Ko2ODJ6GDYHRp1nSNlQm5uNCFY5k7/ufaK5Ib3Du/Zir19IYQQ=="],
@@ -290,31 +292,31 @@
"@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="],
"@types/node": ["@types/node@25.2.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w=="],
"@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="],
"@types/react": ["@types/react@19.2.10", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw=="],
"@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.54.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/type-utils": "8.54.0", "@typescript-eslint/utils": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.54.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.57.2", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.57.2", "@typescript-eslint/type-utils": "8.57.2", "@typescript-eslint/utils": "8.57.2", "@typescript-eslint/visitor-keys": "8.57.2", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.57.2", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.54.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", "@typescript-eslint/typescript-estree": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.57.2", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.57.2", "@typescript-eslint/types": "8.57.2", "@typescript-eslint/typescript-estree": "8.57.2", "@typescript-eslint/visitor-keys": "8.57.2", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA=="],
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.54.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.54.0", "@typescript-eslint/types": "^8.54.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g=="],
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.57.2", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.57.2", "@typescript-eslint/types": "^8.57.2", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw=="],
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0" } }, "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg=="],
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.57.2", "", { "dependencies": { "@typescript-eslint/types": "8.57.2", "@typescript-eslint/visitor-keys": "8.57.2" } }, "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw=="],
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.54.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw=="],
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.57.2", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw=="],
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "@typescript-eslint/typescript-estree": "8.54.0", "@typescript-eslint/utils": "8.54.0", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA=="],
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.57.2", "", { "dependencies": { "@typescript-eslint/types": "8.57.2", "@typescript-eslint/typescript-estree": "8.57.2", "@typescript-eslint/utils": "8.57.2", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg=="],
"@typescript-eslint/types": ["@typescript-eslint/types@8.54.0", "", {}, "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA=="],
"@typescript-eslint/types": ["@typescript-eslint/types@8.57.2", "", {}, "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA=="],
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.54.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.54.0", "@typescript-eslint/tsconfig-utils": "8.54.0", "@typescript-eslint/types": "8.54.0", "@typescript-eslint/visitor-keys": "8.54.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA=="],
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.57.2", "", { "dependencies": { "@typescript-eslint/project-service": "8.57.2", "@typescript-eslint/tsconfig-utils": "8.57.2", "@typescript-eslint/types": "8.57.2", "@typescript-eslint/visitor-keys": "8.57.2", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA=="],
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.54.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", "@typescript-eslint/typescript-estree": "8.54.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA=="],
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.57.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.57.2", "@typescript-eslint/types": "8.57.2", "@typescript-eslint/typescript-estree": "8.57.2" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg=="],
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.54.0", "", { "dependencies": { "@typescript-eslint/types": "8.54.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA=="],
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.57.2", "", { "dependencies": { "@typescript-eslint/types": "8.57.2", "eslint-visitor-keys": "^5.0.0" } }, "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw=="],
"@unrs/resolver-binding-android-arm-eabi": ["@unrs/resolver-binding-android-arm-eabi@1.11.1", "", { "os": "android", "cpu": "arm" }, "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw=="],
@@ -354,11 +356,11 @@
"@unrs/resolver-binding-win32-x64-msvc": ["@unrs/resolver-binding-win32-x64-msvc@1.11.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g=="],
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
"ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="],
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
@@ -386,19 +388,15 @@
"async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="],
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
"axe-core": ["axe-core@4.11.1", "", {}, "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A=="],
"axios": ["axios@1.13.4", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg=="],
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.9.19", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.10", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ=="],
"brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
@@ -414,7 +412,7 @@
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
"caniuse-lite": ["caniuse-lite@1.0.30001767", "", {}, "sha512-34+zUAMhSH+r+9eKmYG+k2Rpt8XttfE4yXAjoZvkAPs15xcYQhyBYdalJ65BzivAvGRMViEjy6oKr/S91loekQ=="],
"caniuse-lite": ["caniuse-lite@1.0.30001781", "", {}, "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw=="],
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
@@ -424,8 +422,6 @@
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
@@ -452,19 +448,17 @@
"define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
"electron-to-chromium": ["electron-to-chromium@1.5.286", "", {}, "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A=="],
"electron-to-chromium": ["electron-to-chromium@1.5.322", "", {}, "sha512-vFU34OcrvMcH66T+dYC3G4nURmgfDVewMIu6Q2urXpumAPSMmzvcn04KVVV8Opikq8Vs5nUbO/8laNhNRqSzYw=="],
"emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
"enhanced-resolve": ["enhanced-resolve@5.19.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg=="],
"enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="],
"es-abstract": ["es-abstract@1.24.1", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw=="],
@@ -472,7 +466,7 @@
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
"es-iterator-helpers": ["es-iterator-helpers@1.2.2", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.1", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.3.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", "safe-array-concat": "^1.1.3" } }, "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w=="],
"es-iterator-helpers": ["es-iterator-helpers@1.3.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.1", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.3.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.5", "math-intrinsics": "^1.1.0", "safe-array-concat": "^1.1.3" } }, "sha512-zWwRvqWiuBPr0muUG/78cW3aHROFCNIQ3zpmYDpwdbnt2m+xlNyRWpHBpa2lJjSBit7BQ+RXA1iwbSmu5yJ/EQ=="],
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
@@ -486,9 +480,9 @@
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
"eslint": ["eslint@9.39.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.2", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw=="],
"eslint": ["eslint@9.39.4", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.5", "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ=="],
"eslint-config-next": ["eslint-config-next@16.1.6", "", { "dependencies": { "@next/eslint-plugin-next": "16.1.6", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", "eslint-plugin-react-hooks": "^7.0.0", "globals": "16.4.0", "typescript-eslint": "^8.46.0" }, "peerDependencies": { "eslint": ">=9.0.0", "typescript": ">=3.3.1" }, "optionalPeers": ["typescript"] }, "sha512-vKq40io2B0XtkkNDYyleATwblNt8xuh3FWp8SpSz3pt7P01OkBFlKsJZ2mWt5WsCySlDQLckb1zMY9yE9Qy0LA=="],
"eslint-config-next": ["eslint-config-next@16.2.1", "", { "dependencies": { "@next/eslint-plugin-next": "16.2.1", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", "eslint-plugin-react-hooks": "^7.0.0", "globals": "16.4.0", "typescript-eslint": "^8.46.0" }, "peerDependencies": { "eslint": ">=9.0.0", "typescript": ">=3.3.1" }, "optionalPeers": ["typescript"] }, "sha512-qhabwjQZ1Mk53XzXvmogf8KQ0tG0CQXF0CZ56+2/lVhmObgmaqj7x5A1DSrWdZd3kwI7GTPGUjFne+krRxYmFg=="],
"eslint-import-resolver-node": ["eslint-import-resolver-node@0.3.9", "", { "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.13.0", "resolve": "^1.22.4" } }, "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g=="],
@@ -538,14 +532,10 @@
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
"follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="],
"flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="],
"for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
"form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
"function.prototype.name": ["function.prototype.name@1.1.8", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "functions-have-names": "^1.2.3", "hasown": "^2.0.2", "is-callable": "^1.2.7" } }, "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q=="],
@@ -562,7 +552,7 @@
"get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="],
"get-tsconfig": ["get-tsconfig@4.13.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-EoY1N2xCn44xU6750Sx7OjOIT59FkmstNc3X6y5xpz7D5cBtZRe/3pSlTkDJgqsOk3WwZPkWfonhhUJfttQo3w=="],
"get-tsconfig": ["get-tsconfig@4.13.7", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q=="],
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
@@ -684,29 +674,29 @@
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
"lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="],
"lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="],
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="],
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="],
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="],
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="],
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="],
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="],
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="],
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="],
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="],
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="],
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
@@ -724,11 +714,7 @@
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
@@ -740,9 +726,11 @@
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
"next": ["next@16.1.6", "", { "dependencies": { "@next/env": "16.1.6", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.1.6", "@next/swc-darwin-x64": "16.1.6", "@next/swc-linux-arm64-gnu": "16.1.6", "@next/swc-linux-arm64-musl": "16.1.6", "@next/swc-linux-x64-gnu": "16.1.6", "@next/swc-linux-x64-musl": "16.1.6", "@next/swc-win32-arm64-msvc": "16.1.6", "@next/swc-win32-x64-msvc": "16.1.6", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw=="],
"next": ["next@16.2.1", "", { "dependencies": { "@next/env": "16.2.1", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.2.1", "@next/swc-darwin-x64": "16.2.1", "@next/swc-linux-arm64-gnu": "16.2.1", "@next/swc-linux-arm64-musl": "16.2.1", "@next/swc-linux-x64-gnu": "16.2.1", "@next/swc-linux-x64-musl": "16.2.1", "@next/swc-win32-arm64-msvc": "16.2.1", "@next/swc-win32-x64-msvc": "16.2.1", "sharp": "^0.34.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-VaChzNL7o9rbfdt60HUj8tev4m6d7iC1igAy157526+cJlXOQu5LzsBXNT+xaJnTP/k+utSX5vMv7m0G+zKH+Q=="],
"node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
"node-exports-info": ["node-exports-info@1.6.0", "", { "dependencies": { "array.prototype.flatmap": "^1.3.3", "es-errors": "^1.3.0", "object.entries": "^1.1.9", "semver": "^6.3.1" } }, "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw=="],
"node-releases": ["node-releases@2.0.36", "", {}, "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
@@ -778,11 +766,11 @@
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
"picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
"postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="],
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
@@ -790,8 +778,6 @@
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
@@ -874,15 +860,15 @@
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
"tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="],
"tailwindcss": ["tailwindcss@4.2.2", "", {}, "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q=="],
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
"tapable": ["tapable@2.3.2", "", {}, "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA=="],
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
"ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="],
"ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="],
"tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="],
@@ -900,11 +886,11 @@
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"typescript-eslint": ["typescript-eslint@8.54.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.54.0", "@typescript-eslint/parser": "8.54.0", "@typescript-eslint/typescript-estree": "8.54.0", "@typescript-eslint/utils": "8.54.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ=="],
"typescript-eslint": ["typescript-eslint@8.57.2", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.57.2", "@typescript-eslint/parser": "8.57.2", "@typescript-eslint/typescript-estree": "8.57.2", "@typescript-eslint/utils": "8.57.2" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-VEPQ0iPgWO/sBaZOU1xo4nuNdODVOajPnTIbog2GKYr31nIlZ0fWPoCQgGfF3ETyBl1vn63F/p50Um9Z4J8O8A=="],
"unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="],
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
"unrs-resolver": ["unrs-resolver@1.11.1", "", { "dependencies": { "napi-postinstall": "^0.3.0" }, "optionalDependencies": { "@unrs/resolver-binding-android-arm-eabi": "1.11.1", "@unrs/resolver-binding-android-arm64": "1.11.1", "@unrs/resolver-binding-darwin-arm64": "1.11.1", "@unrs/resolver-binding-darwin-x64": "1.11.1", "@unrs/resolver-binding-freebsd-x64": "1.11.1", "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-musl": "1.11.1", "@unrs/resolver-binding-wasm32-wasi": "1.11.1", "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg=="],
@@ -936,11 +922,11 @@
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="],
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
@@ -950,9 +936,11 @@
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
"@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"@typescript-eslint/typescript-estree/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
"@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="],
"eslint-config-next/globals": ["globals@16.4.0", "", {}, "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw=="],
@@ -962,18 +950,20 @@
"eslint-plugin-import/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
"eslint-plugin-react/resolve": ["resolve@2.0.0-next.5", "", { "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA=="],
"eslint-plugin-react/resolve": ["resolve@2.0.0-next.6", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "node-exports-info": "^1.6.0", "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA=="],
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"is-bun-module/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"is-bun-module/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
"next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
"sharp/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"sharp/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="],
"@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
}
}
File renamed without changes.
+15 -15
View File
@@ -1,7 +1,7 @@
{
"name": "lncvrt-games-launcher",
"private": true,
"version": "1.5.5",
"version": "1.8.0",
"type": "module",
"scripts": {
"dev": "next dev",
@@ -11,36 +11,36 @@
"tauri": "tauri"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "7.1.0",
"@fortawesome/free-brands-svg-icons": "7.1.0",
"@fortawesome/free-solid-svg-icons": "7.1.0",
"@fortawesome/react-fontawesome": "3.1.1",
"@fortawesome/fontawesome-svg-core": "7.2.0",
"@fortawesome/free-brands-svg-icons": "7.2.0",
"@fortawesome/free-solid-svg-icons": "7.2.0",
"@fortawesome/react-fontawesome": "3.3.0",
"@tauri-apps/api": "2.10.1",
"@tauri-apps/plugin-clipboard-manager": "2.3.2",
"@tauri-apps/plugin-dialog": "2.6.0",
"@tauri-apps/plugin-fs": "2.4.5",
"@tauri-apps/plugin-http": "2.5.7",
"@tauri-apps/plugin-notification": "2.3.3",
"@tauri-apps/plugin-opener": "2.5.3",
"@tauri-apps/plugin-os": "2.3.2",
"@tauri-apps/plugin-window-state": "2.4.1",
"axios": "1.13.4",
"next": "16.1.6",
"next": "16.2.1",
"pretty-bytes": "7.1.0",
"react": "19.2.4",
"react-dom": "19.2.4"
},
"devDependencies": {
"@eslint/eslintrc": "3.3.3",
"@tailwindcss/postcss": "4.1.18",
"@tauri-apps/cli": "2.10.0",
"@eslint/eslintrc": "3.3.5",
"@tailwindcss/postcss": "4.2.2",
"@tauri-apps/cli": "2.10.1",
"@types/crypto-js": "4.2.2",
"@types/node": "25.2.0",
"@types/react": "19.2.10",
"@types/node": "25.5.0",
"@types/react": "19.2.14",
"@types/react-dom": "19.2.3",
"crypto-js": "4.2.0",
"eslint": "9.39.2",
"eslint-config-next": "16.1.6",
"tailwindcss": "4.1.18",
"eslint": "9.39.4",
"eslint-config-next": "16.2.1",
"tailwindcss": "4.2.2",
"typescript": "5.9.3"
}
}
+16 -14
View File
@@ -1,6 +1,6 @@
[package]
name = "lncvrt-games-launcher"
version = "1.5.5"
version = "1.8.0"
authors = ["Lncvrt"]
edition = "2024"
@@ -9,32 +9,34 @@ name = "lncvrt_games_launcher_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2.5.4", features = [] }
tauri-build = { version = "2.5.6", features = [] }
[dependencies]
tauri = { version = "2.10.1", features = ["macos-private-api"] }
tauri = { version = "2.10.3", features = ["macos-private-api"] }
tauri-plugin-opener = "2.5.3"
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.149"
tauri-plugin-os = "2.3.2"
reqwest = { version = "0.13.1", default-features = false, features = ["stream", "rustls"] }
tokio = "1.49.0"
futures-util = { version = "0.3.31", features = ["io"] }
reqwest = { version = "0.13.2", default-features = false, features = ["stream", "rustls"] }
tokio = "1.50.0"
futures-util = { version = "0.3.32", features = ["io"] }
tauri-plugin-decorum = "1.1.1"
tauri-plugin-fs = "2.4.5"
zip = "7.2.0"
libc = "0.2.180"
zip = "8.4.0"
tauri-plugin-dialog = "2.6.0"
tauri-plugin-notification = "2.3.3"
sysinfo = "0.38.0"
sysinfo = "0.38.4"
sha2 = "0.10.9"
tauri-plugin-clipboard-manager = "2.3.2"
[target.'cfg(target_os = "linux")'.dependencies]
shlex = "1.3.0"
tauri-plugin-prevent-default = "4.0.4"
openssl = { version = "0.10.76", features = ["v110", "vendored"] }
base64 = "0.22.1"
tauri-plugin-http = "2.5.7"
dashmap = "6.1.0"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-single-instance = "2.3.7"
tauri-plugin-single-instance = "2.4.0"
tauri-plugin-window-state = "2.4.1"
[target.'cfg(target_os = "windows")'.dependencies]
tauri-plugin-decorum = "1.1.1"
+21
View File
@@ -19,6 +19,7 @@
"core:window:allow-is-maximized",
"core:window:allow-start-dragging",
"core:window:allow-toggle-maximize",
"core:window:allow-request-user-attention",
"decorum:allow-show-snap-overlay",
"fs:default",
"dialog:default",
@@ -32,6 +33,26 @@
"allow": [
{ "path": "$APPLOCALDATA/game/**" }
]
},
{
"identifier": "opener:allow-open-path",
"allow": [
{ "path": "$APPLOCALDATA/game/**" }
]
},
{
"identifier": "http:default",
"allow": [
{ "url": "https://games.lncvrt.xyz/api/launcher/latest" },
{ "url": "https://games.lncvrt.xyz/api/launcher/versions" },
{ "url": "https://games.lncvrt.xyz/api/launcher/download" },
{ "url": "https://games.lncvrt.xyz/api/berrydash/account" },
{ "url": "https://games.lncvrt.xyz/api/berrydash/leaderboard/score" },
{ "url": "https://games.lncvrt.xyz/api/berrydash/leaderboard/berry" },
{ "url": "https://games.lncvrt.xyz/api/berrydash/leaderboard/coin" },
{ "url": "https://games.lncvrt.xyz/api/berrydash/leaderboard/legacy" },
{ "url": "https://games.lncvrt.xyz/api/berrydash/leaderboard/total" }
]
}
]
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 249 KiB

After

Width:  |  Height:  |  Size: 248 KiB

+438 -147
View File
@@ -1,7 +1,12 @@
use base64::{Engine as _, engine::general_purpose};
use dashmap::DashMap;
use futures_util::stream::StreamExt;
use openssl::pkey::PKey;
use openssl::sign::Verifier;
use sha2::{Digest, Sha512};
use std::fs;
use std::path::Path;
use std::sync::OnceLock;
use std::time::Instant;
use std::{
fs::{File, create_dir_all},
@@ -11,25 +16,34 @@ use std::{
time::Duration,
};
use sysinfo::System;
use tauri::window::{ProgressBarState, ProgressBarStatus};
use tauri::{AppHandle, Emitter, Manager};
use tauri_plugin_dialog::{DialogExt, MessageDialogKind};
use tauri_plugin_opener::OpenerExt;
use tauri_plugin_os::platform;
use tauri_plugin_prevent_default::Flags;
use tokio::io::AsyncReadExt;
use tokio::{io::AsyncWriteExt, time::timeout};
use zip::ZipArchive;
use zip::result::ZipError;
#[cfg(target_os = "windows")]
use tauri_plugin_decorum::WebviewWindowExt;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
static CANCEL_MAP: OnceLock<DashMap<String, bool>> = OnceLock::new();
fn cancel_map() -> &'static DashMap<String, bool> {
CANCEL_MAP.get_or_init(DashMap::new)
}
fn is_cancelled(name: &str) -> bool {
cancel_map().get(name).map(|v| *v).unwrap_or(false)
}
#[allow(unused)]
fn is_running_by_path(path: &Path) -> bool {
let sys = System::new_all();
let target = path.canonicalize().ok();
if target.is_none() {
return false;
}
let target = target.unwrap();
let target = match path.canonicalize() {
Ok(t) => t,
Err(_) => return false,
};
sys.processes().values().any(|proc| {
proc.exe()
.and_then(|exe| exe.canonicalize().ok())
@@ -37,24 +51,55 @@ fn is_running_by_path(path: &Path) -> bool {
})
}
async fn unzip_to_dir(zip_path: PathBuf, out_dir: PathBuf) -> String {
fn should_skip(name: &str) -> bool {
name.starts_with("__MACOSX/")
|| name == "__MACOSX"
|| name.ends_with("/.DS_Store")
|| name.ends_with(".DS_Store")
}
async fn unzip_to_dir(app: AppHandle, zip_path: PathBuf, out_dir: PathBuf, name: String) -> String {
let _ = app.emit("unzip-start", format!("{}", name));
let res = tauri::async_runtime::spawn_blocking(move || {
let file = File::open(zip_path)?;
let mut archive = ZipArchive::new(BufReader::new(file))?;
let total = archive.len();
for i in 0..archive.len() {
let mut file = archive.by_index(i)?;
let outpath = out_dir.join(file.name());
for i in 0..total {
let mut entry = archive.by_index(i)?;
let entry_name = entry.name();
if file.is_dir() {
if should_skip(entry_name) {
continue;
}
let outpath = out_dir.join(entry_name);
if !outpath.starts_with(&out_dir) {
return Err(ZipError::InvalidArchive("Path traversal detected".into()));
}
if entry.is_dir() {
create_dir_all(&outpath)?;
#[cfg(unix)]
if let Some(mode) = entry.unix_mode() {
std::fs::set_permissions(&outpath, std::fs::Permissions::from_mode(mode))?;
}
} else {
if let Some(parent) = outpath.parent() {
create_dir_all(parent)?;
}
let mut outfile = File::create(&outpath)?;
copy(&mut file, &mut outfile)?;
copy(&mut entry, &mut outfile)?;
#[cfg(unix)]
if let Some(mode) = entry.unix_mode() {
std::fs::set_permissions(&outpath, std::fs::Permissions::from_mode(mode))?;
}
}
let _ = app.emit("unzip-progress", format!("{}:{}:{}", name, i + 1, total));
}
Ok::<(), zip::result::ZipError>(())
@@ -69,12 +114,10 @@ async fn unzip_to_dir(zip_path: PathBuf, out_dir: PathBuf) -> String {
#[tauri::command]
fn folder_size(app: AppHandle, version: String) -> String {
let path = app
.path()
.app_local_data_dir()
.unwrap()
.join("game")
.join(&version);
let path = match app.path().app_local_data_dir() {
Ok(p) => p.join("game").join(&version),
Err(_) => return "-1".to_string(),
};
fn inner(path: &Path) -> u64 {
let mut size = 0;
if let Ok(entries) = fs::read_dir(path) {
@@ -99,57 +142,191 @@ fn folder_size(app: AppHandle, version: String) -> String {
}
}
#[allow(unused_variables)]
#[tauri::command]
async fn download(
app: AppHandle,
url: String,
name: String,
executable: String,
hash: String,
download_type: i8,
mod_id: String,
) -> String {
let client = reqwest::Client::new();
let resp = match client.get(&url).send().await {
Ok(r) => r,
Err(_) => {
println!("[download] Starting download for '{}' from '{}'", name, url);
let _ = app.emit("download-start", format!("{}", name));
let window = app.get_webview_window("main").expect("main window missing");
let downloads_path = match app.path().app_local_data_dir() {
Ok(p) => p.join("downloads"),
Err(e) => {
println!(
"[download] ERROR: Failed to resolve app_local_data_dir: {}",
e
);
let _ = &app.emit("download-stop", format!("{}", name));
return "-1".to_string();
}
};
let game_path = match app.path().app_local_data_dir() {
Ok(p) => p.join("game"),
Err(e) => {
println!("[download] ERROR: Failed to resolve game path: {}", e);
let _ = &app.emit("download-stop", format!("{}", name));
return "-1".to_string();
}
};
let total_size = resp.content_length().unwrap_or(0);
let mut downloaded: u64 = 0;
let mut stream = resp.bytes_stream();
let downloads_path = app.path().app_local_data_dir().unwrap().join("downloads");
let game_path = app.path().app_local_data_dir().unwrap().join("game");
let download_part_path = downloads_path.join(format!("{}.part", name));
let download_zip_path = downloads_path.join(format!("{}.zip", name));
if download_part_path.exists() {
let _ = tokio::fs::remove_file(&download_part_path).await;
}
println!("[download] Part file: {:?}", download_part_path);
println!("[download] Zip file: {:?}", download_zip_path);
let _ = tokio::fs::create_dir_all(&downloads_path).await;
if let Ok(true) = tokio::fs::try_exists(&game_path.join(name.clone())).await {
let _ = tokio::fs::remove_dir_all(&game_path.join(name.clone())).await;
let resume_from: u64 = match tokio::fs::metadata(&download_part_path).await {
Ok(meta) => {
let size = meta.len();
println!(
"[download] Found existing .part file ({} bytes), will attempt to resume",
size
);
size
}
Err(_) => 0,
};
let client = reqwest::Client::new();
let mut request = client.get(&url);
if resume_from > 0 {
request = request.header("Range", format!("bytes={}-", resume_from));
println!("[download] Requesting range: bytes={}-", resume_from);
}
let _ = tokio::fs::create_dir_all(&game_path.join(&name)).await;
let mut file = tokio::fs::File::create(download_part_path.clone())
.await
.unwrap();
let resp = match request.send().await {
Ok(r) => {
println!("[download] HTTP request succeeded, status: {}", r.status());
r
}
Err(e) => {
println!("[download] ERROR: HTTP request failed: {:?}", e);
let _ = &app.emit("download-stop", format!("{}", name));
return "-1".to_string();
}
};
let status = resp.status();
let actual_resume_from = if resume_from > 0 && status == reqwest::StatusCode::OK {
println!(
"[download] Server does not support range requests (got 200), restarting from scratch"
);
let _ = tokio::fs::remove_file(&download_part_path).await;
0
} else if resume_from > 0 && status == reqwest::StatusCode::PARTIAL_CONTENT {
println!("[download] Server accepted range request (206 Partial Content)");
resume_from
} else if resume_from > 0 {
println!(
"[download] Unexpected status {} while resuming, restarting",
status
);
let _ = tokio::fs::remove_file(&download_part_path).await;
0
} else {
0
};
let remaining_size = resp.content_length().unwrap_or(0);
let total_size = actual_resume_from + remaining_size;
println!(
"[download] Resume offset: {} bytes | Remaining: {} bytes | Total: {} bytes ({:.2} MB)",
actual_resume_from,
remaining_size,
total_size,
total_size as f64 / 1_048_576.0
);
let _ = app.emit("download-size", format!("{}:{}", &name, &total_size));
let mut file = if actual_resume_from > 0 {
match tokio::fs::OpenOptions::new()
.append(true)
.open(&download_part_path)
.await
{
Ok(f) => {
println!("[download] Opened .part file in append mode for resume");
f
}
Err(e) => {
println!(
"[download] ERROR: Failed to open .part file for append: {}",
e
);
let _ = &app.emit("download-stop", format!("{}", name));
return "-1".to_string();
}
}
} else {
if download_type == 0 {
if let Ok(true) = tokio::fs::try_exists(&game_path.join(&name)).await {
println!(
"[download] Existing game dir found, removing: {:?}",
game_path.join(&name)
);
let _ = tokio::fs::remove_dir_all(&game_path.join(&name)).await;
}
let _ = tokio::fs::create_dir_all(&game_path.join(&name)).await;
}
match tokio::fs::File::create(&download_part_path).await {
Ok(f) => {
println!("[download] Created new .part file");
f
}
Err(e) => {
println!("[download] ERROR: Failed to create .part file: {}", e);
let _ = &app.emit("download-stop", format!("{}", name));
return "-1".to_string();
}
}
};
let mut downloaded: u64 = actual_resume_from;
let mut stream = resp.bytes_stream();
let start = Instant::now();
let mut chunk_count: u64 = 0;
while let Ok(Some(chunk_result)) = timeout(Duration::from_secs(5), stream.next()).await {
if is_cancelled(&name) {
println!("[download] Cancelled '{}' during chunk loop", name);
cancel_map().remove(&name);
let _ = window.set_progress_bar(ProgressBarState {
status: Some(ProgressBarStatus::None),
progress: Some(0),
});
return "0".to_string();
}
let chunk = match chunk_result {
Ok(c) => c,
Err(_) => {
Err(e) => {
println!(
"[download] ERROR: Failed to read chunk #{}: {}",
chunk_count, e
);
let _ = &app.emit("download-stop", format!("{}", name));
return "-1".to_string();
}
};
if let Err(_) = file.write_all(&chunk).await {
chunk_count += 1;
if let Err(e) = file.write_all(&chunk).await {
println!(
"[download] ERROR: Failed to write chunk #{} to disk: {}",
chunk_count, e
);
let _ = &app.emit("download-stop", format!("{}", name));
return "-1".to_string();
}
@@ -161,96 +338,166 @@ async fn download(
0.0
};
let session_bytes = downloaded - actual_resume_from;
let elapsed_secs = start.elapsed().as_secs_f64();
let speed = (downloaded as f64) / elapsed_secs;
let eta_secs = if total_size > downloaded {
((total_size - downloaded) as f64) / speed
let speed = if elapsed_secs > 0.0 {
session_bytes as f64 / elapsed_secs
} else {
0.0
};
let eta_secs = if total_size > downloaded && speed > 0.0 {
(total_size - downloaded) as f64 / speed
} else {
0.0
};
app.emit(
if chunk_count % 50 == 0 || downloaded == total_size {
println!(
"[download] [{:.1}%] {:.2} MB / {:.2} MB | Speed: {:.1} KB/s | ETA: {:.1}s | Chunks: {}",
progress,
downloaded as f64 / 1_048_576.0,
total_size as f64 / 1_048_576.0,
speed / 1024.0,
eta_secs,
chunk_count,
);
let _ = window.set_progress_bar(ProgressBarState {
status: Some(ProgressBarStatus::Normal),
progress: Some(progress as u64),
});
}
let _ = app.emit(
"download-progress",
format!(
"{}:{:.8}:{}:{}:{:.2}",
&name, progress, downloaded, speed, eta_secs
),
)
.unwrap();
);
}
if total_size > 0 && downloaded < total_size {
println!(
"[download] ERROR: Download incomplete — got {} of {} bytes ({:.1}% complete)",
downloaded,
total_size,
(downloaded as f64 / total_size as f64) * 100.0
);
let _ = window.set_progress_bar(ProgressBarState {
status: Some(ProgressBarStatus::None),
progress: Some(0),
});
return "-1".to_string();
}
app.emit("download-hash-checking", format!("{}", &name))
.unwrap();
println!(
"[download] Download complete: {} bytes total in {:.2}s (this session)",
downloaded,
start.elapsed().as_secs_f64()
);
println!("[download] Verifying SHA-512 hash...");
let _ = app.emit("download-hash-checking", format!("{}", &name));
let download_hash = {
let mut file = tokio::fs::File::open(download_part_path.clone())
.await
.unwrap();
let mut hasher = Sha512::new();
{
let mut buffer = [0; 8192];
loop {
let bytes_read = file.read(&mut buffer).await.unwrap();
if bytes_read == 0 {
break;
}
hasher.update(&buffer[..bytes_read]);
let mut file = match tokio::fs::File::open(&download_part_path).await {
Ok(f) => f,
Err(e) => {
println!(
"[download] ERROR: Failed to open .part file for hashing: {}",
e
);
return "-1".to_string();
}
};
let mut hasher = Sha512::new();
let mut bytes_hashed: u64 = 0;
let mut buffer = [0u8; 8192];
loop {
let bytes_read = match file.read(&mut buffer).await {
Ok(n) => n,
Err(e) => {
println!(
"[download] ERROR: Failed to read file during hashing: {}",
e
);
return "-1".to_string();
}
};
if bytes_read == 0 {
break;
}
hasher.update(&buffer[..bytes_read]);
bytes_hashed += bytes_read as u64;
}
drop(file);
println!("[download] Hashed {} bytes", bytes_hashed);
format!("{:x}", hasher.finalize())
};
println!("[download] Expected hash: {}", hash);
println!("[download] Computed hash: {}", download_hash);
if hash != download_hash {
tokio::fs::remove_file(download_part_path.clone())
.await
.unwrap();
return "-1".to_string();
}
app.emit("download-finishing", format!("{}", &name))
.unwrap();
tokio::fs::rename(download_part_path.clone(), download_zip_path.clone())
.await
.unwrap();
let unzip_res = unzip_to_dir(download_zip_path.clone(), game_path.join(&name)).await;
tokio::fs::remove_file(download_zip_path.clone())
.await
.unwrap();
if unzip_res == "-1" {
println!("[download] ERROR: Hash mismatch! Deleting corrupt .part file.");
let _ = tokio::fs::remove_file(&download_part_path).await;
let _ = window.set_progress_bar(ProgressBarState {
status: Some(ProgressBarStatus::None),
progress: Some(0),
});
return "-1".to_string();
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
{
use std::{fs, os::unix::fs::PermissionsExt};
println!("[download] Hash verified OK");
println!("[download] Renaming .part -> .zip: {:?}", download_zip_path);
if let Err(e) = tokio::fs::rename(&download_part_path, &download_zip_path).await {
println!("[download] ERROR: Failed to rename .part to .zip: {}", e);
let _ = window.set_progress_bar(ProgressBarState {
status: Some(ProgressBarStatus::None),
progress: Some(0),
});
return "-1".to_string();
}
let executable_path = if cfg!(target_os = "linux") {
game_path.join(&name).join(&executable)
} else {
let _ = tokio::fs::create_dir_all(&game_path.join(&name)).await;
println!("[download] Unzipping to: {:?}", game_path.join(&name));
let unzip_res = unzip_to_dir(
app.clone(),
download_zip_path.clone(),
if download_type == 2 {
game_path
.join(&name)
.join(&executable)
.join("Contents")
.join("MacOS")
.join(
&executable
.chars()
.take(executable.chars().count() - 4)
.collect::<String>(),
)
};
.join("BepInEx")
.join("plugins")
.join(&mod_id)
} else {
game_path.join(&name)
},
name.clone(),
)
.await;
let mut perms = fs::metadata(&executable_path).unwrap().permissions();
perms.set_mode(0o755);
fs::set_permissions(&executable_path, perms).unwrap();
println!("[download] Removing zip file: {:?}", download_zip_path);
let _ = tokio::fs::remove_file(&download_zip_path).await;
if unzip_res == "-1" {
println!("[download] ERROR: Unzip failed for '{}'", name);
let _ = window.set_progress_bar(ProgressBarState {
status: Some(ProgressBarStatus::None),
progress: Some(0),
});
return "-1".to_string();
}
return "1".to_string();
println!(
"[download] SUCCESS: '{}' downloaded, verified, and extracted",
&name
);
let _ = window.set_progress_bar(ProgressBarState {
status: Some(ProgressBarStatus::None),
progress: Some(0),
});
"1".to_string()
}
#[allow(unused_variables)]
@@ -263,12 +510,10 @@ fn launch_game(
use_wine: bool,
wine_command: String,
) {
let game_folder = app
.path()
.app_local_data_dir()
.unwrap()
.join("game")
.join(&name);
let game_folder = match app.path().app_local_data_dir() {
Ok(p) => p.join("game").join(&name),
Err(_) => return,
};
if !game_folder.exists() {
return;
}
@@ -278,6 +523,9 @@ fn launch_game(
//if already running on macos, it'll auto take the user to that proccess
#[cfg(any(target_os = "windows", target_os = "linux"))]
{
use tauri_plugin_dialog::DialogExt;
use tauri_plugin_dialog::MessageDialogKind;
if !use_wine && is_running_by_path(&exe_path) {
app.dialog()
.message(format!(
@@ -297,67 +545,105 @@ fn launch_game(
let quoted_path = format!("\"{}\"", exe_path.to_string_lossy());
let cmd = wine_command.replace("%path%", &quoted_path);
Command::new("bash")
if let Err(_) = Command::new("bash")
.arg("-c")
.arg(cmd)
.current_dir(&game_folder)
.spawn()
.unwrap();
{
eprintln!("Failed to launch game with Wine");
}
return;
}
}
if platform() == "macos" {
Command::new("open")
let bepinex_folder = match app.path().app_local_data_dir() {
Ok(p) => p.join("game").join(&name).join("BepInEx"),
Err(_) => return,
};
if platform() == "macos" && !bepinex_folder.exists() {
if let Err(_) = Command::new("open")
.arg(&executable)
.current_dir(&game_folder)
.spawn()
.unwrap();
{
eprintln!("Failed to launch game on macOS");
}
} else {
Command::new(&exe_path)
.current_dir(&game_folder)
.spawn()
.unwrap();
if (platform() == "macos" || platform() == "linux") && bepinex_folder.exists() {
if let Err(_) = Command::new("./run_bepinex.sh")
.arg(&executable)
.current_dir(&game_folder)
.spawn()
{
eprintln!("Failed to launch game");
}
} else {
if let Err(_) = Command::new(&exe_path).current_dir(&game_folder).spawn() {
eprintln!("Failed to launch game");
}
}
}
}
#[tauri::command]
async fn open_folder(app: AppHandle, name: String) {
let game_path = app
.path()
.app_local_data_dir()
.unwrap()
.join("game")
.join(&name);
if game_path.exists() {
app.opener()
.open_path(game_path.to_string_lossy(), None::<&str>)
.unwrap();
} else {
app.dialog()
.message(format!(
"Game folder \"{}\" not found.",
game_path.display()
))
.kind(MessageDialogKind::Error)
.title("Folder not found")
.show(|_| {});
}
fn verify_signature(body: String, signature: String, public_key: String) -> bool {
let Ok(pem) = general_purpose::STANDARD.decode(public_key) else {
return false;
};
let Ok(pubkey) = PKey::public_key_from_pem(&pem) else {
return false;
};
let Ok(sig) = general_purpose::STANDARD.decode(signature) else {
return false;
};
let mut verifier = match Verifier::new_without_digest(&pubkey) {
Ok(v) => v,
Err(_) => return false,
};
verifier
.verify_oneshot(&sig, body.as_bytes())
.unwrap_or(false)
}
#[tauri::command]
async fn cancel_download(name: String) {
println!("[cancel_download] Cancellation requested for '{}'", name);
cancel_map().insert(name.clone(), true);
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
#[allow(unused_variables)]
tauri::Builder::default()
.plugin(tauri_plugin_http::init())
.plugin(
tauri_plugin_prevent_default::Builder::new()
.with_flags(
Flags::FIND
| Flags::CARET_BROWSING
| Flags::DEV_TOOLS
| Flags::DOWNLOADS
| Flags::FOCUS_MOVE
| Flags::RELOAD
| Flags::SOURCE
| Flags::OPEN
| Flags::PRINT
| Flags::CONTEXT_MENU,
)
.build(),
)
.plugin(tauri_plugin_window_state::Builder::new().build())
.plugin(tauri_plugin_clipboard_manager::init())
.plugin(tauri_plugin_notification::init())
.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
let _ = app
.get_webview_window("main")
.expect("no main window")
.set_focus();
if let Some(window) = app.get_webview_window("main") {
let _ = window.set_focus();
}
}))
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_fs::init())
@@ -367,14 +653,19 @@ pub fn run() {
.invoke_handler(tauri::generate_handler![
download,
launch_game,
open_folder,
folder_size
folder_size,
verify_signature,
cancel_download
])
.setup(|app| {
#[cfg(target_os = "windows")]
{
let main_window = app.get_webview_window("main").unwrap();
main_window.create_overlay_titlebar().unwrap();
use tauri_plugin_decorum::WebviewWindowExt;
if let Some(main_window) = app.get_webview_window("main") {
if let Err(e) = main_window.create_overlay_titlebar() {
eprintln!("Failed to create overlay titlebar: {:?}", e);
}
}
}
Ok(())
})
+2 -2
View File
@@ -1,10 +1,10 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Lncvrt Games Launcher",
"version": "1.5.5",
"version": "1.8.0",
"identifier": "xyz.lncvrt.games-launcher",
"build": {
"beforeDevCommand": "next dev",
"beforeDevCommand": "next dev --webpack",
"devUrl": "http://localhost:3000",
"beforeBuildCommand": "next build",
"frontendDist": "../out"
+2 -2
View File
@@ -1,10 +1,10 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Lncvrt Games Launcher",
"version": "1.5.5",
"version": "1.8.0",
"identifier": "xyz.lncvrt.games-launcher",
"build": {
"beforeDevCommand": "next dev",
"beforeDevCommand": "next dev --webpack",
"devUrl": "http://localhost:3000",
"beforeBuildCommand": "next build",
"frontendDist": "../out"
+3 -3
View File
@@ -1,10 +1,10 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Lncvrt Games Launcher",
"version": "1.5.5",
"version": "1.8.0",
"identifier": "xyz.lncvrt.games-launcher",
"build": {
"beforeDevCommand": "next dev",
"beforeDevCommand": "next dev --webpack",
"devUrl": "http://localhost:3000",
"beforeBuildCommand": "next build",
"frontendDist": "../out"
@@ -30,7 +30,7 @@
"active": true,
"targets": ["app"],
"macOS": {
"minimumSystemVersion": "15.0"
"minimumSystemVersion": "13.0"
},
"icon": ["icons/icon.icns"]
}
+2 -2
View File
@@ -1,10 +1,10 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Lncvrt Games Launcher",
"version": "1.5.5",
"version": "1.8.0",
"identifier": "xyz.lncvrt.games-launcher",
"build": {
"beforeDevCommand": "next dev",
"beforeDevCommand": "next dev --webpack",
"devUrl": "http://localhost:3000",
"beforeBuildCommand": "next build",
"frontendDist": "../out"
+45 -18
View File
@@ -1,41 +1,68 @@
'use client'
import { createContext, useContext, ReactNode } from 'react'
import { DownloadProgress } from './types/DownloadProgress'
import { VersionsConfig } from './types/VersionsConfig'
import { NormalConfig } from './types/NormalConfig'
import { ServerVersionsResponse } from './types/ServerVersionsResponse'
import { GameVersion } from './types/GameVersion'
import { Game } from './types/Game'
import {
createContext,
useContext,
ReactNode,
Dispatch,
SetStateAction
} from 'react'
import { DownloadProgress } from '@/types/DownloadProgress'
import { VersionsConfig } from '@/types/VersionsConfig'
import { NormalConfig } from '@/types/NormalConfig'
import { ServerVersionsResponse } from '@/types/ServerVersionsResponse'
import { GameVersion } from '@/types/GameVersion'
import { Game } from '@/types/Game'
import { Mod } from '@/types/Mod'
type GlobalCtxType = {
serverVersionList: ServerVersionsResponse | null
selectedVersionList: string[]
setSelectedVersionList: (v: string[]) => void
setSelectedVersionList: (value: SetStateAction<string[]>) => void
downloadProgress: DownloadProgress[]
setDownloadProgress: (v: DownloadProgress[]) => void
setDownloadProgress: Dispatch<SetStateAction<DownloadProgress[]>>
showPopup: boolean
setShowPopup: (v: boolean) => void
setShowPopup: Dispatch<SetStateAction<boolean>>
popupMode: number | null
setPopupMode: (v: number | null) => void
setPopupMode: Dispatch<SetStateAction<number | null>>
fadeOut: boolean
setFadeOut: (v: boolean) => void
setFadeOut: Dispatch<SetStateAction<boolean>>
downloadedVersionsConfig: VersionsConfig | null
setDownloadedVersionsConfig: (v: VersionsConfig | null) => void
setDownloadedVersionsConfig: Dispatch<SetStateAction<VersionsConfig | null>>
normalConfig: NormalConfig | null
setNormalConfig: (v: NormalConfig | null) => void
setNormalConfig: Dispatch<SetStateAction<NormalConfig | null>>
managingVersion: string | null
setManagingVersion: (v: string | null) => void
setSelectedGame: (v: number | null) => void
setManagingVersion: Dispatch<SetStateAction<string | null>>
setSelectedGame: Dispatch<SetStateAction<number | null>>
getVersionInfo: (id: string | undefined) => GameVersion | undefined
getGameInfo: (id: number | undefined) => Game | undefined
getListOfGames: () => Game[]
getGameInfo: (game: number | undefined) => Game | undefined
getListOfGames(): Game[]
getVersionsAmountData: (gameId: number) => {
installed: number
total: number
} | null
viewingInfoFromDownloads: boolean
version: string | null
downloadVersions: (
list: Array<{
id: string
type: 0 | 1 | 2
modDownload?: number
gameId?: number
modId?: number
modVersion?: string
}>
) => Promise<void>
category: number
setCategory: Dispatch<SetStateAction<number>>
downloadQueue: string[]
setDownloadQueue: Dispatch<SetStateAction<string[]>>
closePopup: () => void
getSpecialVersionsList(game?: number | undefined): GameVersion[]
selectedGame: number | null
setViewingInfoFromDownloads: Dispatch<SetStateAction<boolean>>
showModInfo: Mod | null
setShowModInfo: Dispatch<SetStateAction<Mod | null>>
}
const GlobalCtx = createContext<GlobalCtxType | null>(null)
+6 -2
View File
@@ -14,6 +14,7 @@ body {
--col6: rgb(96, 96, 96);
--col7: rgb(112, 112, 112);
--col8: rgb(128, 128, 128);
--col9: rgb(144, 144, 144);
}
.red-theme {
@@ -26,6 +27,7 @@ body {
--col6: rgb(120, 96, 96);
--col7: rgb(136, 112, 112);
--col8: rgb(152, 128, 128);
--col9: rgb(168, 144, 144);
}
.blue-theme {
@@ -38,6 +40,7 @@ body {
--col6: rgb(96, 96, 120);
--col7: rgb(112, 112, 136);
--col8: rgb(128, 128, 152);
--col9: rgb(144, 144, 168);
}
.purple-theme {
@@ -50,6 +53,7 @@ body {
--col6: rgb(108, 96, 120);
--col7: rgb(124, 112, 136);
--col8: rgb(140, 128, 152);
--col9: rgb(156, 144, 168);
}
.button {
@@ -126,8 +130,8 @@ body {
@apply absolute;
}
.close-button {
@apply flex justify-center items-center absolute text-2xl text-gray-200 hover:text-white cursor-pointer h-12 w-12 p-3 rounded-lg left-2 top-2 transition-colors border;
.popup-top-button {
@apply flex justify-center items-center absolute text-2xl text-gray-200 hover:text-white cursor-pointer h-12 w-12 p-3 rounded-lg top-2 transition-colors border;
}
*:focus {
+1 -1
View File
@@ -9,5 +9,5 @@
}
.downloads-entry {
@apply flex justify-between items-center m-2 p-2 rounded-lg text-gray-200 text-lg transition-colors bg-(--col2) hover:bg-(--col3) border border-(--col4) hover:border-(--col5);
@apply flex justify-between items-center m-2 p-2 rounded-lg text-gray-200 text-lg transition-colors bg-(--col2) hover:bg-(--col3) border border-(--col4) hover:border-(--col5) cursor-pointer;
}
-146
View File
@@ -1,146 +0,0 @@
'use client'
import './Sidebar.css'
import Icon from '@/assets/Icon.png'
import { openUrl } from '@tauri-apps/plugin-opener'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import {
faCog,
faDownload,
faGamepad,
faHexagonNodes
} from '@fortawesome/free-solid-svg-icons'
import { faDiscord } from '@fortawesome/free-brands-svg-icons'
import { platform } from '@tauri-apps/plugin-os'
import { useGlobal } from '../GlobalProvider'
import Image from 'next/image'
import Link from 'next/link'
import { usePathname, useSearchParams } from 'next/navigation'
import { getCurrentWindow } from '@tauri-apps/api/window'
import { Lexend } from 'next/font/google'
const lexend = Lexend({
subsets: ['latin']
})
export default function Sidebar () {
const {
normalConfig,
getListOfGames,
setShowPopup,
setPopupMode,
setFadeOut,
downloadProgress
} = useGlobal()
const pathname = usePathname()
const params = useSearchParams()
return (
<aside className='sidebar'>
<div
className='macos-drag'
hidden={platform() != 'macos'}
onMouseDown={e => {
if (e.buttons === 1) {
if (e.detail === 2) {
getCurrentWindow().toggleMaximize()
} else {
getCurrentWindow().startDragging()
}
}
}}
></div>
<div
className='logo'
style={{
marginTop:
platform() == 'windows'
? '32px'
: platform() == 'macos'
? '28px'
: ''
}}
onMouseDown={e => {
if (platform() != 'macos') return
if (e.buttons === 1) {
if (e.detail === 2) {
getCurrentWindow().toggleMaximize()
} else {
getCurrentWindow().startDragging()
}
}
}}
>
<Image draggable={false} src={Icon} width={36} height={36} alt='' />
<p className={`ml-1 text-[16px] whitespace-nowrap ${lexend.className}`}>
Lncvrt Games Launcher
</p>
</div>
<nav className='nav-links overflow-auto pt-2'>
<Link
draggable={false}
href='/'
className={`link relative flex items-center ${
pathname === '/' || pathname === '/game' ? 'active' : ''
}`}
>
<FontAwesomeIcon icon={faHexagonNodes} className='mr-2' /> Games
</Link>
{getListOfGames()
.sort((a, b) => {
return a.id - b.id
})
.map(i => (
<Link
key={i.id}
draggable={false}
href={'/game?id=' + i.id}
className={`link ${
pathname === '/game' && Number(params.get('id') || 0) == i.id
? 'active'
: ''
} ml-auto w-50 ${
normalConfig?.settings.alwaysShowGamesInSidebar ||
pathname === '/' ||
pathname === '/game'
? ''
: 'hidden'
}`}
>
<div className='flex items-center'>
<FontAwesomeIcon icon={faGamepad} className='mr-1' />
<span className='truncate max-w-full'>{i.name}</span>
</div>
</Link>
))}
<Link
draggable={false}
href='/settings'
className={`link ${pathname === '/settings' ? 'active' : ''}`}
>
<FontAwesomeIcon icon={faCog} className='mr-1' /> Settings
</Link>
<button
onClick={() => openUrl('https://games.lncvrt.xyz/discord')}
className='link mr-auto'
>
<FontAwesomeIcon icon={faDiscord} className='mr-1' /> Community
</button>
</nav>
<div
className='sidebar-downloads'
hidden={downloadProgress.length == 0}
onClick={() => {
setPopupMode(1)
setShowPopup(true)
setFadeOut(false)
}}
>
<p>
<FontAwesomeIcon icon={faDownload} /> Downloads
</p>
</div>
</aside>
)
}
-27
View File
@@ -1,27 +0,0 @@
'use client'
import { useGlobal } from '../GlobalProvider'
export default function VersionChangelog () {
const { getVersionInfo, managingVersion, downloadedVersionsConfig } =
useGlobal()
if (!managingVersion || !downloadedVersionsConfig) return <></>
const versionInfo = getVersionInfo(managingVersion)
return (
<>
<p className='text-xl text-center'>
Viewing changelog for {versionInfo?.displayName}
</p>
<div className='popup-content text-center p-2'>
<span
className='whitespace-pre-wrap select-text'
dangerouslySetInnerHTML={{
__html: versionInfo?.changelog ? atob(versionInfo.changelog) : ''
}}
/>
</div>
</>
)
}
-125
View File
@@ -1,125 +0,0 @@
'use client'
import {
faArrowUpRightFromSquare,
faCheck,
faCode,
faHardDrive,
faShieldHalved,
faWarning
} from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { useGlobal } from '../GlobalProvider'
import { invoke } from '@tauri-apps/api/core'
import { useEffect, useState } from 'react'
import prettyBytes from 'pretty-bytes'
export default function VersionInfo () {
const {
getGameInfo,
getVersionInfo,
managingVersion,
downloadedVersionsConfig,
viewingInfoFromDownloads,
setPopupMode
} = useGlobal()
const [versionSize, setVersionSize] = useState<number | null>(null)
useEffect(() => {
invoke<string>('folder_size', {
version: managingVersion
}).then(size => {
setVersionSize(parseInt(size, 10))
})
}, [managingVersion, setVersionSize])
if (!managingVersion || !downloadedVersionsConfig) return <></>
const versionInfo = getVersionInfo(managingVersion)
const gameInfo = getGameInfo(versionInfo?.game)
return (
<>
<p className='text-xl text-center'>
Viewing info for {versionInfo?.displayName}
</p>
<div className='popup-content flex flex-col items-center justify-center gap-2 h-full'>
<div
className='entry-info-item btntheme2'
hidden={viewingInfoFromDownloads}
>
<p>
Installed{' '}
{new Intl.DateTimeFormat(undefined).format(
downloadedVersionsConfig.list[managingVersion] ?? 0
)}
</p>
</div>
<div
className='entry-info-item btntheme2'
hidden={!versionInfo || versionInfo.releaseDate == 0}
>
<p>
Released{' '}
{new Intl.DateTimeFormat(undefined).format(
versionInfo?.releaseDate ? versionInfo.releaseDate * 1000 : 0
)}
</p>
</div>
<div className='entry-info-item btntheme2' hidden={!gameInfo?.official}>
<FontAwesomeIcon icon={faCheck} color='#19c84b' />
<p>Official</p>
</div>
<div className='entry-info-item btntheme2' hidden={gameInfo?.official}>
<FontAwesomeIcon
icon={gameInfo?.verified ? faShieldHalved : faWarning}
color={gameInfo?.verified ? '#19c84b' : '#ffc800'}
/>
<p>{gameInfo?.verified ? 'Verified' : 'Unverified'}</p>
</div>
<div
className='entry-info-item btntheme2'
hidden={gameInfo?.developer == null}
>
<FontAwesomeIcon icon={faCode} color='lightgray' />
<p>Developer: {gameInfo?.developer}</p>
</div>
<div
className='entry-info-item btntheme2'
hidden={viewingInfoFromDownloads || versionSize === null}
>
<FontAwesomeIcon icon={faHardDrive} color='lightgray' />
<p>
Size on disk:{' '}
{versionSize !== null
? prettyBytes(versionSize, {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})
: 'Loading...'}
</p>
</div>
<div className='entry-info-item btntheme2' hidden={!versionInfo}>
<FontAwesomeIcon icon={faHardDrive} color='lightgray' />
<p>
Size when downloaded (zipped):{' '}
{versionInfo
? prettyBytes(versionInfo.size, {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})
: 'Loading...'}
</p>
</div>
<div
className='entry-info-item btntheme2'
onClick={() => setPopupMode(4)}
hidden={!versionInfo?.changelog}
>
<p>View Changelog</p>
<FontAwesomeIcon icon={faArrowUpRightFromSquare} color='lightgray' />
</div>
</div>
</>
)
}
@@ -0,0 +1,339 @@
'use client'
import { useCallback, useEffect, useState } from 'react'
import { BirdColor } from '@/types/BerryDash/BirdColor'
import { GetIconForUser } from '@/lib/BerryDash'
import Image from 'next/image'
import './styles.css'
import { useRouter } from 'next/navigation'
import { platform } from '@tauri-apps/plugin-os'
import { fetch } from '@tauri-apps/plugin-http'
import { verifySignature } from '@/lib/Util'
interface BaseEntry {
id: number
username: string
value: number
icon: number
overlay: number
birdColor: BirdColor
overlayColor: BirdColor
customIcon: string | null
}
interface Stats {
highScore: string
totalNormalBerries: string
totalPoisonBerries: string
totalSlowBerries: string
totalUltraBerries: string
totalSpeedyBerries: string
totalCoinBerries: string
totalRandomBerries: string
totalAntiBerries: string
totalGoldenBerries: string
coins: string
}
interface LeaderboardEntry extends BaseEntry {
type: 'leaderboard'
value: number
}
interface Account extends BaseEntry {
type: 'account'
stats: Stats
xp: bigint
}
export function calculateXP (
normalBerries: bigint,
poisonBerries: bigint,
slowBerries: bigint,
ultraBerries: bigint,
speedyBerries: bigint,
coinBerries: bigint,
randomBerries: bigint,
antiBerries: bigint,
goldenBerries: bigint
): bigint {
let totalXp = 0n
totalXp += normalBerries
totalXp -= poisonBerries
totalXp -= slowBerries
totalXp += ultraBerries * 5n
totalXp += speedyBerries * 10n
totalXp += coinBerries * 10n
totalXp += randomBerries
totalXp -= antiBerries
totalXp += goldenBerries * 4n
if (totalXp < 0n) totalXp = 0n
return totalXp
}
export function calculateLevel (xp: bigint): number {
const levelDivisor = 50.0
const xpNumber = Number(xp)
const discriminant = 95 * 95 + levelDivisor * 2 * xpNumber
const level = (-95 + Math.sqrt(discriminant)) / levelDivisor
return Math.floor(level) + 1
}
export default function BerryDashLeaderboards () {
const [selected, setSelected] = useState<number>(-1)
const [selectedBerryOption, setSelectedBerryOption] = useState<number>(0)
const [entries, setEntries] = useState<(LeaderboardEntry | Account)[]>([])
const router = useRouter()
const Refresh = useCallback(async () => {
try {
if (selected == 3 || selected == 4) {
const response = await fetch(
'https://games.lncvrt.xyz/api/berrydash/account?username='
)
const signature = response.headers.get('x-signature') ?? ''
const data = await response.json()
if (
(await verifySignature(JSON.stringify(data), signature)) &&
data.success
) {
let accounts = data.data as Account[]
accounts = accounts.map(acc => {
const xp = calculateXP(
BigInt(acc.stats.totalNormalBerries),
BigInt(acc.stats.totalPoisonBerries),
BigInt(acc.stats.totalSlowBerries),
BigInt(acc.stats.totalUltraBerries),
BigInt(acc.stats.totalSpeedyBerries),
BigInt(acc.stats.totalCoinBerries),
BigInt(acc.stats.totalRandomBerries),
BigInt(acc.stats.totalAntiBerries),
BigInt(acc.stats.totalGoldenBerries)
)
return { ...acc, xp }
})
accounts.sort((a, b) => (b.xp > a.xp ? 1 : b.xp < a.xp ? -1 : 0))
setEntries(accounts)
} else console.log('Failed')
} else {
const response = await fetch(
'https://games.lncvrt.xyz/api/berrydash/leaderboard/' +
(selected == 0
? 'score'
: selected == 1
? 'berry?berry=' + selectedBerryOption
: selected == 2
? 'coin'
: selected == 5
? 'legacy'
: 'total')
)
const signature = response.headers.get('x-signature') ?? ''
const data = await response.json()
if (
(await verifySignature(JSON.stringify(data), signature)) &&
data.success
)
setEntries(data.data as LeaderboardEntry[])
}
} catch {
setEntries([])
}
}, [selected, selectedBerryOption])
useEffect(() => {
document.title = 'Lncvrt Games - Berry Dash Leaderboards'
}, [])
useEffect(() => {
if (selected != -1) setTimeout(() => Refresh(), 0)
}, [selected, Refresh])
return (
<div className='mx-4 mt-4'>
<div className='flex justify-between items-center mb-4'>
<p className='text-3xl'>Berry Dash Leaderboards</p>
<div className='flex gap-2'>
<button
className='button btntheme1'
onClick={() => {
setEntries([])
Refresh()
}}
title='Click to refresh the leaderboards.'
hidden={selected == -1}
>
Refresh
</button>
<button
className='button btntheme1'
onClick={() => {
if (selected == -1) router.push('/game?id=1')
else {
setSelected(-1)
setSelectedBerryOption(0)
setEntries([])
}
}}
title='Click to go up a level.'
>
Back
</button>
</div>
</div>
<div
className={`box ${
platform() == 'windows'
? 'h-[calc(100vh-116px)]'
: 'h-[calc(100vh-84px)]'
}`}
>
{selected == -1 ? (
<>
<p className='text-center mt-2 text-xl'>Select a Leaderboard</p>
<div className='flex flex-col gap-2 mt-2 items-center justify-center'>
<button
className='leaderboard-button'
onClick={() => setSelected(0)}
>
Score Leaderboard
</button>
<button
className='leaderboard-button'
onClick={() => setSelected(1)}
>
Berry Leaderboard
</button>
<button
className='leaderboard-button'
onClick={() => setSelected(2)}
>
Coins Leaderboard
</button>
<button
className='leaderboard-button'
onClick={() => setSelected(3)}
>
Level Leaderboard
</button>
<button
className='leaderboard-button'
onClick={() => setSelected(4)}
>
Total XP Leaderboard
</button>
<button
className='leaderboard-button'
onClick={() => setSelected(5)}
>
Legacy Leaderboard
</button>
<button
className='leaderboard-button'
onClick={() => setSelected(6)}
>
Total Berries Leaderboard
</button>
</div>
</>
) : (
<>
<div
className={`flex flex-col gap-2 overflow-y-auto ${
selected == 1
? platform() == 'windows'
? 'h-[calc(100vh-156px)]'
: 'h-[calc(100vh-124px)]'
: platform() == 'windows'
? 'h-[calc(100vh-128px)]'
: 'h-[calc(100vh-96px)]'
} px-1`}
>
{entries.map((item, index) => {
const isAccount = 'stats' in item
const isLeaderboard = 'value' in item && !isAccount
return (
<div
key={item.id}
className='leaderboard-entry flex justify-between items-center'
>
<div className='flex items-center gap-1'>
<Image
src={
!item.customIcon
? `https://games.lncvrt.xyz/api/berrydash/render-icon?icon=${
item.icon == 1
? GetIconForUser(item.id)
: item.icon
}&overlay=${item.overlay}&birdR=${
item.birdColor[0]
}&birdG=${item.birdColor[1]}&birdB=${
item.birdColor[2]
}&overlayR=${item.overlayColor[0]}&overlayG=${
item.overlayColor[1]
}&overlayB=${item.overlayColor[2]}`
: `https://games.lncvrt.xyz/api/berrydash/icon-marketplace/icon?id=${item.customIcon}&raw=true`
}
width={48}
height={48}
alt=''
className='pointer-events-none scale-x-[-1]'
/>
<p>
{item.username} (#{index + 1})
</p>
</div>
{isLeaderboard ? (
<p>
{selected === 1 || selected === 6
? 'Berries'
: selected === 2
? 'Coins'
: 'Score'}
: {item.value.toLocaleString()}
</p>
) : isAccount ? (
<p>
{selected === 3 ? 'Level' : 'XP'}:{' '}
{selected === 3
? calculateLevel(item.xp).toLocaleString()
: item.xp.toLocaleString()}
</p>
) : null}
</div>
)
})}
</div>
{selected == 1 && (
<div className='flex justify-center'>
<select
className='mt-1.25 bg-(--col2) border border-(--col4) rounded-md'
value={selectedBerryOption}
onChange={e => setSelectedBerryOption(Number(e.target.value))}
>
<option value='0'>Normal Berry</option>
<option value='1'>Poison Berry</option>
<option value='2'>Slow Berry</option>
<option value='3'>Ultra Berry</option>
<option value='4'>Speedy Berry</option>
<option value='5'>Coin Berry</option>
<option value='6'>Random Berry</option>
<option value='7'>Anti Berry</option>
<option value='8'>Golden Berry</option>
</select>
</div>
)}
</>
)}
</div>
</div>
)
}
@@ -0,0 +1,13 @@
@import "tailwindcss";
.box {
@apply bg-(--col1) border border-(--col3) rounded-lg w-auto p-1;
}
.leaderboard-button {
@apply bg-(--col2) hover:bg-(--col4) border border-(--col4) hover:border-(--col6) rounded-lg px-4 py-2 inline-block transition-all duration-200 cursor-pointer;
}
.leaderboard-entry {
@apply rounded-lg text-gray-200 text-lg px-1 py-0.5 transition-colors bg-(--col2) hover:bg-(--col3) border border-(--col4) hover:border-(--col5) cursor-pointer;
}
+181 -118
View File
@@ -1,13 +1,17 @@
'use client'
import { useEffect, useState } from 'react'
import '../Installs.css'
import { useEffect } from 'react'
import '@/app/Installs.css'
import { invoke } from '@tauri-apps/api/core'
import { useGlobal } from '../GlobalProvider'
import { useSearchParams } from 'next/navigation'
import { useGlobal } from '@/app/GlobalProvider'
import { useRouter, useSearchParams } from 'next/navigation'
import { platform } from '@tauri-apps/plugin-os'
import { faWarning } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { ask } from '@tauri-apps/plugin-dialog'
import { BaseDirectory, exists, remove } from '@tauri-apps/plugin-fs'
import { writeVersionsConfig } from '@/lib/BazookaManager'
import { openFolder } from '@/lib/Util'
export default function Installs () {
const {
@@ -22,17 +26,20 @@ export default function Installs () {
getVersionInfo,
getGameInfo,
setSelectedGame,
serverVersionList
serverVersionList,
category,
setCategory,
setDownloadedVersionsConfig,
downloadVersions,
downloadProgress
} = useGlobal()
const params = useSearchParams()
const router = useRouter()
const id = Number(params.get('id') || 0)
const game = serverVersionList?.games.find(g => g.id === id)
const [category, setCategory] = useState<number>(-1)
const [lastId, setLastId] = useState(id)
useEffect(() => {
if (!showPopup) return
setSelectedVersionList([])
@@ -40,16 +47,46 @@ export default function Installs () {
if (!id || !game) return <p>Invalid game</p>
if (lastId !== id) {
setLastId(id)
setCategory(-1)
const needsRevisionUpdate = (
lastRevision: number | undefined,
version: string
) => {
if (!lastRevision) return false
return (
lastRevision > 0 &&
(downloadedVersionsConfig == undefined
? 0
: downloadedVersionsConfig?.list[version]) /
1000 <=
lastRevision
)
}
return (
<div className='mx-4 mt-4'>
<div className='flex justify-between items-center mb-4'>
<p className='text-3xl'>Installs</p>
<p
className={`text-3xl truncate ${
category != -1
? 'w-[calc(100vw-495px)]'
: game.id == 1
? 'w-[calc(100vw-560px)]'
: 'w-[calc(100vw-440px)]'
}`}
>
{game.name} Installs
</p>
<div className='flex gap-2'>
<button
className='button btntheme1'
onClick={() => {
router.push('/game/berrydash/leaderboards')
}}
title='View the leaderboards for this game.'
hidden={game.id != 1}
>
Leaderboards
</button>
<button
className='button btntheme1'
onClick={() => {
@@ -77,7 +114,7 @@ export default function Installs () {
<div className='downloads-container'>
<div
className={`downloads-scroll ${
platform() === 'windows'
platform() == 'windows'
? 'h-[calc(100vh-116px)]'
: 'h-[calc(100vh-84px)]'
}`}
@@ -93,7 +130,7 @@ export default function Installs () {
if (!info) return false
if (
platform() === 'linux' &&
platform() == 'linux' &&
info.wine &&
!normalConfig?.settings.useWineOnUnixWhenNeeded
)
@@ -107,22 +144,10 @@ export default function Installs () {
.map(([key, value]) => {
return (
<div
key={value}
className={`downloads-entry ${
normalConfig?.settings.useLegacyInteractButtons
? ''
: 'cursor-pointer'
}`}
title={
normalConfig?.settings.useLegacyInteractButtons
? ''
: 'Click to view category'
}
onClick={() => {
if (normalConfig?.settings.useLegacyInteractButtons)
return
setCategory(Number(key))
}}
key={key}
className={'downloads-entry'}
title={'Click to view category'}
onClick={() => setCategory(Number(key))}
>
<div className='h-18 w-screen relative'>
<p className='text-2xl'>{value}</p>
@@ -141,7 +166,7 @@ export default function Installs () {
const info = getVersionInfo(v)
if (!info) return false
if (
platform() === 'linux' &&
platform() == 'linux' &&
info.wine &&
!normalConfig?.settings
.useWineOnUnixWhenNeeded
@@ -156,17 +181,6 @@ export default function Installs () {
})()}
</p>
</div>
<button
className='button absolute right-0 bottom-0'
hidden={
!normalConfig?.settings.useLegacyInteractButtons
}
title='Click to view category'
onClick={() => setCategory(Number(key))}
>
Installs
</button>
</div>
</div>
)
@@ -187,7 +201,7 @@ export default function Installs () {
const info = getVersionInfo(v)
if (!info) return false
if (
platform() === 'linux' &&
platform() == 'linux' &&
info.wine &&
!normalConfig?.settings.useWineOnUnixWhenNeeded
)
@@ -202,18 +216,78 @@ export default function Installs () {
.map(entry => (
<div
key={entry}
className={`downloads-entry ${
normalConfig?.settings.useLegacyInteractButtons
? ''
: 'cursor-pointer'
}`}
className={'downloads-entry'}
title={
normalConfig?.settings.useLegacyInteractButtons
? ''
: 'Click to launch game. Right-click to manage this version install'
'Click to launch game. Right-click to manage this version install'
}
onClick={async () => {
if (normalConfig?.settings.useLegacyInteractButtons) return
if (
needsRevisionUpdate(
getVersionInfo(entry)?.lastRevision,
entry
)
) {
const answer = await ask(
'Before proceeding, if you do not want your installation directory wiped just yet, please backup the files to another directory. When you click "Yes", it will be wiped. Click "No" if you want to open the installation folder instead.',
{
title: 'Revision Update',
kind: 'warning'
}
)
if (answer) {
const answer2 = await ask(
'Are you sure you want to update? If you did not read the last popup, please go back and read it.',
{
title: 'Revision Update',
kind: 'warning'
}
)
if (!answer2) return
//open downloads popup
setPopupMode(1)
setShowPopup(true)
setFadeOut(false)
//uninstall
setDownloadedVersionsConfig(prev => {
if (!prev) return prev
const updatedList = Object.fromEntries(
Object.entries(prev.list).filter(
([k]) => k !== entry
)
)
const updatedConfig = {
...prev,
list: updatedList
}
writeVersionsConfig(updatedConfig)
return updatedConfig
})
if (
await exists('game/' + entry, {
baseDir: BaseDirectory.AppLocalData
})
)
await remove('game/' + entry, {
baseDir: BaseDirectory.AppLocalData,
recursive: true
})
//reinstall
setSelectedVersionList([entry])
downloadVersions([
{
id: entry,
type: 0
}
])
} else {
openFolder(entry)
}
return
}
const verInfo = getVersionInfo(entry)
if (verInfo == undefined) return
const gameInfo = getGameInfo(verInfo.game)
@@ -223,7 +297,7 @@ export default function Installs () {
executable: verInfo.executable,
displayName: verInfo.displayName,
useWine: !!(
platform() === 'linux' &&
platform() == 'linux' &&
verInfo.wine &&
normalConfig?.settings.useWineOnUnixWhenNeeded
),
@@ -232,8 +306,6 @@ export default function Installs () {
}}
onContextMenu={e => {
e.preventDefault()
if (normalConfig?.settings.useLegacyInteractButtons) return
setManagingVersion(entry)
setPopupMode(2)
setShowPopup(true)
@@ -242,14 +314,13 @@ export default function Installs () {
>
<div className='h-18 w-screen relative'>
<p className='text-2xl'>
{getVersionInfo(entry)?.displayName}
{getVersionInfo(entry)?.displayName}{' '}
</p>
<div className='flex gap-2 absolute left-0 bottom-0'>
<div
className='entry-info-item'
title='The date the game was installed.'
onClick={e => e.stopPropagation()}
>
<p>
Installed{' '}
@@ -263,76 +334,68 @@ export default function Installs () {
title='This version is using wine. It cannot be guarenteed to work fully and might not work at all.'
hidden={
!(
platform() === 'linux' &&
getVersionInfo(entry)?.wine
platform() == 'linux' && getVersionInfo(entry)?.wine
) ||
needsRevisionUpdate(
getVersionInfo(entry)?.lastRevision,
entry
)
}
onClick={e => e.stopPropagation()}
>
<FontAwesomeIcon icon={faWarning} color='#ffc800' />
<p>Uses wine</p>
</div>
<div
className='entry-info-item'
hidden={
!needsRevisionUpdate(
getVersionInfo(entry)?.lastRevision,
entry
)
}
>
<FontAwesomeIcon icon={faWarning} color='#ffc800' />
<p>Needs revision update!</p>
</div>
</div>
<button
className='absolute right-0 bottom-0 button'
title='Click to manage mods for this game!'
onClick={async e => {
e.stopPropagation()
<div className='flex gap-2 absolute right-0 bottom-0'>
<button
className='button'
onClick={e => {
e.stopPropagation()
setManagingVersion(entry)
setPopupMode(3)
setShowPopup(true)
setFadeOut(false)
}}
title='Click to view version info'
>
View Info
</button>
<button
className='button'
hidden={
!normalConfig?.settings.useLegacyInteractButtons
if (
!(await exists('game/' + entry + '/BepInEx', {
baseDir: BaseDirectory.AppLocalData
}))
) {
if (
(await ask(
"You don't have BepInEx (the mod loader for Unity Games), would you like to install it now? It is about 1MB in size. If you choose yes, it will download the recommended BepInEx version for " +
getVersionInfo(entry)?.displayName +
'.',
{ title: 'BepInEx not found!', kind: 'error' }
)) &&
!downloadProgress.find(d => d.version == entry)
) {
downloadVersions([
{
id: entry,
type: 1
}
])
}
return
}
onClick={e => {
e.stopPropagation()
setManagingVersion(entry)
setPopupMode(2)
setShowPopup(true)
setFadeOut(false)
}}
title='Click to manage this version install'
>
Manage
</button>
<button
className='button'
onClick={e => {
e.stopPropagation()
const verInfo = getVersionInfo(entry)
if (verInfo == undefined) return
const gameInfo = getGameInfo(verInfo.game)
if (gameInfo == undefined) return
invoke('launch_game', {
name: verInfo.id,
executable: verInfo.executable,
displayName: verInfo.displayName,
useWine: !!(
platform() === 'linux' &&
verInfo.wine &&
normalConfig?.settings.useWineOnUnixWhenNeeded
),
wineCommand:
normalConfig?.settings.wineOnUnixCommand
})
}}
hidden={
!normalConfig?.settings.useLegacyInteractButtons
}
title='Click to launch game'
>
Launch
</button>
</div>
setManagingVersion(entry)
setPopupMode(3)
setShowPopup(true)
setFadeOut(false)
}}
hidden={!getVersionInfo(entry)?.modSupportDownload}
>
Mod Manager
</button>
</div>
</div>
))
+429 -619
View File
File diff suppressed because it is too large. Load diff
+6 -23
View File
@@ -3,7 +3,6 @@
import { useEffect } from 'react'
import './Installs.css'
import { useGlobal } from './GlobalProvider'
import Link from 'next/link'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import {
faCheck,
@@ -24,7 +23,8 @@ export default function Installs () {
normalConfig,
setSelectedGame,
getListOfGames,
getVersionsAmountData
getVersionsAmountData,
setCategory
} = useGlobal()
const router = useRouter()
@@ -54,7 +54,7 @@ export default function Installs () {
<div className='downloads-container'>
<div
className={`downloads-scroll ${
platform() === 'windows'
platform() == 'windows'
? 'h-[calc(100vh-116px)]'
: 'h-[calc(100vh-84px)]'
}`}
@@ -68,18 +68,10 @@ export default function Installs () {
.map(i => (
<div
key={i.id}
className={`downloads-entry ${
normalConfig?.settings.useLegacyInteractButtons
? ''
: 'cursor-pointer'
}`}
title={
normalConfig?.settings.useLegacyInteractButtons
? ''
: 'Click to view game installs'
}
className={'downloads-entry'}
title={'Click to view game installs'}
onClick={() => {
if (normalConfig?.settings.useLegacyInteractButtons) return
setCategory(-1)
router.push('/game?id=' + i.id)
}}
>
@@ -127,15 +119,6 @@ export default function Installs () {
<p>{i.verified ? 'Verified' : 'Unverified'}</p>
</div>
</div>
<Link
className='button absolute right-0 bottom-0'
href={'/game?id=' + i.id}
hidden={!normalConfig?.settings.useLegacyInteractButtons}
title='Click to view game installs'
>
Installs
</Link>
</div>
</div>
))
+4 -32
View File
@@ -1,18 +1,16 @@
'use client'
import { useEffect, useState } from 'react'
import { Setting } from '../componets/Setting'
import { writeNormalConfig } from '../util/BazookaManager'
import { useGlobal } from '../GlobalProvider'
import { copyToClipboard } from '../util/Clipboard'
import { Setting } from '@/componets/Setting'
import { writeNormalConfig } from '@/lib/BazookaManager'
import { useGlobal } from '@/app/GlobalProvider'
import { copyToClipboard } from '@/lib/Clipboard'
import { platform } from '@tauri-apps/plugin-os'
export default function Settings () {
const [allowNotifications, setAllowNotifications] = useState(false)
const [alwaysShowGamesInSidebar, setAlwaysShowGamesInSidebar] =
useState(false)
const [useLegacyInteractButtons, setUseLegacyInteractButtons] =
useState(false)
const [useWineOnUnixWhenNeeded, setUseWineOnUnixWhenNeeded] = useState(false)
const [wineOnUnixCommand, setWineOnUnixCommand] = useState('wine %path%')
const [theme, setTheme] = useState(0)
@@ -27,9 +25,6 @@ export default function Settings () {
setAlwaysShowGamesInSidebar(
normalConfig.settings.alwaysShowGamesInSidebar
)
setUseLegacyInteractButtons(
normalConfig.settings.useLegacyInteractButtons
)
setUseWineOnUnixWhenNeeded(
normalConfig.settings.useWineOnUnixWhenNeeded
)
@@ -92,29 +87,6 @@ export default function Settings () {
}}
title="This setting will make it so when you are on a page like this, the games won't disappear."
/>
<Setting
label='Show Installs/Launch/Manage Buttons'
value={useLegacyInteractButtons}
onChange={async () => {
if (!normalConfig) return
setUseLegacyInteractButtons(!useLegacyInteractButtons)
setNormalConfig({
...normalConfig,
settings: {
...normalConfig.settings,
useLegacyInteractButtons: !useLegacyInteractButtons
}
})
await writeNormalConfig({
...normalConfig,
settings: {
...normalConfig.settings,
useLegacyInteractButtons: !useLegacyInteractButtons
}
})
}}
title='Enable the legacy method of using the installs/launch/manage buttons. In the future this setting may be removed so try and get used to the new method.'
/>
<Setting
label='Use wine when needed to launch games'
value={useWineOnUnixWhenNeeded}
-13
View File
@@ -1,13 +0,0 @@
export class DownloadProgress {
constructor (
public version: string,
public progress: number,
public progressBytes: number,
public failed: boolean,
public queued: boolean,
public hash_checking: boolean,
public finishing: boolean,
public speed: number,
public etaSecs: number
) {}
}
-10
View File
@@ -1,10 +0,0 @@
export interface LeaderboardEntry {
username: string
userid: bigint
value: bigint
icon: number
overlay: number
birdColor: number[]
overlayColor: number[]
customIcon: string | null
}
-6
View File
@@ -1,6 +0,0 @@
import { LeaderboardEntry } from './LeaderboardEntry'
export interface LeaderboardResponse {
entries: LeaderboardEntry[]
customIcons: Record<string, string>
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 1.2 MiB

File renamed without changes.
File renamed without changes.
@@ -1,7 +1,7 @@
import './Setting.css'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faCheck } from '@fortawesome/free-solid-svg-icons'
import { SettingProps } from '../types/SettingProps'
import { SettingProps } from '@/types/SettingProps'
export function Setting ({
label,
@@ -13,10 +13,6 @@
@apply bg-(--col3) border-(--col4);
}
.logo {
@apply flex items-center pl-2 h-10 w-60 pt-2;
}
.nav-links {
@apply flex flex-col px-4 space-y-1;
}
+212
View File
@@ -0,0 +1,212 @@
'use client'
import './Sidebar.css'
import Icon from '@/assets/Icon.png'
import { openUrl } from '@tauri-apps/plugin-opener'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import {
faCog,
faDownload,
faGamepad,
faHexagonNodes,
faLayerGroup
} from '@fortawesome/free-solid-svg-icons'
import { faDiscord } from '@fortawesome/free-brands-svg-icons'
import { platform } from '@tauri-apps/plugin-os'
import { useGlobal } from '@/app/GlobalProvider'
import Image from 'next/image'
import Link from 'next/link'
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
import { getCurrentWindow } from '@tauri-apps/api/window'
import { Lexend } from 'next/font/google'
import React from 'react'
const lexend = Lexend({
subsets: ['latin']
})
export default function Sidebar () {
const {
normalConfig,
getListOfGames,
setShowPopup,
setPopupMode,
setFadeOut,
downloadProgress,
downloadedVersionsConfig,
getVersionInfo,
category,
setCategory
} = useGlobal()
const pathname = usePathname()
const params = useSearchParams()
const router = useRouter()
return (
<aside className='sidebar'>
<div
className='macos-drag'
hidden={platform() != 'macos'}
onMouseDown={e => {
if (e.buttons === 1) {
if (e.detail === 2) {
getCurrentWindow().toggleMaximize()
} else {
getCurrentWindow().startDragging()
}
}
}}
></div>
<div
className={`flex items-center h-10 w-60 ${
(platform() == 'windows' ? 'pl-1 pt-1' : 'pl-2 pt-2') +
(platform() == 'macos' ? ' mt-7' : '')
}`}
onMouseDown={e => {
if (platform() != 'macos') return
if (e.buttons === 1) {
if (e.detail === 2) {
getCurrentWindow().toggleMaximize()
} else {
getCurrentWindow().startDragging()
}
}
}}
>
<Image draggable={false} src={Icon} width={36} height={36} alt='' />
<p className={`ml-1 text-[16px] whitespace-nowrap ${lexend.className}`}>
Lncvrt Games Launcher
</p>
</div>
<nav className='nav-links overflow-auto pt-2'>
<Link
draggable={false}
href='/'
className={`link relative flex items-center ${
pathname === '/' || pathname.startsWith('/game') ? 'active' : ''
}`}
>
<FontAwesomeIcon icon={faHexagonNodes} className='mr-2' /> Games
</Link>
{getListOfGames()
.sort((a, b) => {
return a.id - b.id
})
.map(i => (
<React.Fragment key={i.id}>
<div
draggable={false}
className={`link ${
(pathname === '/game' &&
Number(params.get('id') || 0) == i.id) ||
(i.id == 1 && pathname === '/game/berrydash/leaderboards')
? 'active'
: ''
} ml-auto w-50 ${
normalConfig?.settings.alwaysShowGamesInSidebar ||
pathname === '/' ||
pathname.startsWith('/game')
? ''
: 'hidden'
}`}
onClick={() => {
setCategory(-1)
router.push('/game?id=' + i.id)
}}
>
<div className='flex items-center'>
<FontAwesomeIcon
icon={
Object.entries(i.categoryNames).length > 0
? faLayerGroup
: faGamepad
}
className='mr-1'
/>
<span className='truncate max-w-full'>{i.name}</span>
</div>
</div>
{Object.entries(i.categoryNames)
.sort(([a], [b]) => Number(b) - Number(a))
.filter(([key]) => {
const count = Object.keys(
downloadedVersionsConfig?.list ?? {}
).filter(v => {
const info = getVersionInfo(v)
if (!info) return false
if (
platform() == 'linux' &&
info.wine &&
!normalConfig?.settings.useWineOnUnixWhenNeeded
)
return false
return info.game === i.id && info.category === Number(key)
}).length
return count >= 1
})
.map(([key, value]) => (
<div
key={`${i.id}-${key}`}
draggable={false}
className={`link ${
((pathname === '/game' &&
Number(params.get('id') || 0) == i.id) ||
(i.id == 1 &&
pathname === '/game/berrydash/leaderboards')) &&
category == Number(key)
? 'active'
: ''
} ml-auto w-47.5 ${
normalConfig?.settings.alwaysShowGamesInSidebar ||
pathname === '/' ||
pathname.startsWith('/game')
? ''
: 'hidden'
}`}
onClick={() => {
setCategory(Number(key))
router.push('/game?id=' + i.id)
}}
>
<div className='flex items-center'>
<FontAwesomeIcon icon={faGamepad} className='mr-1' />
<span className='truncate max-w-full'>{value}</span>
</div>
</div>
))}
</React.Fragment>
))}
<Link
draggable={false}
href='/settings'
className={`link ${pathname === '/settings' ? 'active' : ''}`}
>
<FontAwesomeIcon icon={faCog} className='mr-1' /> Settings
</Link>
<button
onClick={() => openUrl('https://games.lncvrt.xyz/discord')}
className='link mr-auto'
>
<FontAwesomeIcon icon={faDiscord} className='mr-1' /> Community
</button>
</nav>
<div
className='sidebar-downloads'
hidden={downloadProgress.length == 0}
onClick={() => {
setPopupMode(1)
setShowPopup(true)
setFadeOut(false)
}}
>
<p>
<FontAwesomeIcon icon={faDownload} /> Downloads
</p>
</div>
</aside>
)
}
+240
View File
@@ -0,0 +1,240 @@
import { useGlobal } from '@/app/GlobalProvider'
import prettyBytes from 'pretty-bytes'
import ProgressBar from '../ProgressBar'
import { formatEtaSmart } from '@/lib/Util'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import {
faCancel,
faPause,
faPlay,
faTrash
} from '@fortawesome/free-solid-svg-icons'
import { invoke } from '@tauri-apps/api/core'
import { writeVersionsConfig } from '@/lib/BazookaManager'
import { notifyUser } from '@/lib/Notifications'
import { getCurrentWindow, UserAttentionType } from '@tauri-apps/api/window'
export default function DownloadsPopup () {
const {
downloadProgress,
getVersionInfo,
setDownloadProgress,
downloadQueue,
setDownloadQueue,
normalConfig,
setDownloadedVersionsConfig
} = useGlobal()
return (
<>
<p className='text-xl text-center'>Downloads</p>
<div className='popup-content'>
{downloadProgress.map((v, i) => {
const queuePosition = downloadQueue.indexOf(v.version)
return (
<div
key={i}
className='popup-entry flex flex-col justify-between relative'
>
<div className='absolute right-2 top-2 flex flex-row gap-2'>
<div
className='cursor-pointer bg-(--col5) hover:bg-(--col7) border border-(--col7) hover:border-(--col9) transition-colors w-8 h-8 flex items-center justify-center rounded-full'
hidden={!v.downloading && !v.paused}
onClick={async () => {
if (v.downloading) {
await invoke('cancel_download', { name: v.version })
} else {
setDownloadProgress(prev => {
const i = prev.findIndex(d => d.version === v.version)
if (i === -1) return prev
const copy = [...prev]
copy[i] = {
...copy[i],
paused: false,
downloading: true
}
return copy
})
const res = await invoke<string>('download', {
url: v.url,
name: v.version,
executable: v.executable,
hash: v.hash
})
if (res === '1') {
setDownloadProgress(prev =>
prev.filter(d => d.version !== v.version)
)
setDownloadedVersionsConfig(prev => {
if (!prev) return prev
const updated = {
...prev,
list: {
...prev.list,
[v.version]: Date.now()
}
}
writeVersionsConfig(updated)
return updated
})
} else if (res == '0') {
setDownloadProgress(prev => {
const i = prev.findIndex(d => d.version === v.version)
if (i === -1) return prev
if (prev[i].canceled) {
return prev.filter((_, idx) => idx !== i)
}
const copy = [...prev]
copy[i] = {
...copy[i],
downloading: false,
paused: true,
failed: false
}
return copy
})
} else if (res == '-1') {
setDownloadProgress(prev =>
prev.map(d =>
d.version === v.version
? {
...d,
queued: false,
failed: true,
progress: 0
}
: d
)
)
if (normalConfig?.settings.allowNotifications)
await notifyUser(
'Download Failed',
`The download for version ${
getVersionInfo(v.version)?.displayName
} has failed.`
)
await getCurrentWindow().requestUserAttention(
UserAttentionType.Critical
)
}
}
}}
>
<FontAwesomeIcon
icon={v.downloading ? faPause : faPlay}
className='w-6 h-6'
/>
</div>
<div
className='cursor-pointer bg-(--col5) hover:bg-(--col7) border border-(--col7) hover:border-(--col9) transition-colors w-8 h-8 flex items-center justify-center rounded-full'
hidden={!v.downloading && !v.paused}
onClick={async () => {
if (!v.paused) {
await invoke('cancel_download', { name: v.version })
setDownloadProgress(prev => {
const i = prev.findIndex(d => d.version === v.version)
if (i === -1) return prev
const copy = [...prev]
copy[i] = {
...copy[i],
canceled: true
}
return copy
})
} else {
setDownloadProgress(prev => {
const i = prev.findIndex(d => d.version === v.version)
if (i === -1) return prev
return prev.filter((_, idx) => idx !== i)
})
}
}}
>
<FontAwesomeIcon icon={faCancel} className='w-6 h-6' />
</div>
<div
className='cursor-pointer bg-(--col5) hover:bg-(--col7) border border-(--col7) hover:border-(--col9) transition-colors w-8 h-8 flex items-center justify-center rounded-full'
hidden={!v.failed && !v.queued}
onClick={() => {
setDownloadQueue(prev =>
prev.filter(id => id !== v.version)
)
setDownloadProgress(prev =>
prev.filter(d => d.version !== v.version)
)
}}
>
<FontAwesomeIcon icon={faTrash} className='w-6 h-6' />
</div>
</div>
<p className='text-2xl text-center'>
{v.type == 1
? 'Mod Manager for '
: v.type == 2
? 'Mod for '
: null}{' '}
{getVersionInfo(v.version)?.displayName}
</p>
<div className='mt-6.25 flex items-center justify-between'>
{v.failed || v.queued ? (
<div className='flex items-center justify-between w-full'>
<span
className={`${
v.failed ? 'text-red-500' : 'text-yellow-300'
} inline-block text-center flex-1`}
>
{v.failed
? 'Download failed'
: queuePosition === 0
? 'Starting soon...'
: `Queued (Position ${queuePosition + 1})`}
</span>
</div>
) : v.hash_checking ? (
<span className='text-blue-300 inline-block w-full text-center'>
Verifying file integerty...
</span>
) : v.unzipping ? (
<div className='flex flex-col gap-1 w-full'>
<span className='text-center'>
Unzipped {v.unzipped} / {v.unzipTotal} files
</span>
<ProgressBar
progress={(v.unzipped / v.unzipTotal) * 100}
className='w-full'
/>
</div>
) : (
<div className='flex flex-col gap-1 w-full'>
<span className='text-center'>
Downloaded{' '}
{prettyBytes(v.progressBytes, {
minimumFractionDigits: 1,
maximumFractionDigits: 1
})}{' '}
of{' '}
{prettyBytes(v.size, {
minimumFractionDigits: 1,
maximumFractionDigits: 1
})}{' '}
(ETA: {formatEtaSmart(v.etaSecs)} &bull; Speed:{' '}
{prettyBytes(v.speed, {
minimumFractionDigits: 1,
maximumFractionDigits: 1
})}
/s)
</span>
<ProgressBar progress={v.progress} className='w-full' />
</div>
)}
</div>
</div>
)
})}
</div>
</>
)
}
+92
View File
@@ -0,0 +1,92 @@
import { useGlobal } from '@/app/GlobalProvider'
import {
faCheck,
faCode,
faDownload,
faShieldHalved,
faWarning
} from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
export default function GamesDownloadPopup () {
const { serverVersionList, getVersionsAmountData, setSelectedGame } =
useGlobal()
return (
<>
<p className='text-xl text-center'>Select a game to download</p>
<div className='popup-content'>
{serverVersionList?.games
.filter(v => {
const data = getVersionsAmountData(v.id)
if (!data) return false
if (data.total > 0) return true
})
.map((v, i) => (
<div key={i} className='popup-entry'>
<p className='text-2xl'>{v.name}</p>
<div className='flex gap-2'>
<div
className='entry-info-item btntheme3'
title='The amount of versions installed of this game in installed/installable format.'
>
<p>
{(() => {
const data = getVersionsAmountData(v.id)
if (!data) return 'N/A'
return `${data.installed}/${data.total}`
})()}{' '}
versions installed
</p>
</div>
<div
className='entry-info-item btntheme3'
hidden={!v.official}
title='This game is official.'
>
<FontAwesomeIcon icon={faCheck} color='#19c84b' />
<p>Official</p>
</div>
<div
className='entry-info-item btntheme3'
hidden={v.official}
title={
v.verified
? 'This game is verified to be safe'
: 'This game is NOT verified to be save. Proceed with caution.'
}
>
<FontAwesomeIcon
icon={v.verified ? faShieldHalved : faWarning}
color={v.verified ? '#19c84b' : '#ffc800'}
/>
<p>{v.verified ? 'Verified' : 'Unverified'}</p>
</div>
</div>
<div
className='entry-info-item btntheme3 mt-2'
hidden={v.developer == null}
title={`The developer of ${v.name} is ${v.developer}.`}
>
<FontAwesomeIcon icon={faCode} color='lightgray' />
<p>Developer: {v.developer}</p>
</div>
<button
className='button btntheme3 right-2 bottom-2'
onClick={() => setSelectedGame(v.id)}
title={`Click to download specific versions of the game. You have ${(() => {
const data = getVersionsAmountData(v.id)
if (!data) return 'N/A'
return `${data.installed} of ${data.total}`
})()} versions downloaded.`}
>
<>
<FontAwesomeIcon icon={faDownload} /> Download
</>
</button>
</div>
))}
</div>
</>
)
}
+214
View File
@@ -0,0 +1,214 @@
'use client'
import {
faArrowUpRightFromSquare,
faCheck,
faCode,
faHardDrive,
faShieldHalved,
faWarning
} from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { useGlobal } from '@/app/GlobalProvider'
import { invoke } from '@tauri-apps/api/core'
import { useEffect, useState } from 'react'
import prettyBytes from 'pretty-bytes'
import { message } from '@tauri-apps/plugin-dialog'
import { BaseDirectory, exists, remove } from '@tauri-apps/plugin-fs'
import { writeVersionsConfig } from '@/lib/BazookaManager'
import { openFolder } from '@/lib/Util'
export default function ManageVersionPopup () {
const {
getGameInfo,
getVersionInfo,
managingVersion,
downloadedVersionsConfig,
viewingInfoFromDownloads,
setManagingVersion,
closePopup,
setDownloadedVersionsConfig,
setPopupMode,
setSelectedVersionList,
downloadVersions
} = useGlobal()
const [versionSize, setVersionSize] = useState<number>(0)
useEffect(() => {
if (viewingInfoFromDownloads) return
invoke<string>('folder_size', {
version: managingVersion
}).then(size => {
setVersionSize(Number(size))
})
}, [managingVersion, setVersionSize, viewingInfoFromDownloads])
if (!managingVersion || !downloadedVersionsConfig) return <></>
const versionInfo = getVersionInfo(managingVersion)
const gameInfo = getGameInfo(versionInfo?.game)
return (
<>
<p className='text-xl text-center'>Viewing {versionInfo?.displayName}</p>
<div className='popup-content flex flex-col items-center justify-center gap-2 h-full'>
<div
className='entry-info-item btntheme2'
hidden={viewingInfoFromDownloads}
>
<p>
Installed{' '}
{new Intl.DateTimeFormat(undefined).format(
downloadedVersionsConfig.list[managingVersion] ?? 0
)}
</p>
</div>
<div
className='entry-info-item btntheme2'
hidden={!versionInfo || versionInfo.releaseDate == 0}
>
<p>
Released{' '}
{new Intl.DateTimeFormat(undefined).format(
versionInfo?.releaseDate ? versionInfo.releaseDate * 1000 : 0
)}
</p>
</div>
<div className='entry-info-item btntheme2' hidden={!gameInfo?.official}>
<FontAwesomeIcon icon={faCheck} color='#19c84b' />
<p>Official</p>
</div>
<div className='entry-info-item btntheme2' hidden={gameInfo?.official}>
<FontAwesomeIcon
icon={gameInfo?.verified ? faShieldHalved : faWarning}
color={gameInfo?.verified ? '#19c84b' : '#ffc800'}
/>
<p>{gameInfo?.verified ? 'Verified' : 'Unverified'}</p>
</div>
<div
className='entry-info-item btntheme2'
hidden={gameInfo?.developer == null}
>
<FontAwesomeIcon icon={faCode} color='lightgray' />
<p>Developer: {gameInfo?.developer}</p>
</div>
<div
className='entry-info-item btntheme2'
hidden={viewingInfoFromDownloads}
>
<FontAwesomeIcon icon={faHardDrive} color='lightgray' />
<p>
Size on disk:{' '}
{versionSize && versionSize > 0
? prettyBytes(versionSize, {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})
: 'N/A'}
</p>
</div>
<div
className='entry-info-item btntheme2'
onClick={async () => {
if (!versionInfo) return
await message(atob(versionInfo.changelog), {
title: 'Changelog for ' + versionInfo.displayName,
kind: 'info'
})
}}
hidden={!versionInfo?.changelog}
>
<p>View Changelog</p>
<FontAwesomeIcon icon={faArrowUpRightFromSquare} color='lightgray' />
</div>
<div
className='entry-info-item btntheme2'
onClick={async () => openFolder(managingVersion)}
title="Click to browse the game's files."
hidden={viewingInfoFromDownloads}
>
Open Folder
<FontAwesomeIcon icon={faArrowUpRightFromSquare} color='lightgray' />
</div>
<div
className='entry-info-item btntheme2'
onClick={async () => {
closePopup()
setDownloadedVersionsConfig(prev => {
if (!prev) return prev
const updatedList = Object.fromEntries(
Object.entries(prev.list).filter(([k]) => k !== managingVersion)
)
const updatedConfig = {
...prev,
list: updatedList
}
writeVersionsConfig(updatedConfig)
return updatedConfig
})
if (
await exists('game/' + managingVersion, {
baseDir: BaseDirectory.AppLocalData
})
)
await remove('game/' + managingVersion, {
baseDir: BaseDirectory.AppLocalData,
recursive: true
})
}}
title='Click to uninstall this game. This will NOT remove any progress or any save files.'
hidden={viewingInfoFromDownloads}
>
Uninstall
</div>
<div
className='entry-info-item btntheme2'
onClick={async () => {
//change popup to downloads
setManagingVersion(null)
setPopupMode(1)
//uninstall
setDownloadedVersionsConfig(prev => {
if (!prev) return prev
const updatedList = Object.fromEntries(
Object.entries(prev.list).filter(([k]) => k !== managingVersion)
)
const updatedConfig = {
...prev,
list: updatedList
}
writeVersionsConfig(updatedConfig)
return updatedConfig
})
if (
await exists('game/' + managingVersion, {
baseDir: BaseDirectory.AppLocalData
})
)
await remove('game/' + managingVersion, {
baseDir: BaseDirectory.AppLocalData,
recursive: true
})
//reinstall
setSelectedVersionList([managingVersion])
downloadVersions([
{
id: managingVersion,
type: 0
}
])
}}
title="Click to reinstall this game. This will NOT remove any progress or any save files. This WILL uninstall any modifications to the game's executable files."
hidden={viewingInfoFromDownloads}
>
Reinstall
</div>
</div>
</>
)
}
+380
View File
@@ -0,0 +1,380 @@
'use client'
import { useGlobal } from '@/app/GlobalProvider'
import { writeVersionsConfig } from '@/lib/BazookaManager'
import { verifySignature } from '@/lib/Util'
import { Mod } from '@/types/Mod'
import {
faArrowUpRightFromSquare,
faClock,
faCodeBranch,
faDownload,
faGlobe,
faPeopleGroup,
faRefresh,
faTrash
} from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { message } from '@tauri-apps/plugin-dialog'
import { BaseDirectory, exists, remove } from '@tauri-apps/plugin-fs'
import { arch, platform } from '@tauri-apps/plugin-os'
import { useEffect, useState } from 'react'
export default function ModDownloadsPopup () {
const {
getVersionInfo,
managingVersion,
downloadedVersionsConfig,
showModInfo,
setShowModInfo,
downloadVersions,
downloadProgress,
setDownloadedVersionsConfig
} = useGlobal()
const [mods, setMods] = useState<Mod[] | 0 | 1>(0)
const [tab, setTab] = useState<number>(0)
useEffect(() => {
if (!managingVersion) return
;(async () => {
const response = await fetch(
`https://games.lncvrt.xyz/api/launcher/mods?platform=${platform()}&arch=${arch()}&version=${managingVersion}`
)
const signature = response.headers.get('x-signature') ?? ''
const data = await response.json()
if (await verifySignature(JSON.stringify(data), signature)) {
setMods(data as Mod[])
} else {
setMods(1)
}
})()
}, [managingVersion])
if (!managingVersion) return <></>
const versionInfo = getVersionInfo(managingVersion)
return (
<>
<button
className='popup-top-button btntheme1 right-2'
onClick={async () => {
setMods(0)
const response = await fetch(
`https://games.lncvrt.xyz/api/launcher/mods?platform=${platform()}&arch=${arch()}&version=${managingVersion}`
)
const signature = response.headers.get('x-signature') ?? ''
const data = await response.json()
if (await verifySignature(JSON.stringify(data), signature)) {
setMods(data as Mod[])
} else {
setMods(1)
}
}}
hidden={typeof mods == 'number' || showModInfo != null}
>
<FontAwesomeIcon icon={faRefresh} />
</button>
<p className='text-xl text-center'>
{!showModInfo
? versionInfo?.displayName + ' Mod Manager'
: showModInfo.name +
' by ' +
showModInfo.creators[0] +
(showModInfo.creators.length > 1
? ' + ' + (showModInfo.creators.length - 1) + ' more'
: '')}
</p>
{typeof mods != 'number' && !showModInfo ? (
<div className='flex flex-row justify-center items-center -mb-2 mt-0.5 gap-1.5'>
<button
className={`button ${tab == 0 ? 'btntheme3' : 'btntheme2'}`}
onClick={() => setTab(0)}
>
<FontAwesomeIcon icon={faDownload} />
Installed
</button>
<button
className={`button ${tab == 1 ? 'btntheme3' : 'btntheme2'}`}
onClick={() => setTab(1)}
>
<FontAwesomeIcon icon={faGlobe} />
Download
</button>
</div>
) : null}
<div className='popup-content h-full'>
{typeof mods == 'number' ||
(!showModInfo &&
mods.filter(v =>
tab == 0
? Object.keys(downloadedVersionsConfig?.mods ?? []).includes(
String(versionInfo?.game + '-' + v.id)
)
: !Object.keys(downloadedVersionsConfig?.mods ?? []).includes(
String(versionInfo?.game + '-' + v.id)
)
).length == 0) ? (
<p className='text-2xl flex justify-center items-center h-full'>
{typeof mods == 'number'
? mods == 0
? 'Loading'
: 'Failed to load mods'
: tab == 0
? 'No mods installed'
: 'No mods available to download'}
</p>
) : !showModInfo ? (
<div className='flex flex-col items-center justify-center gap-2 p-2'>
{mods
.filter(v =>
tab == 0
? Object.keys(downloadedVersionsConfig?.mods ?? []).includes(
String(versionInfo?.game + '-' + v.id)
)
: !Object.keys(downloadedVersionsConfig?.mods ?? []).includes(
String(versionInfo?.game + '-' + v.id)
)
)
.map(v => {
const localVersion =
tab == 0
? Object.keys(
downloadedVersionsConfig?.mods?.[
versionInfo?.game + '-' + v.id
] ?? {}
)[0]
: null
return (
<div
key={v.id}
className='bg-(--col3) border border-(--col5) rounded-lg w-full h-16 flex flex-row'
>
<div className='flex flex-col justify-center h-full px-3 w-fit'>
<p>
<span className='text-lg'>{v.name}</span>{' '}
<span
className={
tab != 0 || v.latestVersion == localVersion
? 'text-green-300'
: 'text-red-300'
}
>
v{tab == 0 ? localVersion : v.latestVersion}
</span>
<span
className='text-green-300'
hidden={tab != 0 || v.latestVersion == localVersion}
>
{' '}
(available: v{v.latestVersion})
</span>
</p>
<p className='text-yellow-200'>
Made by {v.creators[0]}
{v.creators.length > 1
? ' + ' + (v.creators.length - 1) + ' more'
: null}
</p>
</div>
<div className='flex flex-row items-center h-full gap-2 px-3 ml-auto'>
<p className='text-green-400' hidden={tab == 0}>
<FontAwesomeIcon icon={faDownload} />{' '}
{v.downloads.toLocaleString()}
</p>
<button
className='button btntheme3'
onClick={async () => {
const path =
'game/' +
versionInfo?.id +
'/BepInEx/plugins/' +
v.id
if (
await exists(path, {
baseDir: BaseDirectory.AppLocalData
})
)
await remove(path, {
baseDir: BaseDirectory.AppLocalData,
recursive: true
})
setDownloadedVersionsConfig(prev => {
if (!prev) return prev
const updatedMods = Object.fromEntries(
Object.entries(prev.mods).filter(
([k]) => k !== versionInfo?.game + '-' + v.id
)
)
const updatedConfig = {
...prev,
mods: updatedMods
}
writeVersionsConfig(updatedConfig)
return updatedConfig
})
downloadVersions([
{
id: managingVersion,
type: 2,
modDownload: v.latestDownload,
gameId: versionInfo?.game,
modId: v.id,
modVersion: v.latestVersion
}
])
}}
hidden={tab != 0 || v.latestVersion == localVersion}
disabled={downloadProgress.length != 0}
>
Update
</button>
<button
className='button btntheme3'
onClick={() => setShowModInfo(v)}
>
{tab == 0 ? 'View' : 'Get'}
</button>
</div>
</div>
)
})}
</div>
) : (
<div className='flex flex-col h-full w-full items-center'>
<div className='flex flex-col p-2 h-fit w-fit bg-(--col3) border border-(--col5) rounded-lg m-2 items-center'>
<p>
<FontAwesomeIcon icon={faDownload} className='text-green-400' />{' '}
Downloads {showModInfo.downloads.toLocaleString()}
</p>
<p>
<FontAwesomeIcon icon={faClock} /> Released{' '}
{new Intl.DateTimeFormat(undefined).format(
showModInfo.released * 1000
)}
</p>
<p>
<FontAwesomeIcon icon={faClock} /> Updated{' '}
{new Intl.DateTimeFormat(undefined).format(
showModInfo.updated * 1000
)}
</p>
<p>
<FontAwesomeIcon icon={faCodeBranch} /> Version{' '}
{showModInfo.latestVersion}
</p>
</div>
<div className='flex flex-col p-2 bg-(--col3) border border-(--col5) rounded-lg h-full w-[calc(100%-16px)]'>
{showModInfo.description
? atob(showModInfo.description)
: '(No description added)'}
</div>
<div className='flex flex-row h-fit w-full gap-2 justify-center my-2'>
{Object.keys(downloadedVersionsConfig?.mods ?? []).includes(
String(versionInfo?.game + '-' + showModInfo.id)
) ? (
<button
className='button btntheme2 w-fit'
disabled={downloadProgress.length != 0}
onClick={async () => {
const path =
'game/' +
versionInfo?.id +
'/BepInEx/plugins/' +
showModInfo.id
if (
await exists(path, {
baseDir: BaseDirectory.AppLocalData
})
)
await remove(path, {
baseDir: BaseDirectory.AppLocalData,
recursive: true
})
setDownloadedVersionsConfig(prev => {
if (!prev) return prev
const updatedMods = Object.fromEntries(
Object.entries(prev.mods).filter(
([k]) =>
k !== versionInfo?.game + '-' + showModInfo.id
)
)
const updatedConfig = {
...prev,
mods: updatedMods
}
writeVersionsConfig(updatedConfig)
return updatedConfig
})
}}
>
<FontAwesomeIcon icon={faTrash} className='text-red-400' />{' '}
Uninstall
</button>
) : (
<button
className='button btntheme2 w-fit'
onClick={() => {
downloadVersions([
{
id: managingVersion,
type: 2,
modDownload: showModInfo.latestDownload,
gameId: versionInfo?.game,
modId: showModInfo.id,
modVersion: showModInfo.latestVersion
}
])
}}
disabled={downloadProgress.length != 0}
>
<FontAwesomeIcon
icon={faDownload}
className='text-green-400'
/>{' '}
Install
</button>
)}
<button
className='button btntheme2 w-fit'
onClick={async () => {
await message(
!showModInfo.changelog
? '(No changelog provided)'
: atob(showModInfo?.changelog ?? ''),
{
title: 'Changelog for ' + showModInfo.name,
kind: 'info'
}
)
}}
>
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
color='lightgray'
/>{' '}
View changelog
</button>
<button
className='button btntheme2 w-fit'
hidden={showModInfo.creators.length < 2}
onClick={async () => {
if (!versionInfo) return
await message(showModInfo.creators.join(', '), {
title: 'Creators for ' + showModInfo.name,
kind: 'info'
})
}}
>
<FontAwesomeIcon icon={faPeopleGroup} color='lightgray' /> View
all creators
</button>
</div>
</div>
)}
</div>
</>
)
}
+126
View File
@@ -0,0 +1,126 @@
import { useGlobal } from '@/app/GlobalProvider'
import { faAdd, faInfo, faRemove } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
export default function VersionsDownloadPopup () {
const {
selectedVersionList,
setSelectedVersionList,
setManagingVersion,
setPopupMode,
getSpecialVersionsList,
selectedGame,
setViewingInfoFromDownloads,
downloadedVersionsConfig,
downloadVersions,
getGameInfo
} = useGlobal()
if (!selectedGame) return <></>
return (
<>
<p className='text-xl text-center'>Select versions to download</p>
<div className='popup-content'>
{getSpecialVersionsList(selectedGame).map((v, i) => (
<div key={i} className='popup-entry'>
<div className='flex items-center'>
<p
className={`text-2xl truncate ${
selectedVersionList.includes(v.id)
? 'max-w-84.5'
: 'max-w-91.5'
}`}
>
{v.displayName}
</p>
</div>
<button
className='button btntheme3 right-20.75 bottom-1.75'
onClick={() => {
setSelectedVersionList(prev =>
prev.includes(v.id)
? prev.filter(i => i !== v.id)
: [...prev, v.id]
)
}}
title={
selectedVersionList.includes(v.id)
? 'This version will be downloaded. Click to remove from the list of versions that will be downloaded.'
: 'This version will NOT be downloaded. Click to add from the list of versions that will be downloaded.'
}
>
{selectedVersionList.includes(v.id) ? (
<>
<FontAwesomeIcon icon={faRemove} /> Remove
</>
) : (
<>
<FontAwesomeIcon icon={faAdd} /> Add
</>
)}
</button>
<button
className='button btntheme3 right-1.5 bottom-1.75'
onClick={() => {
setManagingVersion(v.id)
setViewingInfoFromDownloads(true)
setPopupMode(2)
}}
title='Click to view version info'
>
<FontAwesomeIcon icon={faInfo} /> Info
</button>
</div>
))}
</div>
<div className='flex justify-center'>
<button
className='button btntheme1 w-fit mt-2 -mb-4'
onClick={() => {
if (downloadedVersionsConfig) {
downloadVersions(
selectedVersionList.map(versionId => ({
id: versionId,
type: 0
}))
)
setPopupMode(1)
}
}}
disabled={selectedVersionList.length == 0}
title={
selectedVersionList.length == 0
? 'Select at least one version to download'
: `Download ${selectedVersionList.length} version${
selectedVersionList.length == 1 ? '' : 's'
} of ${getGameInfo(selectedGame)?.name}`
}
>
Download {selectedVersionList.length} version
{selectedVersionList.length == 1 ? '' : 's'}
</button>
<button
className='button btntheme1 w-fit mt-2 ml-2 -mb-4'
onClick={() => {
const allIds = getSpecialVersionsList(selectedGame).map(v => v.id)
setSelectedVersionList(prev =>
prev.length === allIds.length ? [] : allIds
)
}}
title={
selectedVersionList.length ===
getSpecialVersionsList(selectedGame).length
? 'Click to remove all selected versions for download.'
: 'Click to add all selected versions for download.'
}
>
{selectedVersionList.length ===
getSpecialVersionsList(selectedGame).length
? 'Deselect All'
: 'Select All'}
</button>
</div>
</>
)
}
@@ -1,5 +1,5 @@
import { app } from '@tauri-apps/api'
import { NormalConfig } from '../types/NormalConfig'
import { NormalConfig } from '@/types/NormalConfig'
import {
BaseDirectory,
create,
@@ -8,7 +8,7 @@ import {
readTextFile,
writeFile
} from '@tauri-apps/plugin-fs'
import { VersionsConfig } from '../types/VersionsConfig'
import { VersionsConfig } from '@/types/VersionsConfig'
export async function readNormalConfig (): Promise<NormalConfig> {
const version = await app.getVersion()
+7
View File
@@ -0,0 +1,7 @@
export const GetIconForUser = (user: number) => {
if (user == 1) return -1
else if (user == 2) return -2
else if (user == 4) return -3
else if (user == 3) return -4
else return 1
}
@@ -1,4 +1,4 @@
import { NormalConfig } from '../types/NormalConfig'
import { NormalConfig } from '@/types/NormalConfig'
import { notifyUser } from './Notifications'
export async function copyToClipboard (
File renamed without changes.
+64
View File
@@ -0,0 +1,64 @@
import { message } from '@tauri-apps/plugin-dialog'
import { BaseDirectory, exists, stat } from '@tauri-apps/plugin-fs'
import { openPath } from '@tauri-apps/plugin-opener'
import { join, appLocalDataDir } from '@tauri-apps/api/path'
import { invoke } from '@tauri-apps/api/core'
export const openFolder = async (name: string) => {
const relativePath = await join('game', name)
const basePath = await appLocalDataDir()
const absolutePath = await join(basePath, relativePath)
const folderExists = await exists(relativePath, {
baseDir: BaseDirectory.AppLocalData
})
if (!folderExists) {
await message(`Game folder "${absolutePath}" not found.`, {
title: 'Folder not found',
kind: 'error'
})
return
}
const folderStat = await stat(relativePath, {
baseDir: BaseDirectory.AppLocalData
})
if (folderStat.isFile) {
await message(`Game folder "${absolutePath}" not found.`, {
title: 'Folder not found',
kind: 'error'
})
return
}
await openPath(absolutePath)
}
export const formatEtaSmart = (seconds: number) => {
if (seconds < 60) return `${Math.floor(seconds)}s`
if (seconds < 3600)
return `${Math.floor(seconds / 60)}m ${Math.floor(seconds % 60)}s`
if (seconds < 86400) {
const h = Math.floor(seconds / 3600)
const m = Math.floor((seconds % 3600) / 60)
return `${h}h ${m}m`
}
const d = Math.floor(seconds / 86400)
const h = Math.floor((seconds % 86400) / 3600)
return `${d}d ${h}h`
}
export const verifySignature = async (body: string, signature: string) => {
try {
const result = await invoke<boolean>('verify_signature', {
body: body ?? '',
signature: signature ?? '',
publicKey: process.env.NEXT_PUBLIC_PUBLIC_SIGNING_KEY ?? ''
})
return Boolean(result)
} catch {
return false
}
}
+1
View File
@@ -0,0 +1 @@
export type BirdColor = [number, number, number]
+27
View File
@@ -0,0 +1,27 @@
export class DownloadProgress {
constructor (
public version: string,
public progress: number,
public progressBytes: number,
public failed: boolean,
public downloading: boolean,
public paused: boolean,
public canceled: boolean,
public queued: boolean,
public hash_checking: boolean,
public speed: number,
public size: number,
public etaSecs: number,
public unzipping: boolean,
public unzipTotal: number,
public unzipped: number,
public url: string | null,
public executable: string | null,
public hash: string | null,
public type: 0 | 1 | 2,
public modDownload: number | null,
public modGame: number | null,
public modId: number | null,
public modVersion: string | null
) {}
}
File renamed without changes.
@@ -3,13 +3,12 @@ export interface GameVersion {
displayName: string
releaseDate: number
game: number
downloadUrl: string
executable: string
sha512sum: string
size: number
place: number
changelog: string
wine: number | undefined
category: number
lastRevision: number
download: number | undefined
executable: string
modSupportDownload: number | undefined
}
+12
View File
@@ -0,0 +1,12 @@
export interface Mod {
id: number
name: string
description: string | null
creators: string[]
downloads: number
released: number
updated: number
latestVersion: string
latestDownload: number
changelog: string | null
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -2,7 +2,6 @@ export class SettingsType {
constructor (
public allowNotifications: boolean = true,
public alwaysShowGamesInSidebar: boolean = true,
public useLegacyInteractButtons: boolean = false,
public useWineOnUnixWhenNeeded: boolean = false,
public wineOnUnixCommand: string = 'wine %path%',
public theme: number = 0
@@ -1,17 +1,20 @@
export type VersionsConfigData = {
version: string
list: Record<string, number>
mods: Record<string, Record<string, number>>
}
export class VersionsConfig {
constructor (
public version: string,
public list: Record<string, number> = {}
public list: Record<string, number> = {},
public mods: Record<string, Record<string, number>> = {}
) {}
static import (data: VersionsConfigData) {
const cfg = new VersionsConfig(data.version)
cfg.list = { ...data.list }
cfg.mods = { ...data.mods }
return cfg
}
}
+4 -2
View File
@@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "ES2017",
"target": "es2024",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
@@ -28,7 +28,9 @@
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts",
"**/*.mts"
"**/*.mts",
"next.config.ts",
"eslint.config.ts"
],
"exclude": ["node_modules"]
}