diff --git a/.gitignore b/.gitignore
index 2c8942c..5ada0f1 100755
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,10 @@
+src/modules/patcher/patches/controller-customization.js
+src/modules/patcher/patches/expose-stream-session.js
+src/modules/patcher/patches/local-co-op-enable.js
+src/modules/patcher/patches/poll-gamepad.js
+src/modules/patcher/patches/remote-play-keep-alive.js
+src/modules/patcher/patches/vibration-adjust.js
+
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
# Logs
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 58c648f..0ca1742 100755
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,5 +1,11 @@
{
"files.readonlyInclude": {
- "dist/**/*": true
+ "dist/**/*": true,
+ "src/modules/patcher/patches/controller-customization.js": true,
+ "src/modules/patcher/patches/expose-stream-session.js": true,
+ "src/modules/patcher/patches/local-co-op-enable.js": true,
+ "src/modules/patcher/patches/poll-gamepad.js": true,
+ "src/modules/patcher/patches/remote-play-keep-alive.js": true,
+ "src/modules/patcher/patches/vibration-adjust.js": true
}
}
diff --git a/build.ts b/build.ts
index 595643b..aa4dbff 100755
--- a/build.ts
+++ b/build.ts
@@ -1,5 +1,5 @@
#!/usr/bin/env bun
-import { readFile } from "node:fs/promises";
+import { readFile, readdir } from "node:fs/promises";
import { parseArgs } from "node:util";
import { sys } from "typescript";
// @ts-ignore
@@ -56,7 +56,23 @@ function minifyCodeImports(str: string): string {
return str;
}
-const postProcess = (str: string): string => {
+function minifyIfElse(str: string): string {
+ // Collapse if/else blocks without curly braces
+ return str.replaceAll(/((if \(.*?\)|else)\n\s+)/g, '$2 ');
+}
+
+function removeComments(str: string): string {
+ // Remove enum's inlining comments
+ str = str.replaceAll(/ \/\* [A-Z0-9_:]+ \*\//g, '');
+ str = str.replaceAll('/* @__PURE__ */ ', '');
+
+ // Remove comments from import
+ str = str.replaceAll(/\/\/ src.*\n/g, '');
+
+ return str;
+}
+
+function postProcess(str: string): string {
// Unescape unicode charaters
str = unescape((str.replace(/\\u/g, '%u')));
// Replace \x00 to normal character
@@ -65,12 +81,7 @@ const postProcess = (str: string): string => {
// Replace "globalThis." with "var";
str = str.replaceAll('globalThis.', 'var ');
- // Remove enum's inlining comments
- str = str.replaceAll(/ \/\* [A-Z0-9_:]+ \*\//g, '');
- str = str.replaceAll('/* @__PURE__ */ ', '');
-
- // Remove comments from import
- str = str.replaceAll(/\/\/ src.*\n/g, '');
+ str = removeComments(str);
// Add ADDITIONAL CODE block
str = str.replace('var DEFAULT_FLAGS', '\n/* ADDITIONAL CODE */\n\nvar DEFAULT_FLAGS');
@@ -114,8 +125,7 @@ const postProcess = (str: string): string => {
// Set indent to 1 space
if (MINIFY_SYNTAX) {
- // Collapse if/else blocks without curly braces
- str = str.replaceAll(/((if \(.*?\)|else)\n\s+)/g, '$2 ');
+ str = minifyIfElse(str);
str = str.replaceAll(/\n(\s+)/g, (match, p1) => {
const len = p1.length / 2;
@@ -134,7 +144,47 @@ const postProcess = (str: string): string => {
return str;
}
-const build = async (target: BuildTarget, version: string, variant: BuildVariant, config: any={}) => {
+async function buildPatches() {
+ const inputDir = './src/modules/patcher/patches/src';
+ const outputDir = './src/modules/patcher/patches';
+
+ const files = await readdir(inputDir);
+ const tsFiles = files.filter(file => file.endsWith('.ts'));
+
+ tsFiles.forEach(async file => {
+ // You can perform any operation with each TypeScript file
+ console.log(`Building patch: ${file}`);
+ const filePath = `${inputDir}/${file}`;
+
+ await Bun.build({
+ entrypoints: [filePath],
+ outdir: outputDir,
+ target: 'browser',
+ format: 'esm',
+ minify: {
+ syntax: true,
+ whitespace: true,
+ },
+ });
+
+ const outputFile = `${outputDir}/${file.replace('.ts', '.js')}`;
+
+ let code = await readFile(outputFile, 'utf-8');
+
+ // Replace "$this$" to "this"
+ code = code.replaceAll('$this$', 'this');
+
+ // Minify code
+ code = removeComments(code);
+ code = minifyIfElse(code);
+
+ // Save
+ await Bun.write(outputFile, code);
+ console.log(`Patch built successfully: ${file}`)
+ });
+}
+
+async function build(target: BuildTarget, version: string, variant: BuildVariant, config: any={}) {
console.log('-- Target:', target);
const startTime = performance.now();
@@ -153,6 +203,8 @@ const build = async (target: BuildTarget, version: string, variant: BuildVariant
const outDir = './dist';
+ await buildPatches();
+
let output = await Bun.build({
entrypoints: ['src/index.ts'],
outdir: outDir,
diff --git a/bun.lock b/bun.lock
new file mode 100755
index 0000000..40bef75
--- /dev/null
+++ b/bun.lock
@@ -0,0 +1,315 @@
+{
+ "lockfileVersion": 0,
+ "workspaces": {
+ "": {
+ "devDependencies": {
+ "@types/bun": "^1.1.14",
+ "@types/node": "^22.10.2",
+ "@types/stylus": "^0.48.43",
+ "eslint": "^9.17.0",
+ "eslint-plugin-compat": "^6.0.2",
+ "stylus": "^0.64.0",
+ },
+ "peerDependencies": {
+ "typescript": "^5.7.2",
+ },
+ },
+ },
+ "packages": {
+ "@adobe/css-tools": ["@adobe/css-tools@4.3.3", "", {}, "sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ=="],
+
+ "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.4.0", "", { "dependencies": { "eslint-visitor-keys": "^3.3.0" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA=="],
+
+ "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="],
+
+ "@eslint/config-array": ["@eslint/config-array@0.19.0", "", { "dependencies": { "@eslint/object-schema": "^2.1.4", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ=="],
+
+ "@eslint/core": ["@eslint/core@0.9.0", "", {}, "sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg=="],
+
+ "@eslint/eslintrc": ["@eslint/eslintrc@3.2.0", "", { "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.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w=="],
+
+ "@eslint/js": ["@eslint/js@9.17.0", "", {}, "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w=="],
+
+ "@eslint/object-schema": ["@eslint/object-schema@2.1.4", "", {}, "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ=="],
+
+ "@eslint/plugin-kit": ["@eslint/plugin-kit@0.2.3", "", { "dependencies": { "levn": "^0.4.1" } }, "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA=="],
+
+ "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
+
+ "@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="],
+
+ "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
+
+ "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.1", "", {}, "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA=="],
+
+ "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
+
+ "@mdn/browser-compat-data": ["@mdn/browser-compat-data@5.5.42", "", {}, "sha512-qhHVgb2dxaFNT00Z1upHaDCstUEjjrgtIkrk4tr+YnDSGbTIKncbdydIpSed+RCXz0f6nb4UDD4eKEWokNom6g=="],
+
+ "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
+
+ "@types/bun": ["@types/bun@1.1.14", "", { "dependencies": { "bun-types": "1.1.37" } }, "sha512-opVYiFGtO2af0dnWBdZWlioLBoxSdDO5qokaazLhq8XQtGZbY4pY3/JxY8Zdf/hEwGubbp7ErZXoN1+h2yesxA=="],
+
+ "@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="],
+
+ "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
+
+ "@types/node": ["@types/node@22.10.2", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ=="],
+
+ "@types/stylus": ["@types/stylus@0.48.43", "", { "dependencies": { "@types/node": "*" } }, "sha512-72dv/zdhuyXWVHUXG2VTPEQdOG+oen95/DNFx2aMFFaY6LoITI6PwEqf5x31JF49kp2w9hvUzkNfTGBIeg61LQ=="],
+
+ "@types/ws": ["@types/ws@8.5.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A=="],
+
+ "acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="],
+
+ "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=="],
+
+ "ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
+
+ "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
+
+ "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
+
+ "ast-metadata-inferer": ["ast-metadata-inferer@0.8.1", "", { "dependencies": { "@mdn/browser-compat-data": "^5.6.19" } }, "sha512-ht3Dm6Zr7SXv6t1Ra6gFo0+kLDglHGrEbYihTkcycrbHw7WCcuhBzPlJYHEsIpycaUwzsJHje+vUcxXUX4ztTA=="],
+
+ "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+
+ "brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
+
+ "browserslist": ["browserslist@4.24.3", "", { "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" } }, "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA=="],
+
+ "bun-types": ["bun-types@1.1.37", "", { "dependencies": { "@types/node": "~20.12.8", "@types/ws": "~8.5.10" } }, "sha512-C65lv6eBr3LPJWFZ2gswyrGZ82ljnH8flVE03xeXxKhi2ZGtFiO4isRKTKnitbSqtRAcaqYSR6djt1whI66AbA=="],
+
+ "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
+
+ "caniuse-lite": ["caniuse-lite@1.0.30001690", "", {}, "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w=="],
+
+ "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
+
+ "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
+
+ "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
+
+ "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
+
+ "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
+
+ "debug": ["debug@4.3.5", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg=="],
+
+ "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
+
+ "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
+
+ "electron-to-chromium": ["electron-to-chromium@1.5.75", "", {}, "sha512-Lf3++DumRE/QmweGjU+ZcKqQ+3bKkU/qjaKYhIJKEOhgIO9Xs6IiAQFkfFoj+RhgDk4LUeNsLo6plExHqSyu6Q=="],
+
+ "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
+
+ "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
+
+ "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
+
+ "eslint": ["eslint@9.17.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.0", "@eslint/core": "^0.9.0", "@eslint/eslintrc": "^3.2.0", "@eslint/js": "9.17.0", "@eslint/plugin-kit": "^0.2.3", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.1", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "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.2.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.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-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA=="],
+
+ "eslint-plugin-compat": ["eslint-plugin-compat@6.0.2", "", { "dependencies": { "@mdn/browser-compat-data": "^5.5.35", "ast-metadata-inferer": "^0.8.1", "browserslist": "^4.24.2", "caniuse-lite": "^1.0.30001687", "find-up": "^5.0.0", "globals": "^15.7.0", "lodash.memoize": "^4.1.2", "semver": "^7.6.2" }, "peerDependencies": { "eslint": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" } }, "sha512-1ME+YfJjmOz1blH0nPZpHgjMGK4kjgEeoYqGCqoBPQ/mGu/dJzdoP0f1C8H2jcWZjzhZjAMccbM/VdXhPORIfA=="],
+
+ "eslint-scope": ["eslint-scope@8.2.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A=="],
+
+ "eslint-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="],
+
+ "espree": ["espree@10.3.0", "", { "dependencies": { "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.0" } }, "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg=="],
+
+ "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="],
+
+ "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
+
+ "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
+
+ "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
+
+ "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
+
+ "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
+
+ "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
+
+ "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
+
+ "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
+
+ "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
+
+ "flatted": ["flatted@3.3.1", "", {}, "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw=="],
+
+ "foreground-child": ["foreground-child@3.3.0", "", { "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" } }, "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg=="],
+
+ "glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
+
+ "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
+
+ "globals": ["globals@15.8.0", "", {}, "sha512-VZAJ4cewHTExBWDHR6yptdIBlx9YSSZuwojj9Nt5mBRXQzrKakDsVKQ1J63sklLvzAJm0X5+RpO4i3Y2hcOnFw=="],
+
+ "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
+
+ "ignore": ["ignore@5.3.1", "", {}, "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw=="],
+
+ "import-fresh": ["import-fresh@3.3.0", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw=="],
+
+ "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
+
+ "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
+
+ "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
+
+ "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
+
+ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
+
+ "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
+
+ "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
+
+ "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
+
+ "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
+
+ "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
+
+ "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
+
+ "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
+
+ "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
+
+ "lodash.memoize": ["lodash.memoize@4.1.2", "", {}, "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag=="],
+
+ "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
+
+ "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
+
+ "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
+
+ "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
+
+ "ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="],
+
+ "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
+
+ "node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="],
+
+ "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
+
+ "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
+
+ "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
+
+ "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
+
+ "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
+
+ "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
+
+ "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
+
+ "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
+
+ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
+
+ "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
+
+ "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
+
+ "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
+
+ "sax": ["sax@1.4.1", "", {}, "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="],
+
+ "semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="],
+
+ "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
+
+ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
+
+ "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
+
+ "source-map": ["source-map@0.7.4", "", {}, "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA=="],
+
+ "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
+
+ "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
+
+ "strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
+
+ "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
+ "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
+
+ "stylus": ["stylus@0.64.0", "", { "dependencies": { "@adobe/css-tools": "~4.3.3", "debug": "^4.3.2", "glob": "^10.4.5", "sax": "~1.4.1", "source-map": "^0.7.3" }, "bin": { "stylus": "bin/stylus" } }, "sha512-ZIdT8eUv8tegmqy1tTIdJv9We2DumkNZFdCF5mz/Kpq3OcTaxSuCAYZge6HKK2CmNC02G1eJig2RV7XTw5hQrA=="],
+
+ "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
+
+ "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
+
+ "typescript": ["typescript@5.7.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg=="],
+
+ "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
+
+ "update-browserslist-db": ["update-browserslist-db@1.1.1", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.0" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A=="],
+
+ "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
+
+ "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
+
+ "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
+
+ "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
+
+ "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
+
+ "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
+
+ "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
+
+ "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
+
+ "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
+
+ "@types/stylus/@types/node": ["@types/node@22.5.5", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA=="],
+
+ "@types/ws/@types/node": ["@types/node@20.14.2", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q=="],
+
+ "ast-metadata-inferer/@mdn/browser-compat-data": ["@mdn/browser-compat-data@5.6.26", "", {}, "sha512-7NdgdOR7lkzrN70zGSULmrcvKyi/aJjpTJRCbuy8IZuHiLkPTvsr10jW0MJgWzK2l2wTmhdQvegTw6yNU5AVNQ=="],
+
+ "bun-types/@types/node": ["@types/node@20.12.14", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg=="],
+
+ "foreground-child/cross-spawn": ["cross-spawn@7.0.3", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w=="],
+
+ "glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
+
+ "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+
+ "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
+ "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
+ "wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
+
+ "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
+
+ "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
+ "@types/stylus/@types/node/undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="],
+
+ "@types/ws/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
+ "bun-types/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
+ "glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
+
+ "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
+ "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+
+ "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+ }
+}
diff --git a/bun.lockb b/bun.lockb
deleted file mode 100755
index 0caf3b7..0000000
Binary files a/bun.lockb and /dev/null differ
diff --git a/dist/better-xcloud.lite.user.js b/dist/better-xcloud.lite.user.js
index 17dc19e..3b5b9b2 100755
--- a/dist/better-xcloud.lite.user.js
+++ b/dist/better-xcloud.lite.user.js
@@ -1,7 +1,7 @@
// ==UserScript==
// @name Better xCloud (Lite)
// @namespace https://github.com/redphx
-// @version 6.0.7
+// @version 6.1.0-beta
// @description Improve Xbox Cloud Gaming (xCloud) experience
// @author redphx
// @license MIT
@@ -105,7 +105,7 @@ class UserAgent {
});
}
}
-var SCRIPT_VERSION = "6.0.7", SCRIPT_VARIANT = "lite", AppInterface = window.AppInterface;
+var SCRIPT_VERSION = "6.1.0-beta", SCRIPT_VARIANT = "lite", AppInterface = window.AppInterface;
UserAgent.init();
var userAgent = window.navigator.userAgent.toLowerCase(), isTv = userAgent.includes("smart-tv") || userAgent.includes("smarttv") || /\baft.*\b/.test(userAgent), isVr = window.navigator.userAgent.includes("VR") && window.navigator.userAgent.includes("OculusBrowser"), browserHasTouchSupport = "ontouchstart" in window || navigator.maxTouchPoints > 0, userAgentHasTouchSupport = !isTv && !isVr && browserHasTouchSupport, STATES = {
supportedRegion: !0,
@@ -269,6 +269,7 @@ var SUPPORTED_LANGUAGES = {
"zh-CN": "中文(简体)",
"zh-TW": "中文(繁體)"
}, Texts = {
+ "slightly-increase-input-latency": "Slightly increase input latency",
achievements: "Achievements",
activate: "Activate",
activated: "Activated",
@@ -328,12 +329,10 @@ var SUPPORTED_LANGUAGES = {
"continent-south-america": "South America",
contrast: "Contrast",
controller: "Controller",
+ "controller-customization": "Controller customization",
"controller-friendly-ui": "Controller-friendly UI",
- "controller-mapping": "Controller mapping",
- "controller-mapping-in-game": "In-game controller mapping",
"controller-shortcuts": "Controller shortcuts",
"controller-shortcuts-connect-note": "Connect a controller to use this feature",
- "controller-shortcuts-in-game": "In-game controller shortcuts",
"controller-shortcuts-xbox-note": "Button to open the Guide menu",
"controller-vibration": "Controller vibration",
copy: "Copy",
@@ -344,12 +343,12 @@ var SUPPORTED_LANGUAGES = {
default: "Default",
"default-preset-note": "You can't modify default presets. Create a new one to customize it.",
delete: "Delete",
+ "detect-controller-button": "Detect controller button",
device: "Device",
"device-unsupported-touch": "Your device doesn't have touch support",
"device-vibration": "Device vibration",
"device-vibration-not-using-gamepad": "On when not using gamepad",
disable: "Disable",
- "disable-byog-feature": 'Disable "Stream your own game" feature',
"disable-features": "Disable features",
"disable-home-context-menu": "Disable context menu in Home page",
"disable-post-stream-feedback-dialog": "Disable post-stream feedback dialog",
@@ -395,6 +394,10 @@ var SUPPORTED_LANGUAGES = {
"how-to-improve-app-performance": "How to improve app's performance",
ignore: "Ignore",
import: "Import",
+ "in-game-controller-customization": "In-game controller customization",
+ "in-game-controller-shortcuts": "In-game controller shortcuts",
+ "in-game-keyboard-shortcuts": "In-game keyboard shortcuts",
+ "in-game-shortcuts": "In-game shortcuts",
increase: "Increase",
"install-android": "Better xCloud app for Android",
invites: "Invites",
@@ -402,12 +405,13 @@ var SUPPORTED_LANGUAGES = {
jitter: "Jitter",
"keyboard-key": "Keyboard key",
"keyboard-shortcuts": "Keyboard shortcuts",
- "keyboard-shortcuts-in-game": "In-game keyboard shortcuts",
korea: "Korea",
language: "Language",
large: "Large",
layout: "Layout",
"left-stick": "Left stick",
+ "left-stick-deadzone": "Left stick deadzone",
+ "left-trigger-range": "Left trigger range",
"limit-fps": "Limit FPS",
"load-failed-message": "Failed to run Better xCloud",
"loading-screen": "Loading screen",
@@ -415,7 +419,6 @@ var SUPPORTED_LANGUAGES = {
"lowest-quality": "Lowest quality",
manage: "Manage",
"map-mouse-to": "Map mouse to",
- mapping: "Mapping",
"may-not-work-properly": "May not work properly!",
menu: "Menu",
microphone: "Microphone",
@@ -472,6 +475,7 @@ var SUPPORTED_LANGUAGES = {
"preferred-game-language": "Preferred game's language",
preset: "Preset",
press: "Press",
+ "press-any-button": "Press any button...",
"press-esc-to-cancel": "Press Esc to cancel",
"press-key-to-toggle-mkb": [
e => `Press ${e.key} to toggle this feature`,
@@ -527,6 +531,8 @@ var SUPPORTED_LANGUAGES = {
"renderer-configuration": "Renderer configuration",
"right-click-to-unbind": "Right-click on a key to unbind it",
"right-stick": "Right stick",
+ "right-stick-deadzone": "Right stick deadzone",
+ "right-trigger-range": "Right trigger range",
"rocket-always-hide": "Always hide",
"rocket-always-show": "Always show",
"rocket-animation": "Rocket animation",
@@ -536,7 +542,6 @@ var SUPPORTED_LANGUAGES = {
screen: "Screen",
"screenshot-apply-filters": "Apply video filters to screenshots",
"section-all-games": "All games",
- "section-byog": "Stream your own game",
"section-most-popular": "Most popular",
"section-native-mkb": "Play with mouse & keyboard",
"section-news": "News",
@@ -626,7 +631,6 @@ var SUPPORTED_LANGUAGES = {
e => `觸控遊玩佈局由 ${e.name} 提供`
],
"touch-controller": "Touch controller",
- "transparent-background": "Transparent background",
"true-achievements": "TrueAchievements",
ui: "UI",
"unexpected-behavior": "May cause unexpected behavior",
@@ -746,10 +750,11 @@ var ButtonStyleClass = {
64: "bx-focusable",
128: "bx-full-width",
256: "bx-full-height",
- 512: "bx-tall",
- 1024: "bx-circular",
- 2048: "bx-normal-case",
- 4096: "bx-normal-link"
+ 512: "bx-auto-height",
+ 1024: "bx-tall",
+ 2048: "bx-circular",
+ 4096: "bx-normal-case",
+ 8192: "bx-normal-link"
};
function createElement(elmName, props = {}, ..._) {
let $elm, hasNs = "xmlns" in props;
@@ -831,6 +836,23 @@ function clearDataSet($elm) {
delete $elm.dataset[key];
});
}
+function calculateSelectBoxes($root) {
+ let selects = Array.from($root.querySelectorAll("div.bx-select:not([data-calculated]) select"));
+ for (let $select of selects) {
+ let $parent = $select.parentElement;
+ if ($parent.classList.contains("bx-full-width")) {
+ $parent.dataset.calculated = "true";
+ continue;
+ }
+ let rect = $select.getBoundingClientRect(), $label, width = Math.ceil(rect.width);
+ if (!width) continue;
+ if ($label = $parent.querySelector($select.multiple ? ".bx-select-value" : "div"), $parent.isControllerFriendly) {
+ if ($select.multiple) width += 20;
+ if ($select.querySelector("optgroup")) width -= 15;
+ } else width += 10;
+ $select.style.left = "0", $label.style.minWidth = width + "px", $parent.dataset.calculated = "true";
+ }
+}
var FILE_SIZE_UNITS = ["B", "KB", "MB", "GB", "TB"];
function humanFileSize(size) {
let i = size == 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024));
@@ -1001,9 +1023,10 @@ class LocalDb {
static instance;
static getInstance = () => LocalDb.instance ?? (LocalDb.instance = new LocalDb);
static DB_NAME = "BetterXcloud";
- static DB_VERSION = 3;
+ static DB_VERSION = 4;
static TABLE_VIRTUAL_CONTROLLERS = "virtual_controllers";
static TABLE_CONTROLLER_SHORTCUTS = "controller_shortcuts";
+ static TABLE_CONTROLLER_CUSTOMIZATIONS = "controller_customizations";
static TABLE_CONTROLLER_SETTINGS = "controller_settings";
static TABLE_KEYBOARD_SHORTCUTS = "keyboard_shortcuts";
db;
@@ -1028,6 +1051,10 @@ class LocalDb {
if (!db.objectStoreNames.contains(LocalDb.TABLE_CONTROLLER_SETTINGS)) db.createObjectStore(LocalDb.TABLE_CONTROLLER_SETTINGS, {
keyPath: "id"
});
+ if (!db.objectStoreNames.contains(LocalDb.TABLE_CONTROLLER_CUSTOMIZATIONS)) db.createObjectStore(LocalDb.TABLE_CONTROLLER_CUSTOMIZATIONS, {
+ keyPath: "id",
+ autoIncrement: !0
+ });
if (!db.objectStoreNames.contains(LocalDb.TABLE_KEYBOARD_SHORTCUTS)) db.createObjectStore(LocalDb.TABLE_KEYBOARD_SHORTCUTS, {
keyPath: "id",
autoIncrement: !0
@@ -2077,27 +2104,27 @@ class StreamStatsCollector {
current: -1,
grades: [40, 75, 100],
toString() {
- return this.current === -1 ? "???" : this.current.toString().padStart(3, " ");
+ return this.current === -1 ? "???" : this.current.toString().padStart(3);
}
},
jit: {
current: 0,
grades: [30, 40, 60],
toString() {
- return `${this.current.toFixed(1)}ms`.padStart(6, " ");
+ return `${this.current.toFixed(1)}ms`.padStart(6);
}
},
fps: {
current: 0,
toString() {
let maxFps = getPref("video.maxFps");
- return maxFps < 60 ? `${maxFps}/${this.current}`.padStart(5, " ") : this.current.toString();
+ return maxFps < 60 ? `${maxFps}/${this.current}`.padStart(5) : this.current.toString();
}
},
btr: {
current: 0,
toString() {
- return `${this.current.toFixed(1)} Mbps`.padStart(9, " ");
+ return `${this.current.toFixed(1)} Mbps`.padStart(9);
}
},
fl: {
@@ -2121,13 +2148,13 @@ class StreamStatsCollector {
total: 0,
grades: [6, 9, 12],
toString() {
- return isNaN(this.current) ? "??ms" : `${this.current.toFixed(1)}ms`.padStart(6, " ");
+ return isNaN(this.current) ? "??ms" : `${this.current.toFixed(1)}ms`.padStart(6);
}
},
dl: {
total: 0,
toString() {
- return humanFileSize(this.total).padStart(8, " ");
+ return humanFileSize(this.total).padStart(8);
}
},
ul: {
@@ -2594,12 +2621,39 @@ class ControllerShortcutsTable extends BasePresetsTable {
BxLogger.info(this.LOG_TAG, "constructor()");
}
}
+class ControllerCustomizationsTable extends BasePresetsTable {
+ static instance;
+ static getInstance = () => ControllerCustomizationsTable.instance ?? (ControllerCustomizationsTable.instance = new ControllerCustomizationsTable(LocalDb.TABLE_CONTROLLER_CUSTOMIZATIONS));
+ TABLE_PRESETS = LocalDb.TABLE_CONTROLLER_CUSTOMIZATIONS;
+ DEFAULT_PRESETS = {
+ [-1]: {
+ id: -1,
+ name: "ABXY ⇄ BAYX",
+ data: {
+ mapping: {
+ 0: 1,
+ 1: 0,
+ 2: 3,
+ 3: 2
+ },
+ settings: {
+ leftStickDeadzone: [0, 100],
+ rightStickDeadzone: [0, 100],
+ leftTriggerRange: [0, 100],
+ rightTriggerRange: [0, 100],
+ vibrationIntensity: 100
+ }
+ }
+ }
+ };
+ DEFAULT_PRESET_ID = 0;
+}
class ControllerSettingsTable extends BaseLocalTable {
static instance;
static getInstance = () => ControllerSettingsTable.instance ?? (ControllerSettingsTable.instance = new ControllerSettingsTable(LocalDb.TABLE_CONTROLLER_SETTINGS));
static DEFAULT_DATA = {
shortcutPresetId: -1,
- vibrationIntensity: 50
+ customizationPresetId: 0
};
async getControllerData(id) {
let setting = await this.get(id);
@@ -2610,8 +2664,8 @@ class ControllerSettingsTable extends BaseLocalTable {
let all = await this.getAll(), results = {};
for (let key in all) {
if (!all[key]) continue;
- let settings = all[key].data;
- settings.vibrationIntensity /= 100, results[key] = settings;
+ let settings = Object.assign(all[key].data, ControllerSettingsTable.DEFAULT_DATA);
+ results[key] = settings;
}
return results;
}
@@ -2644,24 +2698,65 @@ class StreamSettings {
mkbPreset: null,
keyboardShortcuts: {}
};
+ static CONTROLLER_CUSTOMIZATION_MAPPING = {
+ 0: "A",
+ 1: "B",
+ 2: "X",
+ 3: "Y",
+ 12: "DPadUp",
+ 15: "DPadRight",
+ 13: "DPadDown",
+ 14: "DPadLeft",
+ 4: "LeftShoulder",
+ 5: "RightShoulder",
+ 6: "LeftTrigger",
+ 7: "RightTrigger",
+ 10: "LeftThumb",
+ 11: "RightThumb",
+ 104: "LeftStickAxes",
+ 204: "RightStickAxes",
+ 8: "View",
+ 9: "Menu",
+ 17: "Share"
+ };
static getPref(key) {
return getPref(key);
}
static async refreshControllerSettings() {
- let settings = StreamSettings.settings, controllers = {}, settingsTable = ControllerSettingsTable.getInstance(), shortcutsTable = ControllerShortcutsTable.getInstance(), gamepads = window.navigator.getGamepads();
+ let settings = StreamSettings.settings, controllers = {}, settingsTable = ControllerSettingsTable.getInstance(), shortcutsTable = ControllerShortcutsTable.getInstance(), mappingTable = ControllerCustomizationsTable.getInstance(), gamepads = window.navigator.getGamepads();
for (let gamepad of gamepads) {
if (!gamepad?.connected) continue;
if (gamepad.id === VIRTUAL_GAMEPAD_ID) continue;
- let settingsData = await settingsTable.getControllerData(gamepad.id), shortcutsMapping, preset = await shortcutsTable.getPreset(settingsData.shortcutPresetId);
- if (!preset) shortcutsMapping = null;
- else shortcutsMapping = preset.data.mapping;
+ let settingsData = await settingsTable.getControllerData(gamepad.id), shortcutsPreset = await shortcutsTable.getPreset(settingsData.shortcutPresetId), shortcutsMapping = !shortcutsPreset ? null : shortcutsPreset.data.mapping, customizationPreset = await mappingTable.getPreset(settingsData.customizationPresetId), customizationData = StreamSettings.convertControllerCustomization(customizationPreset?.data);
controllers[gamepad.id] = {
- vibrationIntensity: settingsData.vibrationIntensity,
- shortcuts: shortcutsMapping
+ shortcuts: shortcutsMapping,
+ customization: customizationData
};
}
settings.controllers = controllers, settings.controllerPollingRate = StreamSettings.getPref("controller.pollingRate"), await StreamSettings.refreshDeviceVibration();
}
+ static preCalculateControllerRange(obj, target, values) {
+ if (values && Array.isArray(values)) {
+ let [from, to] = values;
+ if (from > 1 || to < 100) obj[target] = [from / 100, to / 100];
+ }
+ }
+ static convertControllerCustomization(customization) {
+ if (!customization) return null;
+ let converted = {
+ mapping: {},
+ ranges: {},
+ vibrationIntensity: 1
+ }, gamepadKey;
+ for (gamepadKey in customization.mapping) {
+ let gamepadStr = StreamSettings.CONTROLLER_CUSTOMIZATION_MAPPING[gamepadKey];
+ if (!gamepadStr) continue;
+ let mappedKey = customization.mapping[gamepadKey];
+ if (typeof mappedKey === "number") converted.mapping[gamepadStr] = StreamSettings.CONTROLLER_CUSTOMIZATION_MAPPING[mappedKey];
+ else converted.mapping[gamepadStr] = !1;
+ }
+ return StreamSettings.preCalculateControllerRange(converted.ranges, "LeftTrigger", customization.settings.leftTriggerRange), StreamSettings.preCalculateControllerRange(converted.ranges, "RightTrigger", customization.settings.rightTriggerRange), StreamSettings.preCalculateControllerRange(converted.ranges, "LeftThumb", customization.settings.leftStickDeadzone), StreamSettings.preCalculateControllerRange(converted.ranges, "RightThumb", customization.settings.rightStickDeadzone), converted.vibrationIntensity = customization.settings.vibrationIntensity / 100, converted;
+ }
static async refreshDeviceVibration() {
if (!STATES.browser.capabilities.deviceVibration) return;
let mode = StreamSettings.getPref("deviceVibration.mode"), intensity = 0;
@@ -2711,6 +2806,43 @@ class StreamSettings {
window.addEventListener("gamepadconnected", listener), window.addEventListener("gamepaddisconnected", listener), StreamSettings.refreshAllSettings();
}
}
+var BxIcon = {
+ BETTER_XCLOUD: "",
+ TRUE_ACHIEVEMENTS: "",
+ STREAM_SETTINGS: "",
+ STREAM_STATS: "",
+ CLOSE: "",
+ CONTROLLER: "",
+ CREATE_SHORTCUT: "",
+ DISPLAY: "",
+ EYE: "",
+ EYE_SLASH: "",
+ HOME: "",
+ NATIVE_MKB: "",
+ NEW: "",
+ MANAGE: "",
+ COPY: "",
+ TRASH: "",
+ CURSOR_TEXT: "",
+ POWER: "",
+ QUESTION: "",
+ REFRESH: "",
+ REMOTE_PLAY: "",
+ CARET_LEFT: "",
+ CARET_RIGHT: "",
+ SCREENSHOT: "",
+ SPEAKER_MUTED: "",
+ TOUCH_CONTROL_ENABLE: "",
+ TOUCH_CONTROL_DISABLE: "",
+ MICROPHONE: "",
+ MICROPHONE_MUTED: "",
+ BATTERY: "",
+ PLAYTIME: "",
+ SERVER: "",
+ DOWNLOAD: "",
+ UPLOAD: "",
+ AUDIO: ""
+};
class MkbPopup {
static instance;
static getInstance = () => MkbPopup.instance ?? (MkbPopup.instance = new MkbPopup);
@@ -2736,7 +2868,7 @@ class MkbPopup {
}
createActivateButton() {
let options = {
- style: 1 | 512 | 128,
+ style: 1 | 1024 | 128,
label: t("activate"),
onClick: this.onActivate
}, shortcutKey = StreamSettings.findKeyboardShortcut("mkb.toggle");
@@ -2755,6 +2887,7 @@ class MkbPopup {
}
}), createButton({
label: t("manage"),
+ icon: BxIcon.MANAGE,
style: 64,
onClick: () => {
let dialog = SettingsDialog.getInstance();
@@ -3259,16 +3392,22 @@ class NavigationDialogManager {
LOG_TAG = "NavigationDialogManager";
static GAMEPAD_POLLING_INTERVAL = 50;
static GAMEPAD_KEYS = [
- 12,
- 13,
- 14,
- 15,
0,
1,
+ 2,
+ 3,
+ 12,
+ 15,
+ 13,
+ 14,
4,
5,
6,
- 7
+ 7,
+ 10,
+ 11,
+ 8,
+ 9
];
static GAMEPAD_DIRECTION_MAP = {
12: 1,
@@ -3298,31 +3437,14 @@ class NavigationDialogManager {
dialog = null;
dialogsStack = [];
constructor() {
- if (BxLogger.info(this.LOG_TAG, "constructor()"), this.$overlay = CE("div", { class: "bx-navigation-dialog-overlay bx-gone" }), this.$overlay.addEventListener("click", (e) => {
+ BxLogger.info(this.LOG_TAG, "constructor()"), this.$overlay = CE("div", { class: "bx-navigation-dialog-overlay bx-gone" }), this.$overlay.addEventListener("click", (e) => {
e.preventDefault(), e.stopPropagation(), this.dialog?.isCancellable() && this.hide();
- }), document.documentElement.appendChild(this.$overlay), this.$container = CE("div", { class: "bx-navigation-dialog bx-gone" }), document.documentElement.appendChild(this.$container), window.addEventListener(BxEvent.XCLOUD_GUIDE_MENU_SHOWN, (e) => this.hide()), getPref("ui.controllerFriendly"))
- new MutationObserver((mutationList) => {
- if (mutationList.length === 0 || mutationList[0].addedNodes.length === 0) return;
- let $dialog = mutationList[0].addedNodes[0];
- if (!$dialog || !($dialog instanceof HTMLElement)) return;
- this.calculateSelectBoxes($dialog);
- }).observe(this.$container, { childList: !0 });
- }
- calculateSelectBoxes($root) {
- let selects = Array.from($root.querySelectorAll(".bx-select:not([data-calculated]) select"));
- for (let $select of selects) {
- let $parent = $select.parentElement;
- if ($parent.classList.contains("bx-full-width")) {
- $parent.dataset.calculated = "true";
- return;
- }
- let rect = $select.getBoundingClientRect(), $label, width = Math.ceil(rect.width);
- if (!width) return;
- if ($select.multiple) $label = $parent.querySelector(".bx-select-value"), width += 20;
- else $label = $parent.querySelector("div");
- if ($select.querySelector("optgroup")) width -= 15;
- $label.style.minWidth = width + "px", $parent.dataset.calculated = "true";
- }
+ }), document.documentElement.appendChild(this.$overlay), this.$container = CE("div", { class: "bx-navigation-dialog bx-gone" }), document.documentElement.appendChild(this.$container), window.addEventListener(BxEvent.XCLOUD_GUIDE_MENU_SHOWN, (e) => this.hide()), new MutationObserver((mutationList) => {
+ if (mutationList.length === 0 || mutationList[0].addedNodes.length === 0) return;
+ let $dialog = mutationList[0].addedNodes[0];
+ if (!$dialog || !($dialog instanceof HTMLElement)) return;
+ calculateSelectBoxes($dialog);
+ }).observe(this.$container, { childList: !0 });
}
updateActiveInput(input) {
document.documentElement.dataset.activeInput = input;
@@ -3385,7 +3507,7 @@ class NavigationDialogManager {
}
}
this.clearGamepadHoldingInterval();
- }, 200);
+ }, 100);
continue;
}
if (releasedButton === null) {
@@ -3455,7 +3577,12 @@ class NavigationDialogManager {
if (!$focusing || $focusing === this.$container) return null;
if (checked.includes($focusing)) return null;
checked.push($focusing);
- let $target = $focusing, $parent = $target.parentElement, nearby = $target.nearby || {}, orientation = this.getOrientation($target), siblingProperty = NavigationDialogManager.SIBLING_PROPERTY_MAP[orientation][direction];
+ let $target = $focusing, $parent = $target.parentElement, nearby = $target.nearby || {}, orientation = this.getOrientation($target);
+ if (nearby[1] && direction === 1) return nearby[1];
+ else if (nearby[3] && direction === 3) return nearby[3];
+ else if (nearby[4] && direction === 4) return nearby[4];
+ else if (nearby[2] && direction === 2) return nearby[2];
+ let siblingProperty = NavigationDialogManager.SIBLING_PROPERTY_MAP[orientation][direction];
if (siblingProperty) {
let $sibling = $target;
while ($sibling[siblingProperty]) {
@@ -3510,43 +3637,8 @@ class NavigationDialogManager {
dialog && dialog.onBeforeUnmount(), this.$container.firstChild?.remove(), dialog && dialog.onUnmounted(), this.dialog = null;
}
}
-var BxIcon = {
- BETTER_XCLOUD: "",
- TRUE_ACHIEVEMENTS: "",
- STREAM_SETTINGS: "",
- STREAM_STATS: "",
- CLOSE: "",
- CONTROLLER: "",
- CREATE_SHORTCUT: "",
- DISPLAY: "",
- EYE: "",
- EYE_SLASH: "",
- HOME: "",
- NATIVE_MKB: "",
- NEW: "",
- COPY: "",
- TRASH: "",
- CURSOR_TEXT: "",
- POWER: "",
- QUESTION: "",
- REFRESH: "",
- REMOTE_PLAY: "",
- CARET_LEFT: "",
- CARET_RIGHT: "",
- SCREENSHOT: "",
- SPEAKER_MUTED: "",
- TOUCH_CONTROL_ENABLE: "",
- TOUCH_CONTROL_DISABLE: "",
- MICROPHONE: "",
- MICROPHONE_MUTED: "",
- BATTERY: "",
- PLAYTIME: "",
- SERVER: "",
- DOWNLOAD: "",
- UPLOAD: "",
- AUDIO: ""
-};
class BxSelectElement extends HTMLSelectElement {
+ isControllerFriendly;
optionsList;
indicatorsList;
$indicators;
@@ -3558,21 +3650,36 @@ class BxSelectElement extends HTMLSelectElement {
$label;
$checkBox;
static create($select, forceFriendly = !1) {
- if (!forceFriendly && !getPref("ui.controllerFriendly")) return $select.classList.add("bx-select"), $select;
+ let isControllerFriendly = forceFriendly || getPref("ui.controllerFriendly");
+ if ($select.multiple && !isControllerFriendly) return $select.classList.add("bx-select"), $select;
$select.removeAttribute("tabindex");
- let $wrapper = CE("div", { class: "bx-select" }), $btnPrev = createButton({
- label: "<",
- style: 64
- }), $btnNext = createButton({
- label: ">",
- style: 64
- });
- setNearby($wrapper, {
- orientation: "horizontal",
- focus: $btnNext
+ let $wrapper = CE("div", {
+ class: "bx-select",
+ _dataset: {
+ controllerFriendly: isControllerFriendly
+ }
});
+ if ($select.classList.contains("bx-full-width")) $wrapper.classList.add("bx-full-width");
let $content, self = $wrapper;
- if (self.isMultiple = $select.multiple, self.visibleIndex = $select.selectedIndex, self.$select = $select, self.optionsList = Array.from($select.querySelectorAll("option")), self.$indicators = CE("div", { class: "bx-select-indicators" }), self.indicatorsList = [], self.$btnNext = $btnNext, self.$btnPrev = $btnPrev, self.isMultiple) $content = CE("button", {
+ self.isControllerFriendly = isControllerFriendly, self.isMultiple = $select.multiple, self.visibleIndex = $select.selectedIndex, self.$select = $select, self.optionsList = Array.from($select.querySelectorAll("option")), self.$indicators = CE("div", { class: "bx-select-indicators" }), self.indicatorsList = [];
+ let $btnPrev, $btnNext;
+ if (isControllerFriendly) {
+ $btnPrev = createButton({
+ label: "<",
+ style: 64
+ }), $btnNext = createButton({
+ label: ">",
+ style: 64
+ }), setNearby($wrapper, {
+ orientation: "horizontal",
+ focus: $btnNext
+ }), self.$btnNext = $btnNext, self.$btnPrev = $btnPrev;
+ let boundOnPrevNext = BxSelectElement.onPrevNext.bind(self);
+ $btnPrev.addEventListener("click", boundOnPrevNext), $btnNext.addEventListener("click", boundOnPrevNext);
+ } else $select.addEventListener("change", (e) => {
+ self.visibleIndex = $select.selectedIndex, BxSelectElement.resetIndicators.call(self), BxSelectElement.render.call(self);
+ });
+ if (self.isMultiple) $content = CE("button", {
class: "bx-select-value bx-focusable",
tabindex: 0
}, CE("div", {}, self.$checkBox = CE("input", { type: "checkbox" }), self.$label = CE("span", {}, "")), self.$indicators), $content.addEventListener("click", (e) => {
@@ -3582,8 +3689,7 @@ class BxSelectElement extends HTMLSelectElement {
$option && ($option.selected = e.target.checked), BxEvent.dispatch($select, "input");
});
else $content = CE("div", {}, self.$label = CE("label", { for: $select.id + "_checkbox" }, ""), self.$indicators);
- let boundOnPrevNext = BxSelectElement.onPrevNext.bind(self);
- return $select.addEventListener("input", BxSelectElement.render.bind(self)), $btnPrev.addEventListener("click", boundOnPrevNext), $btnNext.addEventListener("click", boundOnPrevNext), new MutationObserver((mutationList, observer2) => {
+ return $select.addEventListener("input", BxSelectElement.render.bind(self)), new MutationObserver((mutationList, observer2) => {
mutationList.forEach((mutation) => {
if (mutation.type === "childList" || mutation.type === "attributes") self.visibleIndex = $select.selectedIndex, self.optionsList = Array.from($select.querySelectorAll("option")), BxSelectElement.resetIndicators.call(self), BxSelectElement.render.call(self);
});
@@ -3591,7 +3697,7 @@ class BxSelectElement extends HTMLSelectElement {
subtree: !0,
childList: !0,
attributes: !0
- }), self.append($select, $btnPrev, $content, $btnNext), BxSelectElement.resetIndicators.call(self), BxSelectElement.render.call(self), Object.defineProperty(self, "value", {
+ }), self.append($select, $btnPrev || "", $content, $btnNext || ""), BxSelectElement.resetIndicators.call(self), BxSelectElement.render.call(self), Object.defineProperty(self, "value", {
get() {
return $select.value;
},
@@ -3640,7 +3746,6 @@ class BxSelectElement extends HTMLSelectElement {
$btnNext,
$btnPrev,
$checkBox,
- visibleIndex,
optionsList,
indicatorsList
} = this;
@@ -3649,7 +3754,7 @@ class BxSelectElement extends HTMLSelectElement {
let $option = BxSelectElement.getOptionAtIndex.call(this, this.visibleIndex), content = "";
if ($option) {
let $parent = $option.parentElement, hasLabel = $parent instanceof HTMLOptGroupElement || this.$select.querySelector("optgroup");
- if (content = $option.textContent || "", content && hasLabel) {
+ if (content = $option.dataset.label || $option.textContent || "", content && hasLabel) {
let groupLabel = $parent instanceof HTMLOptGroupElement ? $parent.label : " ";
$label.innerHTML = "";
let fragment = document.createDocumentFragment();
@@ -3658,12 +3763,12 @@ class BxSelectElement extends HTMLSelectElement {
} else $label.textContent = content;
if ($label.classList.toggle("bx-line-through", $option && $option.disabled), this.isMultiple) $checkBox.checked = $option?.selected || !1, $checkBox.classList.toggle("bx-gone", !content);
let disableButtons = optionsList.length <= 1;
- $btnPrev.classList.toggle("bx-inactive", disableButtons), $btnNext.classList.toggle("bx-inactive", disableButtons);
+ $btnPrev?.classList.toggle("bx-inactive", disableButtons), $btnNext?.classList.toggle("bx-inactive", disableButtons);
for (let i = 0;i < optionsList.length; i++) {
let $option2 = optionsList[i], $indicator = indicatorsList[i];
if (!$option2 || !$indicator) continue;
if (clearDataSet($indicator), $option2.selected) $indicator.dataset.selected = "true";
- if ($option2.index === visibleIndex) $indicator.dataset.highlighted = "true";
+ if ($option2.index === this.visibleIndex) $indicator.dataset.highlighted = "true";
}
}
static normalizeIndex(index) {
@@ -4006,7 +4111,7 @@ class SuggestionsSetting {
_nearby: {
orientation: "vertical"
}
- }, BxSelectElement.create($select, !0), $suggestedSettings, $btnApply, BX_FLAGS.DeviceInfo.deviceType.includes("android") && CE("a", {
+ }, BxSelectElement.create($select), $suggestedSettings, $btnApply, BX_FLAGS.DeviceInfo.deviceType.includes("android") && CE("a", {
class: "bx-suggest-link bx-focusable",
href: "https://better-xcloud.github.io/guide/android-webview-tweaks/",
target: "_blank",
@@ -4385,7 +4490,6 @@ class SettingsDialog extends NavigationDialog {
}]
}];
TAB_CONTROLLER_ITEMS = [
- !1,
{
group: "controller",
label: t("controller"),
@@ -4396,6 +4500,7 @@ class SettingsDialog extends NavigationDialog {
!1
]
},
+ !1,
!1
];
TAB_MKB_ITEMS = () => [
@@ -4505,9 +4610,8 @@ class SettingsDialog extends NavigationDialog {
}
let $child, children = Array.from(this.$tabContents.children);
for ($child of children)
- if ($child.dataset.tabGroup === $svg.dataset.group) {
- if ($child.classList.remove("bx-gone"), getPref("ui.controllerFriendly")) this.dialogManager.calculateSelectBoxes($child);
- } else $child.classList.add("bx-gone");
+ if ($child.dataset.tabGroup === $svg.dataset.group) $child.classList.remove("bx-gone"), calculateSelectBoxes($child);
+ else $child.classList.add("bx-gone");
for (let $child2 of Array.from(this.$tabs.children))
$child2.classList.remove("bx-active");
$svg.classList.add("bx-active");
@@ -4541,7 +4645,6 @@ class SettingsDialog extends NavigationDialog {
}
}, $control = CE("select", {
id: `bx_setting_${escapeCssSelector(setting.pref)}`,
- title: setting.label,
tabindex: 0
});
$control.name = $control.id, $control.addEventListener("input", (e) => {
@@ -4640,7 +4743,7 @@ class SettingsDialog extends NavigationDialog {
label = createButton({
label,
url: "https://github.com/redphx/better-xcloud/releases",
- style: 2048 | 16 | 64
+ style: 4096 | 16 | 64
});
}
if (label) {
@@ -4873,8 +4976,8 @@ var BxExposed = {
BxLogger.error("setupGainNode", e), STATES.currentStream.audioGainNode = null;
}
},
- handleControllerShortcut: !1,
- resetControllerShortcut: !1,
+ handleControllerShortcut: () => {},
+ resetControllerShortcut: () => {},
overrideSettings: {
Tv_settings: {
hasCompletedOnboarding: !0
@@ -4942,7 +5045,7 @@ class HeaderSection {
classes: ["bx-header-remote-play-button", "bx-gone"],
icon: BxIcon.REMOTE_PLAY,
title: t("remote-play"),
- style: 8 | 64 | 1024,
+ style: 8 | 64 | 2048,
onClick: (e) => RemotePlayManager.getInstance()?.togglePopup()
}), this.$btnSettings = createButton({
classes: ["bx-header-settings-button"],
@@ -5772,7 +5875,7 @@ function getOsNameFromResolution(resolution) {
return osName;
}
function addCss() {
- let css = ':root{--bx-title-font:Bahnschrift,Arial,Helvetica,sans-serif;--bx-title-font-semibold:Bahnschrift Semibold,Arial,Helvetica,sans-serif;--bx-normal-font:"Segoe UI",Arial,Helvetica,sans-serif;--bx-monospaced-font:Consolas,"Courier New",Courier,monospace;--bx-promptfont-font:promptfont;--bx-button-height:40px;--bx-default-button-color:#2d3036;--bx-default-button-rgb:45,48,54;--bx-default-button-hover-color:#515863;--bx-default-button-hover-rgb:81,88,99;--bx-default-button-active-color:#222428;--bx-default-button-active-rgb:34,36,40;--bx-default-button-disabled-color:#8e8e8e;--bx-default-button-disabled-rgb:142,142,142;--bx-primary-button-color:#008746;--bx-primary-button-rgb:0,135,70;--bx-primary-button-hover-color:#04b358;--bx-primary-button-hover-rgb:4,179,88;--bx-primary-button-active-color:#044e2a;--bx-primary-button-active-rgb:4,78,42;--bx-primary-button-disabled-color:#448262;--bx-primary-button-disabled-rgb:68,130,98;--bx-warning-button-color:#c16e04;--bx-warning-button-rgb:193,110,4;--bx-warning-button-hover-color:#fa9005;--bx-warning-button-hover-rgb:250,144,5;--bx-warning-button-active-color:#965603;--bx-warning-button-active-rgb:150,86,3;--bx-warning-button-disabled-color:#a2816c;--bx-warning-button-disabled-rgb:162,129,108;--bx-danger-button-color:#c10404;--bx-danger-button-rgb:193,4,4;--bx-danger-button-hover-color:#e61d1d;--bx-danger-button-hover-rgb:230,29,29;--bx-danger-button-active-color:#a26c6c;--bx-danger-button-active-rgb:162,108,108;--bx-danger-button-disabled-color:#df5656;--bx-danger-button-disabled-rgb:223,86,86;--bx-fullscreen-text-z-index:9999;--bx-toast-z-index:6000;--bx-key-binding-dialog-z-index:5010;--bx-key-binding-dialog-overlay-z-index:5000;--bx-stats-bar-z-index:4010;--bx-navigation-dialog-z-index:3010;--bx-navigation-dialog-overlay-z-index:3000;--bx-mkb-pointer-lock-msg-z-index:2000;--bx-game-bar-z-index:1000;--bx-screenshot-animation-z-index:200;--bx-wait-time-box-z-index:100}@font-face{font-family:\'promptfont\';src:url("https://redphx.github.io/better-xcloud/fonts/promptfont.otf")}div[class^=HUDButton-module__hiddenContainer] ~ div:not([class^=HUDButton-module__hiddenContainer]){opacity:0;pointer-events:none !important;position:absolute;top:-9999px;left:-9999px}@media screen and (max-width:640px){header a[href="/play"]{display:none}}.bx-full-width{width:100% !important}.bx-full-height{height:100% !important}.bx-no-scroll{overflow:hidden !important}.bx-hide-scroll-bar{scrollbar-width:none}.bx-hide-scroll-bar::-webkit-scrollbar{display:none}.bx-gone{display:none !important}.bx-offscreen{position:absolute !important;top:-9999px !important;left:-9999px !important;visibility:hidden !important}.bx-hidden{visibility:hidden !important}.bx-invisible{opacity:0}.bx-unclickable{pointer-events:none}.bx-pixel{width:1px !important;height:1px !important}.bx-no-margin{margin:0 !important}.bx-no-padding{padding:0 !important}.bx-prompt{font-family:var(--bx-promptfont-font) !important}.bx-line-through{text-decoration:line-through !important}.bx-normal-case{text-transform:none !important}.bx-normal-link{text-transform:none !important;text-align:left !important;font-weight:400 !important;font-family:var(--bx-normal-font) !important}select[multiple],select[multiple]:focus{overflow:auto;border:none}select[multiple] option,select[multiple]:focus option{padding:4px 6px}select[multiple] option:checked,select[multiple]:focus option:checked{background:#1a7bc0 linear-gradient(0deg,#1a7bc0 0%,#1a7bc0 100%)}select[multiple] option:checked::before,select[multiple]:focus option:checked::before{content:\'☑️\';font-size:12px;display:inline-block;margin-right:6px;height:100%;line-height:100%;vertical-align:middle}#headerArea,#uhfSkipToMain,.uhf-footer{display:none}div[class*=NotFocusedDialog]{position:absolute !important;top:-9999px !important;left:-9999px !important;width:0 !important;height:0 !important}#game-stream video:not([src]){visibility:hidden}div[class*=SupportedInputsBadge]:not(:has(:nth-child(2))),div[class*=SupportedInputsBadge] svg:first-of-type{display:none}.bx-game-tile-wait-time{position:absolute;top:0;left:0;z-index:1;background:rgba(0,0,0,0.5);display:flex;border-radius:4px 0 4px 0;align-items:center;padding:4px 8px}.bx-game-tile-wait-time svg{width:14px;height:16px;margin-right:2px}.bx-game-tile-wait-time span{display:inline-block;height:16px;line-height:16px;font-size:12px;font-weight:bold;margin-left:2px}.bx-game-tile-wait-time[data-duration=short]{background-color:rgba(0,133,133,0.75)}.bx-game-tile-wait-time[data-duration=medium]{background-color:rgba(213,133,0,0.75)}.bx-game-tile-wait-time[data-duration=long]{background-color:rgba(150,0,0,0.75)}.bx-fullscreen-text{position:fixed;top:0;bottom:0;left:0;right:0;background:rgba(0,0,0,0.8);z-index:var(--bx-fullscreen-text-z-index);line-height:100vh;color:#fff;text-align:center;font-weight:400;font-family:var(--bx-normal-font);font-size:1.3rem;user-select:none;-webkit-user-select:none}#root section[class*=DeviceCodePage-module__page]{margin-left:20px !important;margin-right:20px !important;margin-top:20px !important;max-width:800px !important}#root div[class*=DeviceCodePage-module__back]{display:none}.bx-blink-me{animation:bx-blinker 1s linear infinite}@-moz-keyframes bx-blinker{100%{opacity:0}}@-webkit-keyframes bx-blinker{100%{opacity:0}}@-o-keyframes bx-blinker{100%{opacity:0}}@keyframes bx-blinker{100%{opacity:0}}.bx-button{--button-rgb:var(--bx-default-button-rgb);--button-hover-rgb:var(--bx-default-button-hover-rgb);--button-active-rgb:var(--bx-default-button-active-rgb);--button-disabled-rgb:var(--bx-default-button-disabled-rgb);background-color:rgb(var(--button-rgb));user-select:none;-webkit-user-select:none;color:#fff;font-family:var(--bx-title-font-semibold);font-size:14px;border:none;font-weight:400;height:var(--bx-button-height);border-radius:4px;padding:0 8px;text-transform:uppercase;cursor:pointer;overflow:hidden}.bx-button:not([disabled]):active{background-color:rgb(var(--button-active-rgb))}.bx-button:focus{outline:none !important}.bx-button:not([disabled]):not(:active):hover,.bx-button:not([disabled]):not(:active).bx-focusable:focus{background-color:rgb(var(--button-hover-rgb))}.bx-button:disabled{cursor:default;background-color:rgb(var(--button-disabled-rgb))}.bx-button.bx-ghost{background-color:transparent}.bx-button.bx-ghost:not([disabled]):not(:active):hover,.bx-button.bx-ghost:not([disabled]):not(:active).bx-focusable:focus{background-color:rgb(var(--button-hover-rgb))}.bx-button.bx-primary{--button-rgb:var(--bx-primary-button-rgb)}.bx-button.bx-primary:not([disabled]):active{--button-active-rgb:var(--bx-primary-button-active-rgb)}.bx-button.bx-primary:not([disabled]):not(:active):hover,.bx-button.bx-primary:not([disabled]):not(:active).bx-focusable:focus{--button-hover-rgb:var(--bx-primary-button-hover-rgb)}.bx-button.bx-primary:disabled{--button-disabled-rgb:var(--bx-primary-button-disabled-rgb)}.bx-button.bx-warning{--button-rgb:var(--bx-warning-button-rgb)}.bx-button.bx-warning:not([disabled]):active{--button-active-rgb:var(--bx-warning-button-active-rgb)}.bx-button.bx-warning:not([disabled]):not(:active):hover,.bx-button.bx-warning:not([disabled]):not(:active).bx-focusable:focus{--button-hover-rgb:var(--bx-warning-button-hover-rgb)}.bx-button.bx-warning:disabled{--button-disabled-rgb:var(--bx-warning-button-disabled-rgb)}.bx-button.bx-danger{--button-rgb:var(--bx-danger-button-rgb)}.bx-button.bx-danger:not([disabled]):active{--button-active-rgb:var(--bx-danger-button-active-rgb)}.bx-button.bx-danger:not([disabled]):not(:active):hover,.bx-button.bx-danger:not([disabled]):not(:active).bx-focusable:focus{--button-hover-rgb:var(--bx-danger-button-hover-rgb)}.bx-button.bx-danger:disabled{--button-disabled-rgb:var(--bx-danger-button-disabled-rgb)}.bx-button.bx-frosted{--button-alpha:.2;background-color:rgba(var(--button-rgb), var(--button-alpha));backdrop-filter:blur(4px) brightness(1.5)}.bx-button.bx-frosted:not([disabled]):not(:active):hover,.bx-button.bx-frosted:not([disabled]):not(:active).bx-focusable:focus{background-color:rgba(var(--button-hover-rgb), var(--button-alpha))}.bx-button.bx-drop-shadow{box-shadow:0 0 4px rgba(0,0,0,0.502)}.bx-button.bx-tall{height:calc(var(--bx-button-height) * 1.5) !important}.bx-button.bx-circular{border-radius:var(--bx-button-height);width:var(--bx-button-height);height:var(--bx-button-height)}.bx-button svg{display:inline-block;width:16px;height:var(--bx-button-height)}.bx-button span{display:inline-block;line-height:var(--bx-button-height);vertical-align:middle;color:#fff;overflow:hidden;white-space:nowrap}.bx-button span:not(:only-child){margin-left:10px}.bx-button.bx-button-multi-lines{height:auto;text-align:left;padding:10px 0}.bx-button.bx-button-multi-lines span{line-height:unset;display:block}.bx-button.bx-button-multi-lines span:last-of-type{text-transform:none;font-weight:normal;font-family:"Segoe Sans Variable Text";font-size:12px;margin-top:4px}.bx-focusable{position:relative;overflow:visible}.bx-focusable::after{border:2px solid transparent;border-radius:10px}.bx-focusable:focus::after{content:\'\';border-color:#fff;position:absolute;top:-6px;left:-6px;right:-6px;bottom:-6px}html[data-active-input=touch] .bx-focusable:focus::after,html[data-active-input=mouse] .bx-focusable:focus::after{border-color:transparent !important}.bx-focusable.bx-circular::after{border-radius:var(--bx-button-height)}a.bx-button{display:inline-block}a.bx-button.bx-full-width{text-align:center}button.bx-inactive{pointer-events:none;opacity:.2;background:transparent !important}.bx-header-remote-play-button{height:auto;margin-right:8px !important}.bx-header-remote-play-button svg{width:24px;height:24px}.bx-header-settings-button{line-height:30px;font-size:14px;text-transform:uppercase;position:relative}.bx-header-settings-button[data-update-available]::before{content:\'🌟\' !important;line-height:var(--bx-button-height);display:inline-block;margin-left:4px}.bx-key-binding-dialog-overlay{position:fixed;inset:0;z-index:var(--bx-key-binding-dialog-overlay-z-index);background:#000;opacity:50%}.bx-key-binding-dialog{display:flex;flex-flow:column;max-height:90vh;position:fixed;top:50%;left:50%;margin-right:-50%;transform:translate(-50%,-50%);min-width:420px;padding:16px;border-radius:8px;z-index:var(--bx-key-binding-dialog-z-index);background:#1a1b1e;color:#fff;font-weight:400;font-size:16px;font-family:var(--bx-normal-font);box-shadow:0 0 6px #000;user-select:none;-webkit-user-select:none}.bx-key-binding-dialog *:focus{outline:none !important}.bx-key-binding-dialog h2{margin-bottom:12px;color:#fff;display:block;font-family:var(--bx-title-font);font-size:32px;font-weight:400;line-height:var(--bx-button-height)}.bx-key-binding-dialog > div{overflow:auto;padding:2px 0}.bx-key-binding-dialog > button{padding:8px 32px;margin:10px auto 0;border:none;border-radius:4px;display:block;background-color:#2d3036;text-align:center;color:#fff;text-transform:uppercase;font-family:var(--bx-title-font);font-weight:400;line-height:18px;font-size:14px}@media (hover:hover){.bx-key-binding-dialog > button:hover{background-color:#515863}}.bx-key-binding-dialog > button:focus{background-color:#515863}.bx-key-binding-dialog ul{margin-bottom:1rem}.bx-key-binding-dialog ul li{display:none}.bx-key-binding-dialog ul[data-flags*="[1]"] > li[data-flag="1"],.bx-key-binding-dialog ul[data-flags*="[2]"] > li[data-flag="2"],.bx-key-binding-dialog ul[data-flags*="[4]"] > li[data-flag="4"],.bx-key-binding-dialog ul[data-flags*="[8]"] > li[data-flag="8"]{display:list-item}@media screen and (max-width:450px){.bx-key-binding-dialog{min-width:100%}}.bx-navigation-dialog{position:absolute;z-index:var(--bx-navigation-dialog-z-index);font-family:var(--bx-title-font)}.bx-navigation-dialog *:focus{outline:none !important}.bx-navigation-dialog select:disabled{-webkit-appearance:none;text-align-last:right;text-align:right;color:#fff;background:#131416;border:none;border-radius:4px;padding:0 5px}.bx-navigation-dialog-overlay{position:fixed;background:rgba(11,11,11,0.89);top:0;left:0;right:0;bottom:0;z-index:var(--bx-navigation-dialog-overlay-z-index)}.bx-navigation-dialog-overlay[data-is-playing="true"]{background:transparent}.bx-centered-dialog{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);color:#fff;background:#1a1b1e;border-radius:10px;width:450px;max-width:calc(100vw - 20px);margin:0 0 0 auto;padding:16px;max-height:95vh;flex-direction:column;overflow:hidden;display:flex;flex-direction:column}.bx-centered-dialog .bx-dialog-title{display:flex;flex-direction:row;align-items:center;margin-bottom:10px}.bx-centered-dialog .bx-dialog-title p{padding:0;margin:0;flex:1;font-size:1.2rem;font-weight:bold}.bx-centered-dialog .bx-dialog-title button{flex-shrink:0}.bx-centered-dialog .bx-dialog-content{flex:1;overflow:auto;overflow-x:hidden}.bx-centered-dialog .bx-dialog-preset-tools{display:flex;margin-bottom:12px;gap:6px}.bx-centered-dialog .bx-dialog-preset-tools select{flex:1}.bx-centered-dialog .bx-default-preset-note{font-size:12px;font-style:italic;text-align:center;margin-bottom:10px}.bx-centered-dialog input,.bx-settings-dialog input{accent-color:var(--bx-primary-button-color)}.bx-centered-dialog input:focus,.bx-settings-dialog input:focus{accent-color:var(--bx-danger-button-color)}.bx-centered-dialog select:disabled,.bx-settings-dialog select:disabled{-webkit-appearance:none;background:transparent;text-align-last:right;border:none;color:#fff}.bx-centered-dialog select option:disabled,.bx-settings-dialog select option:disabled{display:none}.bx-centered-dialog input[type=checkbox]:focus,.bx-settings-dialog input[type=checkbox]:focus,.bx-centered-dialog select:focus,.bx-settings-dialog select:focus{filter:drop-shadow(1px 0 0 #fff) drop-shadow(-1px 0 0 #fff) drop-shadow(0 1px 0 #fff) drop-shadow(0 -1px 0 #fff)}.bx-centered-dialog a,.bx-settings-dialog a{color:#1c9d1c;text-decoration:none}.bx-centered-dialog a:hover,.bx-settings-dialog a:hover,.bx-centered-dialog a:focus,.bx-settings-dialog a:focus{color:#5dc21e}.bx-centered-dialog label,.bx-settings-dialog label{margin:0}.bx-controller-shortcuts-manager-container .bx-shortcut-note{margin-top:10px;font-size:14px;text-align:center}.bx-controller-shortcuts-manager-container .bx-shortcut-row{display:flex;gap:10px;margin-bottom:10px;align-items:center}.bx-controller-shortcuts-manager-container .bx-shortcut-row label.bx-prompt{flex-shrink:0;font-size:32px;margin:0}.bx-controller-shortcuts-manager-container .bx-shortcut-row label.bx-prompt::first-letter{letter-spacing:6px}.bx-controller-shortcuts-manager-container .bx-shortcut-row .bx-shortcut-actions{flex:1;position:relative}.bx-controller-shortcuts-manager-container .bx-shortcut-row .bx-shortcut-actions select{width:100%;height:100%;min-height:38px;display:block}.bx-controller-shortcuts-manager-container .bx-shortcut-row .bx-shortcut-actions select:first-of-type{position:absolute;top:0;left:0}.bx-controller-shortcuts-manager-container .bx-shortcut-row .bx-shortcut-actions select:last-of-type{opacity:0;z-index:calc(var(--bx-settings-z-index) + 1)}.bx-controller-shortcuts-manager-container select:disabled{text-align:left;text-align-last:left}.bx-keyboard-shortcuts-manager-container{display:flex;flex-direction:column;gap:16px}.bx-keyboard-shortcuts-manager-container fieldset{background:#2a2a2a;border:1px solid #2a2a2a;border-radius:4px;padding:4px}.bx-keyboard-shortcuts-manager-container legend{width:auto;padding:4px 8px;margin:0 4px 4px;background:#004f87;box-shadow:0 2px 0 #071e3d;border-radius:4px;font-size:14px;font-weight:bold;text-transform:uppercase}.bx-keyboard-shortcuts-manager-container .bx-settings-row{background:none;padding:10px}.bx-settings-dialog{display:flex;position:fixed;top:0;right:0;bottom:0;opacity:.98;user-select:none;-webkit-user-select:none}.bx-settings-dialog .bx-focusable::after{border-radius:4px}.bx-settings-dialog .bx-focusable:focus::after{top:0;left:0;right:0;bottom:0}.bx-settings-dialog .bx-settings-reload-note{font-size:.8rem;display:block;padding:8px;font-style:italic;font-weight:normal;height:var(--bx-button-height)}.bx-settings-tabs-container{position:fixed;width:48px;max-height:100vh;display:flex;flex-direction:column}.bx-settings-tabs-container > div:last-of-type{display:flex;flex-direction:column;align-items:end}.bx-settings-tabs-container > div:last-of-type button{flex-shrink:0;border-top-right-radius:0;border-bottom-right-radius:0;margin-top:8px;height:unset;padding:8px 10px}.bx-settings-tabs-container > div:last-of-type button svg{width:16px;height:16px}.bx-settings-tabs{display:flex;flex-direction:column;border-radius:0 0 0 8px;box-shadow:0 0 6px #000;overflow:overlay;flex:1}.bx-settings-tabs svg{width:24px;height:24px;padding:10px;flex-shrink:0;box-sizing:content-box;background:#131313;cursor:pointer;border-left:4px solid #1e1e1e}.bx-settings-tabs svg.bx-active{background:#222;border-color:#008746}.bx-settings-tabs svg:not(.bx-active):hover{background:#2f2f2f;border-color:#484848}.bx-settings-tabs svg:focus{border-color:#fff}.bx-settings-tabs svg[data-group=global][data-need-refresh=true]{background:var(--bx-danger-button-color) !important}.bx-settings-tabs svg[data-group=global][data-need-refresh=true]:hover{background:var(--bx-danger-button-hover-color) !important}.bx-settings-tab-contents{flex-direction:column;padding:10px;margin-left:48px;width:450px;max-width:calc(100vw - tabsWidth);background:#1a1b1e;color:#fff;font-weight:400;font-size:16px;font-family:var(--bx-title-font);text-align:center;box-shadow:0 0 6px #000;overflow:overlay;z-index:1}.bx-settings-tab-contents > div[data-tab-group=mkb]{display:flex;flex-direction:column;height:100%;overflow:hidden}.bx-settings-tab-contents .bx-top-buttons{display:flex;flex-direction:column;gap:8px;margin-bottom:8px}.bx-settings-tab-contents .bx-top-buttons .bx-button{display:block}.bx-settings-tab-contents h2{margin:16px 0 8px 0;display:flex;align-items:center}.bx-settings-tab-contents h2:first-of-type{margin-top:0}.bx-settings-tab-contents h2 span{display:inline-block;font-size:20px;font-weight:bold;text-align:left;flex:1;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;min-height:var(--bx-button-height);align-content:center}@media (max-width:500px){.bx-settings-tab-contents{width:calc(100vw - 48px)}}.bx-settings-row{display:flex;gap:10px;padding:16px 10px;margin:0;background:#2a2a2a;border-bottom:1px solid #343434}.bx-settings-row:hover,.bx-settings-row:focus-within{background-color:#242424}.bx-settings-row:not(:has(> input[type=checkbox])){flex-wrap:wrap}.bx-settings-row > span.bx-settings-label{font-size:14px;display:block;text-align:left;align-self:center;margin-bottom:0 !important;flex:1}.bx-settings-row > span.bx-settings-label + *{margin:0 0 0 auto}.bx-settings-row[data-multi-lines="true"]{flex-direction:column}.bx-settings-row[data-multi-lines="true"] > span.bx-settings-label{align-self:start}.bx-settings-row[data-multi-lines="true"] > span.bx-settings-label + *{margin:unset}.bx-settings-dialog-note{display:block;color:#afafb0;font-size:12px;font-weight:lighter;font-style:italic}.bx-settings-dialog-note:not(:has(a)){margin-top:4px}.bx-settings-dialog-note a{display:inline-block;padding:4px}.bx-settings-custom-user-agent{display:block;width:100%;padding:6px}.bx-donation-link{display:block;text-align:center;text-decoration:none;height:20px;line-height:20px;font-size:14px;margin-top:10px;margin-bottom:10px}.bx-debug-info button{margin-top:10px}.bx-debug-info pre{margin-top:10px;cursor:copy;color:#fff;padding:8px;border:1px solid #2d2d2d;background:#212121;white-space:break-spaces;text-align:left}.bx-debug-info pre:hover{background:#272727}.bx-settings-app-version{margin-top:10px;text-align:center;color:#747474;font-size:12px}.bx-note-unsupported{display:block;font-size:12px;font-style:italic;font-weight:normal;color:#828282}.bx-settings-tab-contents > div *:not(.bx-settings-row):has(+ .bx-settings-row) + .bx-settings-row:has(+ .bx-settings-row){border-top-left-radius:6px;border-top-right-radius:6px}.bx-settings-tab-contents > div .bx-settings-row:not(:has(+ .bx-settings-row)){border:none;border-bottom-left-radius:6px;border-bottom-right-radius:6px}.bx-settings-tab-contents > div *:not(.bx-settings-row):has(+ .bx-settings-row) + .bx-settings-row:not(:has(+ .bx-settings-row)){border:none;border-radius:6px}.bx-suggest-toggler{text-align:left;display:flex;border-radius:4px;overflow:hidden;background:#003861;height:45px;align-items:center}.bx-suggest-toggler label{flex:1;align-content:center;padding:0 10px;background:#004f87;height:100%}.bx-suggest-toggler span{display:inline-block;align-self:center;padding:10px;width:45px;text-align:center}.bx-suggest-toggler:hover,.bx-suggest-toggler:focus{cursor:pointer;background:#005da1}.bx-suggest-toggler:hover label,.bx-suggest-toggler:focus label{background:#006fbe}.bx-suggest-toggler[bx-open] span{transform:rotate(90deg)}.bx-suggest-toggler[bx-open]+ .bx-suggest-box{display:block}.bx-suggest-box{display:none}.bx-suggest-wrapper{display:flex;flex-direction:column;gap:10px;margin:10px}.bx-suggest-note{font-size:11px;color:#8c8c8c;font-style:italic;font-weight:100}.bx-suggest-link{font-size:14px;display:inline-block;margin-top:4px;padding:4px}.bx-suggest-row{display:flex;flex-direction:row;gap:10px}.bx-suggest-row label{flex:1;overflow:overlay;border-radius:4px}.bx-suggest-row label .bx-suggest-label{background:#323232;padding:4px 10px;font-size:12px;text-align:left}.bx-suggest-row label .bx-suggest-value{padding:6px;font-size:14px}.bx-suggest-row label .bx-suggest-value.bx-suggest-change{background-color:var(--bx-warning-color)}.bx-suggest-row.bx-suggest-ok input{visibility:hidden}.bx-suggest-row.bx-suggest-ok .bx-suggest-label{background-color:#008114}.bx-suggest-row.bx-suggest-ok .bx-suggest-value{background-color:#13a72a}.bx-suggest-row.bx-suggest-change .bx-suggest-label{background-color:#a65e08}.bx-suggest-row.bx-suggest-change .bx-suggest-value{background-color:#d57f18}.bx-suggest-row.bx-suggest-change:hover label{cursor:pointer}.bx-suggest-row.bx-suggest-change:hover .bx-suggest-label{background-color:#995707}.bx-suggest-row.bx-suggest-change:hover .bx-suggest-value{background-color:#bd7115}.bx-suggest-row.bx-suggest-change input:not(:checked) + label{opacity:.5}.bx-suggest-row.bx-suggest-change input:not(:checked) + label .bx-suggest-label{background-color:#2a2a2a}.bx-suggest-row.bx-suggest-change input:not(:checked) + label .bx-suggest-value{background-color:#393939}.bx-suggest-row.bx-suggest-change:hover input:not(:checked) + label{opacity:1}.bx-suggest-row.bx-suggest-change:hover input:not(:checked) + label .bx-suggest-label{background-color:#202020}.bx-suggest-row.bx-suggest-change:hover input:not(:checked) + label .bx-suggest-value{background-color:#303030}.bx-sub-content-box{background:#161616;padding:10px;box-shadow:0 0 12px #0f0f0f inset;border-radius:10px}.bx-settings-row .bx-sub-content-box{background:#202020;padding:12px;box-shadow:0 0 4px #000 inset;border-radius:6px}.bx-controller-extra-settings[data-has-gamepad=true] > :first-child{display:none}.bx-controller-extra-settings[data-has-gamepad=true] > :last-child{display:block}.bx-controller-extra-settings[data-has-gamepad=false] > :first-child{display:block}.bx-controller-extra-settings[data-has-gamepad=false] > :last-child{display:none}.bx-controller-extra-settings .bx-controller-extra-wrapper{flex:1;min-width:1px}.bx-controller-extra-settings .bx-sub-content-box{flex:1;text-align:left;display:flex;flex-direction:column;margin-top:10px}.bx-controller-extra-settings .bx-sub-content-box > label{font-size:14px}.bx-preset-row{display:flex;gap:8px}.bx-preset-row .bx-select{flex:1}.bx-toast{user-select:none;-webkit-user-select:none;position:fixed;left:50%;top:24px;transform:translate(-50%,0);background:#000;border-radius:16px;color:#fff;z-index:var(--bx-toast-z-index);font-family:var(--bx-normal-font);border:2px solid #fff;display:flex;align-items:center;opacity:0;overflow:clip;transition:opacity .2s ease-in}.bx-toast.bx-show{opacity:.85}.bx-toast.bx-hide{opacity:0;pointer-events:none}.bx-toast-msg{font-size:14px;display:inline-block;padding:12px 16px;white-space:pre}.bx-toast-status{font-weight:bold;font-size:14px;text-transform:uppercase;display:inline-block;background:#515863;padding:12px 16px;color:#fff;white-space:pre}.bx-wait-time-box{position:fixed;top:0;right:0;background-color:rgba(0,0,0,0.8);color:#fff;z-index:var(--bx-wait-time-box-z-index);padding:12px;border-radius:0 0 0 8px}.bx-wait-time-box label{display:block;text-transform:uppercase;text-align:right;font-size:12px;font-weight:bold;margin:0}.bx-wait-time-box span{display:block;font-family:var(--bx-monospaced-font);text-align:right;font-size:16px;margin-bottom:10px}.bx-wait-time-box span:last-of-type{margin-bottom:0}.bx-remote-play-container{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);color:#fff;background:#1a1b1e;border-radius:10px;width:420px;max-width:calc(100vw - 20px);margin:0 0 0 auto;padding:16px}.bx-remote-play-container > .bx-button{display:table;margin:0 0 0 auto}.bx-remote-play-settings{margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid #2d2d2d}.bx-remote-play-settings > div{display:flex}.bx-remote-play-settings label{flex:1}.bx-remote-play-settings label p{margin:4px 0 0;padding:0;color:#888;font-size:12px}.bx-remote-play-resolution{display:block}.bx-remote-play-resolution input[type="radio"]{accent-color:var(--bx-primary-button-color);margin-right:6px}.bx-remote-play-resolution input[type="radio"]:focus{accent-color:var(--bx-primary-button-hover-color)}.bx-remote-play-device-wrapper{display:flex;margin-bottom:12px}.bx-remote-play-device-wrapper:last-child{margin-bottom:2px}.bx-remote-play-device-info{flex:1;padding:4px 0}.bx-remote-play-device-name{font-size:20px;font-weight:bold;display:inline-block;vertical-align:middle}.bx-remote-play-console-type{font-size:12px;background:#004c87;color:#fff;display:inline-block;border-radius:14px;padding:2px 10px;margin-left:8px;vertical-align:middle}.bx-remote-play-power-state{color:#888;font-size:12px}.bx-remote-play-connect-button{min-height:100%;margin:4px 0}.bx-remote-play-buttons{display:flex;justify-content:space-between}select.bx-select{min-height:30px}div.bx-select{display:flex;align-items:center;flex:0 1 auto;gap:8px}div.bx-select select{position:absolute !important;top:-9999px !important;left:-9999px !important;visibility:hidden !important}div.bx-select select:disabled ~ button{display:none}div.bx-select select:disabled ~ div{background:#131416;color:#fff;pointer-events:none}div.bx-select select:disabled ~ div .bx-select-indicators{visibility:hidden}div.bx-select > div,div.bx-select button.bx-select-value{min-width:120px;text-align:left;line-height:24px;vertical-align:middle;background:#fff;color:#000;border-radius:4px;padding:2px 8px;display:flex;flex:1;flex-direction:column}div.bx-select > div{min-height:24px;box-sizing:content-box}div.bx-select > div input{display:inline-block;margin-right:8px}div.bx-select > div label{margin-bottom:0;font-size:14px;width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;min-height:15px}div.bx-select > div label span{display:block;font-size:10px;font-weight:bold;text-align:left;line-height:initial;white-space:pre;min-height:15px;align-content:center}div.bx-select button.bx-select-value{border:none;cursor:pointer;min-height:30px;font-size:.9rem;align-items:center}div.bx-select button.bx-select-value > div{display:flex;width:100%}div.bx-select button.bx-select-value span{flex:1;text-align:left;display:inline-block}div.bx-select button.bx-select-value input{margin:0 4px;accent-color:var(--bx-primary-button-color);pointer-events:none}div.bx-select button.bx-select-value:hover input,div.bx-select button.bx-select-value:focus input{accent-color:var(--bx-danger-button-color)}div.bx-select button.bx-select-value:hover::after,div.bx-select button.bx-select-value:focus::after{border-color:#4d4d4d !important}div.bx-select button.bx-button{border:none;height:24px;width:24px;padding:0;line-height:24px;color:#fff;border-radius:4px;font-weight:bold;font-size:12px;font-family:var(--bx-monospaced-font);flex-shrink:0}div.bx-select button.bx-button span{line-height:unset}.bx-select-indicators{display:flex;height:4px;gap:2px;margin-bottom:2px}.bx-select-indicators span{content:\' \';display:inline-block;flex:1;background:#cfcfcf;border-radius:4px}.bx-select-indicators span[data-highlighted]{background:#9c9c9c}.bx-select-indicators span[data-selected]{background:#aacfe7}.bx-select-indicators span[data-highlighted][data-selected]{background:#5fa3d0}.bx-guide-home-achievements-progress{display:flex;gap:10px;flex-direction:row}.bx-guide-home-achievements-progress .bx-button{margin-bottom:0 !important}body[data-bx-media-type=tv] .bx-guide-home-achievements-progress{flex-direction:column}body:not([data-bx-media-type=tv]) .bx-guide-home-achievements-progress{flex-direction:row}body:not([data-bx-media-type=tv]) .bx-guide-home-achievements-progress > button:first-of-type{flex:1}body:not([data-bx-media-type=tv]) .bx-guide-home-achievements-progress > button:last-of-type{width:40px}body:not([data-bx-media-type=tv]) .bx-guide-home-achievements-progress > button:last-of-type span{display:none}.bx-guide-home-buttons > div{display:flex;flex-direction:row;gap:12px}body[data-bx-media-type=tv] .bx-guide-home-buttons > div{flex-direction:column}body[data-bx-media-type=tv] .bx-guide-home-buttons > div button{margin-bottom:0 !important}body:not([data-bx-media-type=tv]) .bx-guide-home-buttons > div button span{display:none}.bx-guide-home-buttons[data-is-playing="true"] button[data-state=\'normal\']{display:none}.bx-guide-home-buttons[data-is-playing="false"] button[data-state=\'playing\']{display:none}div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module]{overflow:visible}.bx-stream-menu-button-on{fill:#000 !important;background-color:#2d2d2d !important;color:#000 !important}.bx-stream-refresh-button{top:calc(env(safe-area-inset-top, 0px) + 10px + 50px) !important}body[data-media-type=default] .bx-stream-refresh-button{left:calc(env(safe-area-inset-left, 0px) + 11px) !important}body[data-media-type=tv] .bx-stream-refresh-button{top:calc(var(--gds-focus-borderSize) + 80px) !important}.bx-stream-home-button{top:calc(env(safe-area-inset-top, 0px) + 10px + 50px * 2) !important}body[data-media-type=default] .bx-stream-home-button{left:calc(env(safe-area-inset-left, 0px) + 12px) !important}body[data-media-type=tv] .bx-stream-home-button{top:calc(var(--gds-focus-borderSize) + 80px * 2) !important}div[data-testid=media-container][data-position=center]{display:flex}div[data-testid=media-container][data-position=top] video,div[data-testid=media-container][data-position=top] canvas{top:0}div[data-testid=media-container][data-position=bottom] video,div[data-testid=media-container][data-position=bottom] canvas{bottom:0}#game-stream video{margin:auto;align-self:center;background:#000;position:absolute;left:0;right:0}#game-stream canvas{align-self:center;margin:auto;position:absolute;left:0;right:0}#game-stream.bx-taking-screenshot:before{animation:bx-anim-taking-screenshot .5s ease;content:\' \';position:absolute;width:100%;height:100%;z-index:var(--bx-screenshot-animation-z-index)}#gamepass-dialog-root div[class^=Guide-module__guide] .bx-button{overflow:visible;margin-bottom:12px}@-moz-keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}@-webkit-keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}@-o-keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}@keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}.bx-number-stepper{text-align:center}.bx-number-stepper > div{display:flex;align-items:center}.bx-number-stepper > div span{flex:1;display:inline-block;min-width:40px;font-family:var(--bx-monospaced-font);font-size:13px;margin:0 4px}.bx-number-stepper > div button{flex-shrink:0;border:none;width:24px;height:24px;margin:0;line-height:24px;background-color:var(--bx-default-button-color);color:#fff;border-radius:4px;font-weight:bold;font-size:14px;font-family:var(--bx-monospaced-font)}@media (hover:hover){.bx-number-stepper > div button:hover{background-color:var(--bx-default-button-hover-color)}}.bx-number-stepper > div button:active{background-color:var(--bx-default-button-hover-color)}.bx-number-stepper > div button:disabled + span{font-family:var(--bx-title-font)}.bx-number-stepper input[type="range"]{display:block;margin:8px 0 2px auto;min-width:180px;width:100%;color:#959595 !important}.bx-number-stepper input[type=range]:disabled,.bx-number-stepper button:disabled{display:none}.bx-number-stepper[data-disabled=true] input[type=range],.bx-number-stepper[disabled=true] input[type=range],.bx-number-stepper[data-disabled=true] button,.bx-number-stepper[disabled=true] button{display:none}#bx-game-bar{z-index:var(--bx-game-bar-z-index);position:fixed;bottom:0;width:40px;height:90px;overflow:visible;cursor:pointer}#bx-game-bar > svg{display:none;pointer-events:none;position:absolute;height:28px;margin-top:16px}@media (hover:hover){#bx-game-bar:hover > svg{display:block}}#bx-game-bar .bx-game-bar-container{opacity:0;position:absolute;display:flex;overflow:hidden;background:rgba(26,27,30,0.91);box-shadow:0 0 6px #1c1c1c;transition:opacity .1s ease-in}#bx-game-bar .bx-game-bar-container.bx-show{opacity:.9}#bx-game-bar .bx-game-bar-container.bx-show + svg{display:none !important}#bx-game-bar .bx-game-bar-container.bx-hide{opacity:0;pointer-events:none}#bx-game-bar .bx-game-bar-container button{width:60px;height:60px;border-radius:0}#bx-game-bar .bx-game-bar-container button svg{width:28px;height:28px;transition:transform .08s ease 0s}#bx-game-bar .bx-game-bar-container button:hover{border-radius:0}#bx-game-bar .bx-game-bar-container button:active svg{transform:scale(.75)}#bx-game-bar .bx-game-bar-container button.bx-activated{background-color:#fff}#bx-game-bar .bx-game-bar-container button.bx-activated svg{filter:invert(1)}#bx-game-bar .bx-game-bar-container div[data-activated] button{display:none}#bx-game-bar .bx-game-bar-container div[data-activated=\'false\'] button:first-of-type{display:block}#bx-game-bar .bx-game-bar-container div[data-activated=\'true\'] button:last-of-type{display:block}#bx-game-bar[data-position="bottom-left"]{left:0;direction:ltr}#bx-game-bar[data-position="bottom-left"] .bx-game-bar-container{border-radius:0 10px 10px 0}#bx-game-bar[data-position="bottom-right"]{right:0;direction:rtl}#bx-game-bar[data-position="bottom-right"] .bx-game-bar-container{direction:ltr;border-radius:10px 0 0 10px}.bx-badges{margin-left:0;user-select:none;-webkit-user-select:none}.bx-badge{border:none;display:inline-block;line-height:24px;color:#fff;font-family:var(--bx-title-font-semibold);font-size:14px;font-weight:400;margin:0 8px 8px 0;box-shadow:0 0 6px #000;border-radius:4px}.bx-badge-name{background-color:#2d3036;border-radius:4px 0 0 4px}.bx-badge-name svg{width:16px;height:16px}.bx-badge-value{background-color:#808080;border-radius:0 4px 4px 0}.bx-badge-name,.bx-badge-value{display:inline-block;padding:0 8px;line-height:30px;vertical-align:bottom}.bx-badge-battery[data-charging=true] span:first-of-type::after{content:\' ⚡️\'}div[class^=StreamMenu-module__container] .bx-badges{position:absolute;max-width:500px}#gamepass-dialog-root .bx-badges{position:fixed;top:60px;left:460px;max-width:500px}@media (min-width:568px) and (max-height:480px){#gamepass-dialog-root .bx-badges{position:unset;top:unset;left:unset;margin:8px 0}}.bx-stats-bar{display:flex;flex-direction:row;gap:8px;user-select:none;-webkit-user-select:none;position:fixed;top:0;background-color:#000;color:#fff;font-family:var(--bx-monospaced-font);font-size:.9rem;padding-left:8px;z-index:var(--bx-stats-bar-z-index);text-wrap:nowrap}.bx-stats-bar[data-stats*="[time]"] > .bx-stat-time,.bx-stats-bar[data-stats*="[play]"] > .bx-stat-play,.bx-stats-bar[data-stats*="[batt]"] > .bx-stat-batt,.bx-stats-bar[data-stats*="[fps]"] > .bx-stat-fps,.bx-stats-bar[data-stats*="[ping]"] > .bx-stat-ping,.bx-stats-bar[data-stats*="[jit]"] > .bx-stat-jit,.bx-stats-bar[data-stats*="[btr]"] > .bx-stat-btr,.bx-stats-bar[data-stats*="[dt]"] > .bx-stat-dt,.bx-stats-bar[data-stats*="[pl]"] > .bx-stat-pl,.bx-stats-bar[data-stats*="[fl]"] > .bx-stat-fl,.bx-stats-bar[data-stats*="[dl]"] > .bx-stat-dl,.bx-stats-bar[data-stats*="[ul]"] > .bx-stat-ul{display:inline-flex;align-items:baseline}.bx-stats-bar[data-stats$="[time]"] > .bx-stat-time,.bx-stats-bar[data-stats$="[play]"] > .bx-stat-play,.bx-stats-bar[data-stats$="[batt]"] > .bx-stat-batt,.bx-stats-bar[data-stats$="[fps]"] > .bx-stat-fps,.bx-stats-bar[data-stats$="[ping]"] > .bx-stat-ping,.bx-stats-bar[data-stats$="[jit]"] > .bx-stat-jit,.bx-stats-bar[data-stats$="[btr]"] > .bx-stat-btr,.bx-stats-bar[data-stats$="[dt]"] > .bx-stat-dt,.bx-stats-bar[data-stats$="[pl]"] > .bx-stat-pl,.bx-stats-bar[data-stats$="[fl]"] > .bx-stat-fl,.bx-stats-bar[data-stats$="[dl]"] > .bx-stat-dl,.bx-stats-bar[data-stats$="[ul]"] > .bx-stat-ul{border-right:none}.bx-stats-bar::before{display:none;content:\'👀\';vertical-align:middle;margin-right:8px}.bx-stats-bar[data-display=glancing]::before{display:inline-block}.bx-stats-bar[data-position=top-left]{left:0;border-radius:0 0 4px 0}.bx-stats-bar[data-position=top-right]{right:0;border-radius:0 0 0 4px}.bx-stats-bar[data-position=top-center]{transform:translate(-50%,0);left:50%;border-radius:0 0 4px 4px}.bx-stats-bar[data-shadow=true]{background:none;filter:drop-shadow(1px 0 0 rgba(0,0,0,0.941)) drop-shadow(-1px 0 0 rgba(0,0,0,0.941)) drop-shadow(0 1px 0 rgba(0,0,0,0.941)) drop-shadow(0 -1px 0 rgba(0,0,0,0.941))}.bx-stats-bar > div{display:none;border-right:1px solid #fff;padding-right:8px}.bx-stats-bar label{margin:0 8px 0 0;font-family:var(--bx-title-font);font-size:70%;font-weight:bold;vertical-align:middle;cursor:help}.bx-stats-bar span{display:inline-block;text-align:right;vertical-align:middle;white-space:pre}.bx-stats-bar span[data-grade=good]{color:#6bffff}.bx-stats-bar span[data-grade=ok]{color:#fff16b}.bx-stats-bar span[data-grade=bad]{color:#ff5f5f}.bx-mkb-settings{display:flex;flex-direction:column;flex:1;padding-bottom:10px;overflow:hidden}.bx-mkb-pointer-lock-msg{user-select:none;-webkit-user-select:none;position:fixed;left:50%;bottom:40px;transform:translateX(-50%);margin:auto;background:#151515;z-index:var(--bx-mkb-pointer-lock-msg-z-index);color:#fff;font-weight:400;font-family:"Segoe UI",Arial,Helvetica,sans-serif;font-size:1.3rem;padding:12px;border-radius:8px;align-items:center;box-shadow:0 0 6px #000;min-width:300px;opacity:.9;display:flex;flex-direction:column;gap:10px}.bx-mkb-pointer-lock-msg:hover{opacity:1}.bx-mkb-pointer-lock-msg > p{margin:0;width:100%;font-size:22px;margin-bottom:4px;font-weight:bold;text-align:left}.bx-mkb-pointer-lock-msg > div{width:100%;display:flex;flex-direction:row;gap:10px}.bx-mkb-pointer-lock-msg > div button:first-of-type{flex-shrink:1}.bx-mkb-pointer-lock-msg > div button:last-of-type{flex-grow:1}.bx-mkb-key-row{display:flex;margin-bottom:10px;align-items:center;gap:20px}.bx-mkb-key-row label{margin-bottom:0;font-family:var(--bx-promptfont-font);font-size:32px;text-align:center}.bx-mkb-settings.bx-editing .bx-mkb-key-row button{background:#393939;border-radius:4px;border:none}.bx-mkb-settings.bx-editing .bx-mkb-key-row button:hover{background:#333;cursor:pointer}.bx-mkb-action-buttons > div{text-align:right;display:none}.bx-mkb-action-buttons button{margin-left:8px}.bx-mkb-settings:not(.bx-editing) .bx-mkb-action-buttons > div:first-child{display:block}.bx-mkb-settings.bx-editing .bx-mkb-action-buttons > div:last-child{display:block}.bx-mkb-note{display:block;margin:0 0 10px;font-size:12px;text-align:center}button.bx-binding-button{flex:1;min-height:38px;border:none;border-radius:4px;font-size:14px;color:#fff;display:flex;align-items:center;align-self:center;padding:0 6px}button.bx-binding-button:disabled{background:#131416;padding:0 8px}button.bx-binding-button:not(:disabled){border:2px solid transparent;border-top:none;border-bottom:4px solid #252525;background:#3b3b3b;cursor:pointer}button.bx-binding-button:not(:disabled):hover,button.bx-binding-button:not(:disabled).bx-focusable:focus{background:#20b217;border-bottom-color:#186c13}button.bx-binding-button:not(:disabled):active{background:#16900f;border-bottom:3px solid #0c4e08;border-left-width:2px;border-right-width:2px}button.bx-binding-button:not(:disabled).bx-focusable:focus::after{top:-6px;left:-8px;right:-8px;bottom:-10px}.bx-settings-row .bx-binding-button-wrapper button.bx-binding-button{min-width:60px}.bx-product-details-buttons{display:flex;gap:10px;flex-direction:row}.bx-product-details-buttons button{max-width:max-content;margin:10px 0 0 0;display:flex}@media (min-width:568px) and (max-height:480px){.bx-product-details-buttons{flex-direction:column}.bx-product-details-buttons button{margin:8px 0 0 10px}}', PREF_HIDE_SECTIONS = getPref("ui.hideSections"), selectorToHide = [];
+ let css = ':root{--bx-title-font:Bahnschrift,Arial,Helvetica,sans-serif;--bx-title-font-semibold:Bahnschrift Semibold,Arial,Helvetica,sans-serif;--bx-normal-font:"Segoe UI",Arial,Helvetica,sans-serif;--bx-monospaced-font:Consolas,"Courier New",Courier,monospace;--bx-promptfont-font:promptfont;--bx-button-height:40px;--bx-default-button-color:#2d3036;--bx-default-button-rgb:45,48,54;--bx-default-button-hover-color:#515863;--bx-default-button-hover-rgb:81,88,99;--bx-default-button-active-color:#222428;--bx-default-button-active-rgb:34,36,40;--bx-default-button-disabled-color:#8e8e8e;--bx-default-button-disabled-rgb:142,142,142;--bx-primary-button-color:#008746;--bx-primary-button-rgb:0,135,70;--bx-primary-button-hover-color:#04b358;--bx-primary-button-hover-rgb:4,179,88;--bx-primary-button-active-color:#044e2a;--bx-primary-button-active-rgb:4,78,42;--bx-primary-button-disabled-color:#448262;--bx-primary-button-disabled-rgb:68,130,98;--bx-warning-button-color:#c16e04;--bx-warning-button-rgb:193,110,4;--bx-warning-button-hover-color:#fa9005;--bx-warning-button-hover-rgb:250,144,5;--bx-warning-button-active-color:#965603;--bx-warning-button-active-rgb:150,86,3;--bx-warning-button-disabled-color:#a2816c;--bx-warning-button-disabled-rgb:162,129,108;--bx-danger-button-color:#c10404;--bx-danger-button-rgb:193,4,4;--bx-danger-button-hover-color:#e61d1d;--bx-danger-button-hover-rgb:230,29,29;--bx-danger-button-active-color:#a26c6c;--bx-danger-button-active-rgb:162,108,108;--bx-danger-button-disabled-color:#df5656;--bx-danger-button-disabled-rgb:223,86,86;--bx-fullscreen-text-z-index:9999;--bx-toast-z-index:6000;--bx-key-binding-dialog-z-index:5010;--bx-key-binding-dialog-overlay-z-index:5000;--bx-stats-bar-z-index:4010;--bx-navigation-dialog-z-index:3010;--bx-navigation-dialog-overlay-z-index:3000;--bx-mkb-pointer-lock-msg-z-index:2000;--bx-game-bar-z-index:1000;--bx-screenshot-animation-z-index:200;--bx-wait-time-box-z-index:100}@font-face{font-family:\'promptfont\';src:url("https://redphx.github.io/better-xcloud/fonts/promptfont.otf");unicode-range:U+2196-E011}div[class^=HUDButton-module__hiddenContainer] ~ div:not([class^=HUDButton-module__hiddenContainer]){opacity:0;pointer-events:none !important;position:absolute;top:-9999px;left:-9999px}@media screen and (max-width:640px){header a[href="/play"]{display:none}}.bx-full-width{width:100% !important}.bx-full-height{height:100% !important}.bx-auto-height{height:auto !important}.bx-no-scroll{overflow:hidden !important}.bx-hide-scroll-bar{scrollbar-width:none}.bx-hide-scroll-bar::-webkit-scrollbar{display:none}.bx-gone{display:none !important}.bx-offscreen{position:absolute !important;top:-9999px !important;left:-9999px !important;visibility:hidden !important}.bx-hidden{visibility:hidden !important}.bx-invisible{opacity:0}.bx-unclickable{pointer-events:none}.bx-pixel{width:1px !important;height:1px !important}.bx-no-margin{margin:0 !important}.bx-no-padding{padding:0 !important}.bx-prompt{font-family:var(--bx-promptfont-font) !important}.bx-monospaced{font-family:var(--bx-monospaced-font) !important}.bx-line-through{text-decoration:line-through !important}.bx-normal-case{text-transform:none !important}.bx-normal-link{text-transform:none !important;text-align:left !important;font-weight:400 !important;font-family:var(--bx-normal-font) !important}select[multiple],select[multiple]:focus{overflow:auto;border:none}select[multiple] option,select[multiple]:focus option{padding:4px 6px}select[multiple] option:checked,select[multiple]:focus option:checked{background:#1a7bc0 linear-gradient(0deg,#1a7bc0 0%,#1a7bc0 100%)}select[multiple] option:checked::before,select[multiple]:focus option:checked::before{content:\'☑️\';font-size:12px;display:inline-block;margin-right:6px;height:100%;line-height:100%;vertical-align:middle}#headerArea,#uhfSkipToMain,.uhf-footer{display:none}div[class*=NotFocusedDialog]{position:absolute !important;top:-9999px !important;left:-9999px !important;width:0 !important;height:0 !important}#game-stream video:not([src]){visibility:hidden}div[class*=SupportedInputsBadge]:not(:has(:nth-child(2))),div[class*=SupportedInputsBadge] svg:first-of-type{display:none}.bx-game-tile-wait-time{position:absolute;top:0;left:0;z-index:1;background:rgba(0,0,0,0.5);display:flex;border-radius:4px 0 4px 0;align-items:center;padding:4px 8px}.bx-game-tile-wait-time svg{width:14px;height:16px;margin-right:2px}.bx-game-tile-wait-time span{display:inline-block;height:16px;line-height:16px;font-size:12px;font-weight:bold;margin-left:2px}.bx-game-tile-wait-time[data-duration=short]{background-color:rgba(0,133,133,0.75)}.bx-game-tile-wait-time[data-duration=medium]{background-color:rgba(213,133,0,0.75)}.bx-game-tile-wait-time[data-duration=long]{background-color:rgba(150,0,0,0.75)}.bx-fullscreen-text{position:fixed;top:0;bottom:0;left:0;right:0;background:rgba(0,0,0,0.8);z-index:var(--bx-fullscreen-text-z-index);line-height:100vh;color:#fff;text-align:center;font-weight:400;font-family:var(--bx-normal-font);font-size:1.3rem;user-select:none;-webkit-user-select:none}#root section[class*=DeviceCodePage-module__page]{margin-left:20px !important;margin-right:20px !important;margin-top:20px !important;max-width:800px !important}#root div[class*=DeviceCodePage-module__back]{display:none}.bx-blink-me{animation:bx-blinker 1s linear infinite}.bx-horizontal-shaking{animation:bx-horizontal-shaking .4s ease-in-out 2}@-moz-keyframes bx-blinker{100%{opacity:0}}@-webkit-keyframes bx-blinker{100%{opacity:0}}@-o-keyframes bx-blinker{100%{opacity:0}}@keyframes bx-blinker{100%{opacity:0}}@-moz-keyframes bx-horizontal-shaking{0%{transform:translateX(0)}25%{transform:translateX(5px)}50%{transform:translateX(-5px)}75%{transform:translateX(5px)}100%{transform:translateX(0)}}@-webkit-keyframes bx-horizontal-shaking{0%{transform:translateX(0)}25%{transform:translateX(5px)}50%{transform:translateX(-5px)}75%{transform:translateX(5px)}100%{transform:translateX(0)}}@-o-keyframes bx-horizontal-shaking{0%{transform:translateX(0)}25%{transform:translateX(5px)}50%{transform:translateX(-5px)}75%{transform:translateX(5px)}100%{transform:translateX(0)}}@keyframes bx-horizontal-shaking{0%{transform:translateX(0)}25%{transform:translateX(5px)}50%{transform:translateX(-5px)}75%{transform:translateX(5px)}100%{transform:translateX(0)}}.bx-button{--button-rgb:var(--bx-default-button-rgb);--button-hover-rgb:var(--bx-default-button-hover-rgb);--button-active-rgb:var(--bx-default-button-active-rgb);--button-disabled-rgb:var(--bx-default-button-disabled-rgb);background-color:rgb(var(--button-rgb));user-select:none;-webkit-user-select:none;color:#fff;font-family:var(--bx-title-font-semibold);font-size:14px;border:none;font-weight:400;height:var(--bx-button-height);border-radius:4px;padding:0 8px;text-transform:uppercase;cursor:pointer;overflow:hidden}.bx-button:not([disabled]):active{background-color:rgb(var(--button-active-rgb))}.bx-button:focus{outline:none !important}.bx-button:not([disabled]):not(:active):hover,.bx-button:not([disabled]):not(:active).bx-focusable:focus{background-color:rgb(var(--button-hover-rgb))}.bx-button:disabled{cursor:default;background-color:rgb(var(--button-disabled-rgb))}.bx-button.bx-ghost{background-color:transparent}.bx-button.bx-ghost:not([disabled]):not(:active):hover,.bx-button.bx-ghost:not([disabled]):not(:active).bx-focusable:focus{background-color:rgb(var(--button-hover-rgb))}.bx-button.bx-primary{--button-rgb:var(--bx-primary-button-rgb)}.bx-button.bx-primary:not([disabled]):active{--button-active-rgb:var(--bx-primary-button-active-rgb)}.bx-button.bx-primary:not([disabled]):not(:active):hover,.bx-button.bx-primary:not([disabled]):not(:active).bx-focusable:focus{--button-hover-rgb:var(--bx-primary-button-hover-rgb)}.bx-button.bx-primary:disabled{--button-disabled-rgb:var(--bx-primary-button-disabled-rgb)}.bx-button.bx-warning{--button-rgb:var(--bx-warning-button-rgb)}.bx-button.bx-warning:not([disabled]):active{--button-active-rgb:var(--bx-warning-button-active-rgb)}.bx-button.bx-warning:not([disabled]):not(:active):hover,.bx-button.bx-warning:not([disabled]):not(:active).bx-focusable:focus{--button-hover-rgb:var(--bx-warning-button-hover-rgb)}.bx-button.bx-warning:disabled{--button-disabled-rgb:var(--bx-warning-button-disabled-rgb)}.bx-button.bx-danger{--button-rgb:var(--bx-danger-button-rgb)}.bx-button.bx-danger:not([disabled]):active{--button-active-rgb:var(--bx-danger-button-active-rgb)}.bx-button.bx-danger:not([disabled]):not(:active):hover,.bx-button.bx-danger:not([disabled]):not(:active).bx-focusable:focus{--button-hover-rgb:var(--bx-danger-button-hover-rgb)}.bx-button.bx-danger:disabled{--button-disabled-rgb:var(--bx-danger-button-disabled-rgb)}.bx-button.bx-frosted{--button-alpha:.2;background-color:rgba(var(--button-rgb), var(--button-alpha));backdrop-filter:blur(4px) brightness(1.5)}.bx-button.bx-frosted:not([disabled]):not(:active):hover,.bx-button.bx-frosted:not([disabled]):not(:active).bx-focusable:focus{background-color:rgba(var(--button-hover-rgb), var(--button-alpha))}.bx-button.bx-drop-shadow{box-shadow:0 0 4px rgba(0,0,0,0.502)}.bx-button.bx-tall{height:calc(var(--bx-button-height) * 1.5) !important}.bx-button.bx-circular{border-radius:var(--bx-button-height);width:var(--bx-button-height);height:var(--bx-button-height)}.bx-button svg{display:inline-block;width:16px;height:var(--bx-button-height)}.bx-button span{display:inline-block;line-height:var(--bx-button-height);vertical-align:middle;color:#fff;overflow:hidden;white-space:nowrap}.bx-button span:not(:only-child){margin-left:10px}.bx-button.bx-button-multi-lines{height:auto;text-align:left;padding:10px 0}.bx-button.bx-button-multi-lines span{line-height:unset;display:block}.bx-button.bx-button-multi-lines span:last-of-type{text-transform:none;font-weight:normal;font-family:"Segoe Sans Variable Text";font-size:12px;margin-top:4px}.bx-focusable{position:relative;overflow:visible}.bx-focusable::after{border:2px solid transparent;border-radius:10px}.bx-focusable:focus::after{content:\'\';border-color:#fff;position:absolute;top:-6px;left:-6px;right:-6px;bottom:-6px}html[data-active-input=touch] .bx-focusable:focus::after,html[data-active-input=mouse] .bx-focusable:focus::after{border-color:transparent !important}.bx-focusable.bx-circular::after{border-radius:var(--bx-button-height)}a.bx-button{display:inline-block}a.bx-button.bx-full-width{text-align:center}button.bx-inactive{pointer-events:none;opacity:.2;background:transparent !important}.bx-header-remote-play-button{height:auto;margin-right:8px !important}.bx-header-remote-play-button svg{width:24px;height:24px}.bx-header-settings-button{line-height:30px;font-size:14px;text-transform:uppercase;position:relative}.bx-header-settings-button[data-update-available]::before{content:\'🌟\' !important;line-height:var(--bx-button-height);display:inline-block;margin-left:4px}.bx-key-binding-dialog-overlay{position:fixed;inset:0;z-index:var(--bx-key-binding-dialog-overlay-z-index);background:#000;opacity:50%}.bx-key-binding-dialog{display:flex;flex-flow:column;max-height:90vh;position:fixed;top:50%;left:50%;margin-right:-50%;transform:translate(-50%,-50%);min-width:420px;padding:16px;border-radius:8px;z-index:var(--bx-key-binding-dialog-z-index);background:#1a1b1e;color:#fff;font-weight:400;font-size:16px;font-family:var(--bx-normal-font);box-shadow:0 0 6px #000;user-select:none;-webkit-user-select:none}.bx-key-binding-dialog *:focus{outline:none !important}.bx-key-binding-dialog h2{margin-bottom:12px;color:#fff;display:block;font-family:var(--bx-title-font);font-size:32px;font-weight:400;line-height:var(--bx-button-height)}.bx-key-binding-dialog > div{overflow:auto;padding:2px 0}.bx-key-binding-dialog > button{padding:8px 32px;margin:10px auto 0;border:none;border-radius:4px;display:block;background-color:#2d3036;text-align:center;color:#fff;text-transform:uppercase;font-family:var(--bx-title-font);font-weight:400;line-height:18px;font-size:14px}@media (hover:hover){.bx-key-binding-dialog > button:hover{background-color:#515863}}.bx-key-binding-dialog > button:focus{background-color:#515863}.bx-key-binding-dialog ul{margin-bottom:1rem}.bx-key-binding-dialog ul li{display:none}.bx-key-binding-dialog ul[data-flags*="[1]"] > li[data-flag="1"],.bx-key-binding-dialog ul[data-flags*="[2]"] > li[data-flag="2"],.bx-key-binding-dialog ul[data-flags*="[4]"] > li[data-flag="4"],.bx-key-binding-dialog ul[data-flags*="[8]"] > li[data-flag="8"]{display:list-item}@media screen and (max-width:450px){.bx-key-binding-dialog{min-width:100%}}.bx-navigation-dialog{position:absolute;z-index:var(--bx-navigation-dialog-z-index);font-family:var(--bx-title-font)}.bx-navigation-dialog *:focus{outline:none !important}.bx-navigation-dialog select:disabled{-webkit-appearance:none;text-align-last:right;text-align:right;color:#fff;background:#131416;border:none;border-radius:4px;padding:0 5px}.bx-navigation-dialog .bx-focusable::after{border-radius:4px}.bx-navigation-dialog .bx-focusable:focus::after{top:0;left:0;right:0;bottom:0}.bx-navigation-dialog-overlay{position:fixed;background:rgba(11,11,11,0.89);top:0;left:0;right:0;bottom:0;z-index:var(--bx-navigation-dialog-overlay-z-index)}.bx-navigation-dialog-overlay[data-is-playing="true"]{background:transparent}.bx-centered-dialog{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);color:#fff;background:#1a1b1e;border-radius:10px;min-width:min(calc(100vw - 20px), 500px);max-width:calc(100vw - 20px);margin:0 0 0 auto;padding:16px;max-height:95vh;flex-direction:column;overflow:hidden;display:flex;flex-direction:column}.bx-centered-dialog .bx-dialog-title{display:flex;flex-direction:row;align-items:center;margin-bottom:10px}.bx-centered-dialog .bx-dialog-title p{padding:0;margin:0;flex:1;font-size:1.2rem;font-weight:bold}.bx-centered-dialog .bx-dialog-title button{flex-shrink:0}.bx-centered-dialog .bx-dialog-content{flex:1;padding:6px;overflow:auto;overflow-x:hidden}.bx-centered-dialog .bx-dialog-preset-tools{display:flex;margin-bottom:12px;gap:6px}.bx-centered-dialog .bx-dialog-preset-tools button{align-self:center;min-height:50px}.bx-centered-dialog .bx-default-preset-note{font-size:12px;font-style:italic;text-align:center;margin-bottom:10px}.bx-centered-dialog input,.bx-settings-dialog input{accent-color:var(--bx-primary-button-color)}.bx-centered-dialog input:focus,.bx-settings-dialog input:focus{accent-color:var(--bx-danger-button-color)}.bx-centered-dialog select:disabled,.bx-settings-dialog select:disabled{-webkit-appearance:none;background:transparent;text-align-last:right;border:none;color:#fff}.bx-centered-dialog select option:disabled,.bx-settings-dialog select option:disabled{display:none}.bx-centered-dialog input[type=checkbox]:focus,.bx-settings-dialog input[type=checkbox]:focus,.bx-centered-dialog select:focus,.bx-settings-dialog select:focus{filter:drop-shadow(1px 0 0 #fff) drop-shadow(-1px 0 0 #fff) drop-shadow(0 1px 0 #fff) drop-shadow(0 -1px 0 #fff)}.bx-centered-dialog a,.bx-settings-dialog a{color:#1c9d1c;text-decoration:none}.bx-centered-dialog a:hover,.bx-settings-dialog a:hover,.bx-centered-dialog a:focus,.bx-settings-dialog a:focus{color:#5dc21e}.bx-centered-dialog label,.bx-settings-dialog label{margin:0}.bx-controller-shortcuts-manager-container .bx-shortcut-note{margin-top:10px;font-size:14px;text-align:center}.bx-controller-shortcuts-manager-container .bx-shortcut-row{display:flex;gap:10px;margin-bottom:10px;align-items:center}.bx-controller-shortcuts-manager-container .bx-shortcut-row label.bx-prompt{flex-shrink:0;font-size:32px;margin:0}.bx-controller-shortcuts-manager-container .bx-shortcut-row label.bx-prompt::first-letter{letter-spacing:6px}.bx-controller-shortcuts-manager-container select:disabled{text-align:left;text-align-last:left}.bx-keyboard-shortcuts-manager-container{display:flex;flex-direction:column;gap:16px}.bx-keyboard-shortcuts-manager-container fieldset{background:#2a2a2a;border:1px solid #2a2a2a;border-radius:4px;padding:4px}.bx-keyboard-shortcuts-manager-container legend{width:auto;padding:4px 8px;margin:0 4px 4px;background:#004f87;box-shadow:0 2px 0 #071e3d;border-radius:4px;font-size:14px;font-weight:bold;text-transform:uppercase}.bx-keyboard-shortcuts-manager-container .bx-settings-row{background:none;padding:10px}.bx-settings-dialog{display:flex;position:fixed;top:0;right:0;bottom:0;opacity:.98;user-select:none;-webkit-user-select:none}.bx-settings-dialog .bx-settings-reload-note{font-size:.8rem;display:block;padding:8px;font-style:italic;font-weight:normal;height:var(--bx-button-height)}.bx-settings-tabs-container{position:fixed;width:48px;max-height:100vh;display:flex;flex-direction:column}.bx-settings-tabs-container > div:last-of-type{display:flex;flex-direction:column;align-items:end}.bx-settings-tabs-container > div:last-of-type button{flex-shrink:0;border-top-right-radius:0;border-bottom-right-radius:0;margin-top:8px;height:unset;padding:8px 10px}.bx-settings-tabs-container > div:last-of-type button svg{width:16px;height:16px}.bx-settings-tabs{display:flex;flex-direction:column;border-radius:0 0 0 8px;box-shadow:0 0 6px #000;overflow:overlay;flex:1}.bx-settings-tabs svg{width:24px;height:24px;padding:10px;flex-shrink:0;box-sizing:content-box;background:#131313;cursor:pointer;border-left:4px solid #1e1e1e}.bx-settings-tabs svg.bx-active{background:#222;border-color:#008746}.bx-settings-tabs svg:not(.bx-active):hover{background:#2f2f2f;border-color:#484848}.bx-settings-tabs svg:focus{border-color:#fff}.bx-settings-tabs svg[data-group=global][data-need-refresh=true]{background:var(--bx-danger-button-color) !important}.bx-settings-tabs svg[data-group=global][data-need-refresh=true]:hover{background:var(--bx-danger-button-hover-color) !important}.bx-settings-tab-contents{flex-direction:column;padding:10px;margin-left:48px;width:450px;max-width:calc(100vw - tabsWidth);background:#1a1b1e;color:#fff;font-weight:400;font-size:16px;font-family:var(--bx-title-font);text-align:center;box-shadow:0 0 6px #000;overflow:overlay;z-index:1}.bx-settings-tab-contents > div[data-tab-group=mkb]{display:flex;flex-direction:column;height:100%;overflow:hidden}.bx-settings-tab-contents .bx-top-buttons{display:flex;flex-direction:column;gap:8px;margin-bottom:8px}.bx-settings-tab-contents .bx-top-buttons .bx-button{display:block}.bx-settings-tab-contents h2{margin:16px 0 8px 0;display:flex;align-items:center}.bx-settings-tab-contents h2:first-of-type{margin-top:0}.bx-settings-tab-contents h2 span{display:inline-block;font-size:20px;font-weight:bold;text-align:left;flex:1;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;min-height:var(--bx-button-height);align-content:center}@media (max-width:500px){.bx-settings-tab-contents{width:calc(100vw - 48px)}}.bx-settings-row{display:flex;gap:10px;padding:16px 10px;margin:0;background:#2a2a2a;border-bottom:1px solid #343434}.bx-settings-row:hover,.bx-settings-row:focus-within{background-color:#242424}.bx-settings-row:not(:has(> input[type=checkbox])){flex-wrap:wrap}.bx-settings-row > span.bx-settings-label{font-size:14px;display:block;text-align:left;align-self:center;margin-bottom:0 !important;flex:1}.bx-settings-row > span.bx-settings-label + *{margin:0 0 0 auto}.bx-settings-row[data-multi-lines="true"]{flex-direction:column}.bx-settings-row[data-multi-lines="true"] > span.bx-settings-label{align-self:start}.bx-settings-row[data-multi-lines="true"] > span.bx-settings-label + *{margin:unset}.bx-settings-dialog-note{display:block;color:#afafb0;font-size:12px;font-weight:lighter;font-style:italic}.bx-settings-dialog-note:not(:has(a)){margin-top:4px}.bx-settings-dialog-note a{display:inline-block;padding:4px}.bx-settings-custom-user-agent{display:block;width:100%;padding:6px}.bx-donation-link{display:block;text-align:center;text-decoration:none;height:20px;line-height:20px;font-size:14px;margin-top:10px;margin-bottom:10px}.bx-debug-info button{margin-top:10px}.bx-debug-info pre{margin-top:10px;cursor:copy;color:#fff;padding:8px;border:1px solid #2d2d2d;background:#212121;white-space:break-spaces;text-align:left}.bx-debug-info pre:hover{background:#272727}.bx-settings-app-version{margin-top:10px;text-align:center;color:#747474;font-size:12px}.bx-note-unsupported{display:block;font-size:12px;font-style:italic;font-weight:normal;color:#828282}.bx-settings-tab-contents > div *:not(.bx-settings-row):has(+ .bx-settings-row) + .bx-settings-row:has(+ .bx-settings-row){border-top-left-radius:6px;border-top-right-radius:6px}.bx-settings-tab-contents > div .bx-settings-row:not(:has(+ .bx-settings-row)){border:none;border-bottom-left-radius:6px;border-bottom-right-radius:6px}.bx-settings-tab-contents > div *:not(.bx-settings-row):has(+ .bx-settings-row) + .bx-settings-row:not(:has(+ .bx-settings-row)){border:none;border-radius:6px}.bx-suggest-toggler{text-align:left;display:flex;border-radius:4px;overflow:hidden;background:#003861;height:45px;align-items:center}.bx-suggest-toggler label{flex:1;align-content:center;padding:0 10px;background:#004f87;height:100%}.bx-suggest-toggler span{display:inline-block;align-self:center;padding:10px;width:45px;text-align:center}.bx-suggest-toggler:hover,.bx-suggest-toggler:focus{cursor:pointer;background:#005da1}.bx-suggest-toggler:hover label,.bx-suggest-toggler:focus label{background:#006fbe}.bx-suggest-toggler[bx-open] span{transform:rotate(90deg)}.bx-suggest-toggler[bx-open]+ .bx-suggest-box{display:block}.bx-suggest-box{display:none}.bx-suggest-wrapper{display:flex;flex-direction:column;gap:10px;margin:10px}.bx-suggest-note{font-size:11px;color:#8c8c8c;font-style:italic;font-weight:100}.bx-suggest-link{font-size:14px;display:inline-block;margin-top:4px;padding:4px}.bx-suggest-row{display:flex;flex-direction:row;gap:10px}.bx-suggest-row label{flex:1;overflow:overlay;border-radius:4px}.bx-suggest-row label .bx-suggest-label{background:#323232;padding:4px 10px;font-size:12px;text-align:left}.bx-suggest-row label .bx-suggest-value{padding:6px;font-size:14px}.bx-suggest-row label .bx-suggest-value.bx-suggest-change{background-color:var(--bx-warning-color)}.bx-suggest-row.bx-suggest-ok input{visibility:hidden}.bx-suggest-row.bx-suggest-ok .bx-suggest-label{background-color:#008114}.bx-suggest-row.bx-suggest-ok .bx-suggest-value{background-color:#13a72a}.bx-suggest-row.bx-suggest-change .bx-suggest-label{background-color:#a65e08}.bx-suggest-row.bx-suggest-change .bx-suggest-value{background-color:#d57f18}.bx-suggest-row.bx-suggest-change:hover label{cursor:pointer}.bx-suggest-row.bx-suggest-change:hover .bx-suggest-label{background-color:#995707}.bx-suggest-row.bx-suggest-change:hover .bx-suggest-value{background-color:#bd7115}.bx-suggest-row.bx-suggest-change input:not(:checked) + label{opacity:.5}.bx-suggest-row.bx-suggest-change input:not(:checked) + label .bx-suggest-label{background-color:#2a2a2a}.bx-suggest-row.bx-suggest-change input:not(:checked) + label .bx-suggest-value{background-color:#393939}.bx-suggest-row.bx-suggest-change:hover input:not(:checked) + label{opacity:1}.bx-suggest-row.bx-suggest-change:hover input:not(:checked) + label .bx-suggest-label{background-color:#202020}.bx-suggest-row.bx-suggest-change:hover input:not(:checked) + label .bx-suggest-value{background-color:#303030}.bx-sub-content-box{background:#161616;padding:10px;box-shadow:0 0 12px #0f0f0f inset;border-radius:10px}.bx-settings-row .bx-sub-content-box{background:#202020;padding:12px;box-shadow:0 0 4px #000 inset;border-radius:6px}.bx-controller-extra-settings[data-has-gamepad=true] > :first-child{display:none}.bx-controller-extra-settings[data-has-gamepad=true] > :last-child{display:block}.bx-controller-extra-settings[data-has-gamepad=false] > :first-child{display:block}.bx-controller-extra-settings[data-has-gamepad=false] > :last-child{display:none}.bx-controller-extra-settings .bx-controller-extra-wrapper{flex:1;min-width:1px}.bx-controller-extra-settings .bx-sub-content-box{flex:1;text-align:left;display:flex;flex-direction:column;margin-top:10px}.bx-controller-extra-settings .bx-sub-content-box > label{font-size:14px}.bx-preset-row{display:flex;gap:8px}.bx-preset-row .bx-select{flex:1}.bx-toast{user-select:none;-webkit-user-select:none;position:fixed;left:50%;top:24px;transform:translate(-50%,0);background:#000;border-radius:16px;color:#fff;z-index:var(--bx-toast-z-index);font-family:var(--bx-normal-font);border:2px solid #fff;display:flex;align-items:center;opacity:0;overflow:clip;transition:opacity .2s ease-in}.bx-toast.bx-show{opacity:.85}.bx-toast.bx-hide{opacity:0;pointer-events:none}.bx-toast-msg{font-size:14px;display:inline-block;padding:12px 16px;white-space:pre}.bx-toast-status{font-weight:bold;font-size:14px;text-transform:uppercase;display:inline-block;background:#515863;padding:12px 16px;color:#fff;white-space:pre}.bx-wait-time-box{position:fixed;top:0;right:0;background-color:rgba(0,0,0,0.8);color:#fff;z-index:var(--bx-wait-time-box-z-index);padding:12px;border-radius:0 0 0 8px}.bx-wait-time-box label{display:block;text-transform:uppercase;text-align:right;font-size:12px;font-weight:bold;margin:0}.bx-wait-time-box span{display:block;font-family:var(--bx-monospaced-font);text-align:right;font-size:16px;margin-bottom:10px}.bx-wait-time-box span:last-of-type{margin-bottom:0}.bx-remote-play-container{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);color:#fff;background:#1a1b1e;border-radius:10px;width:420px;max-width:calc(100vw - 20px);margin:0 0 0 auto;padding:16px}.bx-remote-play-container > .bx-button{display:table;margin:0 0 0 auto}.bx-remote-play-settings{margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid #2d2d2d}.bx-remote-play-settings > div{display:flex}.bx-remote-play-settings label{flex:1}.bx-remote-play-settings label p{margin:4px 0 0;padding:0;color:#888;font-size:12px}.bx-remote-play-resolution{display:block}.bx-remote-play-resolution input[type="radio"]{accent-color:var(--bx-primary-button-color);margin-right:6px}.bx-remote-play-resolution input[type="radio"]:focus{accent-color:var(--bx-primary-button-hover-color)}.bx-remote-play-device-wrapper{display:flex;margin-bottom:12px}.bx-remote-play-device-wrapper:last-child{margin-bottom:2px}.bx-remote-play-device-info{flex:1;padding:4px 0}.bx-remote-play-device-name{font-size:20px;font-weight:bold;display:inline-block;vertical-align:middle}.bx-remote-play-console-type{font-size:12px;background:#004c87;color:#fff;display:inline-block;border-radius:14px;padding:2px 10px;margin-left:8px;vertical-align:middle}.bx-remote-play-power-state{color:#888;font-size:12px}.bx-remote-play-connect-button{min-height:100%;margin:4px 0}.bx-remote-play-buttons{display:flex;justify-content:space-between}select.bx-select{min-height:30px}div.bx-select{display:flex;align-items:stretch;flex:0 1 auto;gap:8px}div.bx-select select:disabled ~ button{display:none}div.bx-select select:disabled ~ div{background:#131416;color:#fff;pointer-events:none}div.bx-select select:disabled ~ div .bx-select-indicators{visibility:hidden}div.bx-select > div,div.bx-select button.bx-select-value{min-width:120px;text-align:left;line-height:24px;vertical-align:middle;background:#fff;color:#000;border-radius:4px;padding:2px 8px;display:flex;flex:1;flex-direction:column}div.bx-select > div{min-height:24px}div.bx-select > div input{display:inline-block;margin-right:8px}div.bx-select > div label{margin-bottom:0;font-size:14px;width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;min-height:15px}div.bx-select > div label span{display:block;font-size:10px;font-weight:bold;text-align:left;line-height:20px;white-space:pre;min-height:15px;align-content:center}div.bx-select button.bx-select-value{border:none;cursor:pointer;min-height:30px;font-size:.9rem;align-items:center}div.bx-select button.bx-select-value > div{display:flex;width:100%}div.bx-select button.bx-select-value span{flex:1;text-align:left;display:inline-block}div.bx-select button.bx-select-value input{margin:0 4px;accent-color:var(--bx-primary-button-color);pointer-events:none}div.bx-select button.bx-select-value:hover input,div.bx-select button.bx-select-value:focus input{accent-color:var(--bx-danger-button-color)}div.bx-select button.bx-select-value:hover::after,div.bx-select button.bx-select-value:focus::after{border-color:#4d4d4d !important}div.bx-select button.bx-button{border:none;width:24px;height:auto;padding:0;color:#fff;border-radius:4px;font-weight:bold;font-size:12px;font-family:var(--bx-monospaced-font);flex-shrink:0}div.bx-select button.bx-button span{line-height:unset}div.bx-select[data-controller-friendly=true] > div{box-sizing:content-box}div.bx-select[data-controller-friendly=true] select{position:absolute !important;top:-9999px !important;left:-9999px !important;visibility:hidden !important}div.bx-select[data-controller-friendly=false]{position:relative}div.bx-select[data-controller-friendly=false] > div{box-sizing:border-box}div.bx-select[data-controller-friendly=false] > div label{margin-right:24px}div.bx-select[data-controller-friendly=false] select:disabled{display:none}div.bx-select[data-controller-friendly=false] select:not(:disabled){cursor:pointer;position:absolute;top:0;right:0;bottom:0;display:block;opacity:0;z-index:calc(var(--bx-settings-z-index) + 1)}div.bx-select[data-controller-friendly=false] select:not(:disabled):hover + div{background:#f0f0f0}div.bx-select[data-controller-friendly=false] select:not(:disabled) + div label::after{content:\'▾\';font-size:14px;position:absolute;right:8px;pointer-events:none}.bx-select-indicators{display:flex;height:4px;gap:2px;margin-bottom:2px}.bx-select-indicators span{content:\' \';display:inline-block;flex:1;background:#cfcfcf;border-radius:4px;min-width:1px}.bx-select-indicators span[data-highlighted]{background:#9c9c9c;min-width:6px}.bx-select-indicators span[data-selected]{background:#aacfe7}.bx-select-indicators span[data-highlighted][data-selected]{background:#5fa3d0}.bx-guide-home-achievements-progress{display:flex;gap:10px;flex-direction:row}.bx-guide-home-achievements-progress .bx-button{margin-bottom:0 !important}body[data-bx-media-type=tv] .bx-guide-home-achievements-progress{flex-direction:column}body:not([data-bx-media-type=tv]) .bx-guide-home-achievements-progress{flex-direction:row}body:not([data-bx-media-type=tv]) .bx-guide-home-achievements-progress > button:first-of-type{flex:1}body:not([data-bx-media-type=tv]) .bx-guide-home-achievements-progress > button:last-of-type{width:40px}body:not([data-bx-media-type=tv]) .bx-guide-home-achievements-progress > button:last-of-type span{display:none}.bx-guide-home-buttons > div{display:flex;flex-direction:row;gap:12px}body[data-bx-media-type=tv] .bx-guide-home-buttons > div{flex-direction:column}body[data-bx-media-type=tv] .bx-guide-home-buttons > div button{margin-bottom:0 !important}body:not([data-bx-media-type=tv]) .bx-guide-home-buttons > div button span{display:none}.bx-guide-home-buttons[data-is-playing="true"] button[data-state=\'normal\']{display:none}.bx-guide-home-buttons[data-is-playing="false"] button[data-state=\'playing\']{display:none}div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module]{overflow:visible}.bx-stream-menu-button-on{fill:#000 !important;background-color:#2d2d2d !important;color:#000 !important}.bx-stream-refresh-button{top:calc(env(safe-area-inset-top, 0px) + 10px + 50px) !important}body[data-media-type=default] .bx-stream-refresh-button{left:calc(env(safe-area-inset-left, 0px) + 11px) !important}body[data-media-type=tv] .bx-stream-refresh-button{top:calc(var(--gds-focus-borderSize) + 80px) !important}.bx-stream-home-button{top:calc(env(safe-area-inset-top, 0px) + 10px + 50px * 2) !important}body[data-media-type=default] .bx-stream-home-button{left:calc(env(safe-area-inset-left, 0px) + 12px) !important}body[data-media-type=tv] .bx-stream-home-button{top:calc(var(--gds-focus-borderSize) + 80px * 2) !important}div[data-testid=media-container][data-position=center]{display:flex}div[data-testid=media-container][data-position=top] video,div[data-testid=media-container][data-position=top] canvas{top:0}div[data-testid=media-container][data-position=bottom] video,div[data-testid=media-container][data-position=bottom] canvas{bottom:0}#game-stream video{margin:auto;align-self:center;background:#000;position:absolute;left:0;right:0}#game-stream canvas{align-self:center;margin:auto;position:absolute;left:0;right:0}#game-stream.bx-taking-screenshot:before{animation:bx-anim-taking-screenshot .5s ease;content:\' \';position:absolute;width:100%;height:100%;z-index:var(--bx-screenshot-animation-z-index)}#gamepass-dialog-root div[class^=Guide-module__guide] .bx-button{overflow:visible;margin-bottom:12px}@-moz-keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}@-webkit-keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}@-o-keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}@keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}.bx-number-stepper{text-align:center}.bx-number-stepper > div{display:flex;align-items:center}.bx-number-stepper > div span{flex:1;display:inline-block;min-width:40px;font-family:var(--bx-monospaced-font);white-space:pre;font-size:13px;margin:0 4px}.bx-number-stepper > div button{flex-shrink:0;border:none;width:24px;height:24px;margin:0;line-height:24px;background-color:var(--bx-default-button-color);color:#fff;border-radius:4px;font-weight:bold;font-size:14px;font-family:var(--bx-monospaced-font)}@media (hover:hover){.bx-number-stepper > div button:hover{background-color:var(--bx-default-button-hover-color)}}.bx-number-stepper > div button:active{background-color:var(--bx-default-button-hover-color)}.bx-number-stepper > div button:disabled + span{font-family:var(--bx-title-font)}.bx-number-stepper input[type=range]{display:block;margin:8px 0 2px auto;min-width:180px;width:100%;color:#959595 !important}.bx-number-stepper input[type=range]:disabled,.bx-number-stepper button:disabled{display:none}.bx-number-stepper[data-disabled=true] input[type=range],.bx-number-stepper[disabled=true] input[type=range],.bx-number-stepper[data-disabled=true] button,.bx-number-stepper[disabled=true] button{display:none}.bx-dual-number-stepper > span{display:block;font-family:var(--bx-monospaced-font);font-size:13px;white-space:pre;margin:0 4px;text-align:center}.bx-dual-number-stepper > div input[type=range]{display:block;width:100%;min-width:180px;background:transparent;color:#959595 !important;appearance:none;padding:8px 0}.bx-dual-number-stepper > div input[type=range]::-webkit-slider-runnable-track{background:linear-gradient(90deg,#fff var(--from),var(--bx-primary-button-color) var(--from) var(--to),#fff var(--to) 100%);height:8px;border-radius:2px}.bx-dual-number-stepper > div input[type=range]::-moz-range-track{background:linear-gradient(90deg,#fff var(--from),var(--bx-primary-button-color) var(--from) var(--to),#fff var(--to) 100%);height:8px;border-radius:2px}.bx-dual-number-stepper > div input[type=range]::-webkit-slider-thumb{margin-top:-4px;appearance:none;width:4px;height:16px;background:#00b85f;border:none;border-radius:2px}.bx-dual-number-stepper > div input[type=range]::-moz-range-thumb{margin-top:-4px;appearance:none;width:4px;height:16px;background:#00b85f;border:none;border-radius:2px}.bx-dual-number-stepper > div input[type=range]:hover::-webkit-slider-runnable-track,.bx-dual-number-stepper > div input[type=range].bx-dual-number-stepper > div input[type=range]:active::-webkit-slider-runnable-track,.bx-dual-number-stepper > div input[type=range]:focus::-webkit-slider-runnable-track{background:linear-gradient(90deg,#fff var(--from),#006635 var(--from) var(--to),#fff var(--to) 100%)}.bx-dual-number-stepper > div input[type=range]:hover::-moz-range-track,.bx-dual-number-stepper > div input[type=range].bx-dual-number-stepper > div input[type=range]:active::-moz-range-track,.bx-dual-number-stepper > div input[type=range]:focus::-moz-range-track{background:linear-gradient(90deg,#fff var(--from),#006635 var(--from) var(--to),#fff var(--to) 100%)}.bx-dual-number-stepper > div input[type=range]:hover::-webkit-slider-thumb,.bx-dual-number-stepper > div input[type=range].bx-dual-number-stepper > div input[type=range]:active::-webkit-slider-thumb,.bx-dual-number-stepper > div input[type=range]:focus::-webkit-slider-thumb{background:#fb3232}.bx-dual-number-stepper > div input[type=range]:hover::-moz-range-thumb,.bx-dual-number-stepper > div input[type=range].bx-dual-number-stepper > div input[type=range]:active::-moz-range-thumb,.bx-dual-number-stepper > div input[type=range]:focus::-moz-range-thumb{background:#fb3232}.bx-dual-number-stepper[data-disabled=true] input[type=range],.bx-dual-number-stepper[disabled=true] input[type=range]{display:none}#bx-game-bar{z-index:var(--bx-game-bar-z-index);position:fixed;bottom:0;width:40px;height:90px;overflow:visible;cursor:pointer}#bx-game-bar > svg{display:none;pointer-events:none;position:absolute;height:28px;margin-top:16px}@media (hover:hover){#bx-game-bar:hover > svg{display:block}}#bx-game-bar .bx-game-bar-container{opacity:0;position:absolute;display:flex;overflow:hidden;background:rgba(26,27,30,0.91);box-shadow:0 0 6px #1c1c1c;transition:opacity .1s ease-in}#bx-game-bar .bx-game-bar-container.bx-show{opacity:.9}#bx-game-bar .bx-game-bar-container.bx-show + svg{display:none !important}#bx-game-bar .bx-game-bar-container.bx-hide{opacity:0;pointer-events:none}#bx-game-bar .bx-game-bar-container button{width:60px;height:60px;border-radius:0}#bx-game-bar .bx-game-bar-container button svg{width:28px;height:28px;transition:transform .08s ease 0s}#bx-game-bar .bx-game-bar-container button:hover{border-radius:0}#bx-game-bar .bx-game-bar-container button:active svg{transform:scale(.75)}#bx-game-bar .bx-game-bar-container button.bx-activated{background-color:#fff}#bx-game-bar .bx-game-bar-container button.bx-activated svg{filter:invert(1)}#bx-game-bar .bx-game-bar-container div[data-activated] button{display:none}#bx-game-bar .bx-game-bar-container div[data-activated=\'false\'] button:first-of-type{display:block}#bx-game-bar .bx-game-bar-container div[data-activated=\'true\'] button:last-of-type{display:block}#bx-game-bar[data-position="bottom-left"]{left:0;direction:ltr}#bx-game-bar[data-position="bottom-left"] .bx-game-bar-container{border-radius:0 10px 10px 0}#bx-game-bar[data-position="bottom-right"]{right:0;direction:rtl}#bx-game-bar[data-position="bottom-right"] .bx-game-bar-container{direction:ltr;border-radius:10px 0 0 10px}.bx-badges{margin-left:0;user-select:none;-webkit-user-select:none}.bx-badge{border:none;display:inline-block;line-height:24px;color:#fff;font-family:var(--bx-title-font-semibold);font-size:14px;font-weight:400;margin:0 8px 8px 0;box-shadow:0 0 6px #000;border-radius:4px}.bx-badge-name{background-color:#2d3036;border-radius:4px 0 0 4px}.bx-badge-name svg{width:16px;height:16px}.bx-badge-value{background-color:#808080;border-radius:0 4px 4px 0}.bx-badge-name,.bx-badge-value{display:inline-block;padding:0 8px;line-height:30px;vertical-align:bottom}.bx-badge-battery[data-charging=true] span:first-of-type::after{content:\' ⚡️\'}div[class^=StreamMenu-module__container] .bx-badges{position:absolute;max-width:500px}#gamepass-dialog-root .bx-badges{position:fixed;top:60px;left:460px;max-width:500px}@media (min-width:568px) and (max-height:480px){#gamepass-dialog-root .bx-badges{position:unset;top:unset;left:unset;margin:8px 0}}.bx-stats-bar{display:flex;flex-direction:row;gap:8px;user-select:none;-webkit-user-select:none;position:fixed;top:0;background-color:#000;color:#fff;font-family:var(--bx-monospaced-font);font-size:.9rem;padding-left:8px;z-index:var(--bx-stats-bar-z-index);text-wrap:nowrap}.bx-stats-bar[data-stats*="[time]"] > .bx-stat-time,.bx-stats-bar[data-stats*="[play]"] > .bx-stat-play,.bx-stats-bar[data-stats*="[batt]"] > .bx-stat-batt,.bx-stats-bar[data-stats*="[fps]"] > .bx-stat-fps,.bx-stats-bar[data-stats*="[ping]"] > .bx-stat-ping,.bx-stats-bar[data-stats*="[jit]"] > .bx-stat-jit,.bx-stats-bar[data-stats*="[btr]"] > .bx-stat-btr,.bx-stats-bar[data-stats*="[dt]"] > .bx-stat-dt,.bx-stats-bar[data-stats*="[pl]"] > .bx-stat-pl,.bx-stats-bar[data-stats*="[fl]"] > .bx-stat-fl,.bx-stats-bar[data-stats*="[dl]"] > .bx-stat-dl,.bx-stats-bar[data-stats*="[ul]"] > .bx-stat-ul{display:inline-flex;align-items:baseline}.bx-stats-bar[data-stats$="[time]"] > .bx-stat-time,.bx-stats-bar[data-stats$="[play]"] > .bx-stat-play,.bx-stats-bar[data-stats$="[batt]"] > .bx-stat-batt,.bx-stats-bar[data-stats$="[fps]"] > .bx-stat-fps,.bx-stats-bar[data-stats$="[ping]"] > .bx-stat-ping,.bx-stats-bar[data-stats$="[jit]"] > .bx-stat-jit,.bx-stats-bar[data-stats$="[btr]"] > .bx-stat-btr,.bx-stats-bar[data-stats$="[dt]"] > .bx-stat-dt,.bx-stats-bar[data-stats$="[pl]"] > .bx-stat-pl,.bx-stats-bar[data-stats$="[fl]"] > .bx-stat-fl,.bx-stats-bar[data-stats$="[dl]"] > .bx-stat-dl,.bx-stats-bar[data-stats$="[ul]"] > .bx-stat-ul{border-right:none}.bx-stats-bar::before{display:none;content:\'👀\';vertical-align:middle;margin-right:8px}.bx-stats-bar[data-display=glancing]::before{display:inline-block}.bx-stats-bar[data-position=top-left]{left:0;border-radius:0 0 4px 0}.bx-stats-bar[data-position=top-right]{right:0;border-radius:0 0 0 4px}.bx-stats-bar[data-position=top-center]{transform:translate(-50%,0);left:50%;border-radius:0 0 4px 4px}.bx-stats-bar[data-shadow=true]{background:none;filter:drop-shadow(1px 0 0 rgba(0,0,0,0.941)) drop-shadow(-1px 0 0 rgba(0,0,0,0.941)) drop-shadow(0 1px 0 rgba(0,0,0,0.941)) drop-shadow(0 -1px 0 rgba(0,0,0,0.941))}.bx-stats-bar > div{display:none;border-right:1px solid #fff;padding-right:8px}.bx-stats-bar label{margin:0 8px 0 0;font-family:var(--bx-title-font);font-size:70%;font-weight:bold;vertical-align:middle;cursor:help}.bx-stats-bar span{display:inline-block;text-align:right;vertical-align:middle;white-space:pre}.bx-stats-bar span[data-grade=good]{color:#6bffff}.bx-stats-bar span[data-grade=ok]{color:#fff16b}.bx-stats-bar span[data-grade=bad]{color:#ff5f5f}.bx-mkb-settings{display:flex;flex-direction:column;flex:1;padding-bottom:10px;overflow:hidden}.bx-mkb-pointer-lock-msg{user-select:none;-webkit-user-select:none;position:fixed;left:50%;bottom:40px;transform:translateX(-50%);margin:auto;background:#151515;z-index:var(--bx-mkb-pointer-lock-msg-z-index);color:#fff;font-weight:400;font-family:"Segoe UI",Arial,Helvetica,sans-serif;font-size:1.3rem;padding:12px;border-radius:8px;align-items:center;box-shadow:0 0 6px #000;min-width:300px;opacity:.9;display:flex;flex-direction:column;gap:10px}.bx-mkb-pointer-lock-msg:hover{opacity:1}.bx-mkb-pointer-lock-msg > p{margin:0;width:100%;font-size:22px;margin-bottom:4px;font-weight:bold;text-align:left}.bx-mkb-pointer-lock-msg > div{width:100%;display:flex;flex-direction:row;gap:10px}.bx-mkb-pointer-lock-msg > div button:first-of-type{flex-shrink:1}.bx-mkb-pointer-lock-msg > div button:last-of-type{flex-grow:1}.bx-mkb-key-row{display:flex;margin-bottom:10px;align-items:center;gap:20px}.bx-mkb-key-row label{margin-bottom:0;font-family:var(--bx-promptfont-font);font-size:32px;text-align:center}.bx-mkb-settings.bx-editing .bx-mkb-key-row button{background:#393939;border-radius:4px;border:none}.bx-mkb-settings.bx-editing .bx-mkb-key-row button:hover{background:#333;cursor:pointer}.bx-mkb-action-buttons > div{text-align:right;display:none}.bx-mkb-action-buttons button{margin-left:8px}.bx-mkb-settings:not(.bx-editing) .bx-mkb-action-buttons > div:first-child{display:block}.bx-mkb-settings.bx-editing .bx-mkb-action-buttons > div:last-child{display:block}.bx-mkb-note{display:block;margin:0 0 10px;font-size:12px;text-align:center}button.bx-binding-button{flex:1;min-height:38px;border:none;border-radius:4px;font-size:14px;color:#fff;display:flex;align-items:center;align-self:center;padding:0 6px}button.bx-binding-button:disabled{background:#131416;padding:0 8px}button.bx-binding-button:not(:disabled){border:2px solid transparent;border-top:none;border-bottom:4px solid #252525;background:#3b3b3b;cursor:pointer}button.bx-binding-button:not(:disabled):hover,button.bx-binding-button:not(:disabled).bx-focusable:focus{background:#20b217;border-bottom-color:#186c13}button.bx-binding-button:not(:disabled):active{background:#16900f;border-bottom:3px solid #0c4e08;border-left-width:2px;border-right-width:2px}button.bx-binding-button:not(:disabled).bx-focusable:focus::after{top:-6px;left:-8px;right:-8px;bottom:-10px}.bx-settings-row .bx-binding-button-wrapper button.bx-binding-button{min-width:60px}.bx-controller-customizations-container .bx-btn-detect{display:block;margin-bottom:20px}.bx-controller-customizations-container .bx-btn-detect.bx-monospaced{background:none;font-weight:bold;font-size:12px}.bx-controller-customizations-container .bx-buttons-grid{display:grid;grid-template-columns:auto auto;column-gap:20px;row-gap:10px;margin-bottom:20px}.bx-controller-key-row{display:flex;align-items:stretch}.bx-controller-key-row > label{margin-bottom:0;font-family:var(--bx-promptfont-font);font-size:32px;text-align:center;min-width:50px;flex-shrink:0;display:flex;align-self:center}.bx-controller-key-row > label::after{content:\'❯\';margin:0 12px;font-size:16px;align-self:center}.bx-controller-key-row .bx-select{width:100% !important}.bx-controller-key-row .bx-select > div{min-width:50px}.bx-controller-key-row .bx-select label{font-family:var(--bx-promptfont-font),var(--bx-normal-font);font-size:32px;text-align:center;margin-bottom:6px;height:40px;line-height:40px}.bx-controller-key-row:hover > label{color:#ffe64b}.bx-controller-key-row:hover > label::after{color:#fff}.bx-product-details-buttons{display:flex;gap:10px;flex-direction:row}.bx-product-details-buttons button{max-width:max-content;margin:10px 0 0 0;display:flex}@media (min-width:568px) and (max-height:480px){.bx-product-details-buttons{flex-direction:column}.bx-product-details-buttons button{margin:8px 0 0 10px}}', PREF_HIDE_SECTIONS = getPref("ui.hideSections"), selectorToHide = [];
if (PREF_HIDE_SECTIONS.includes("news")) selectorToHide.push("#BodyContent > div[class*=CarouselRow-module]");
if (getPref("block.features").includes("byog")) selectorToHide.push("#BodyContent > div[class*=ByogRow-module__container___]");
if (PREF_HIDE_SECTIONS.includes("all-games")) selectorToHide.push("#BodyContent div[class*=AllGamesRow-module__gridContainer]"), selectorToHide.push("#BodyContent div[class*=AllGamesRow-module__rowHeader]");
@@ -6396,7 +6499,7 @@ class RootDialogObserver {
static $btnShortcut = AppInterface && createButton({
icon: BxIcon.CREATE_SHORTCUT,
label: t("create-shortcut"),
- style: 64 | 8 | 128 | 2048 | 4096,
+ style: 64 | 8 | 128 | 4096 | 8192,
onClick: (e) => {
window.BX_EXPOSED.dialogRoutes?.closeAll();
let $btn = e.target.closest("button");
@@ -6406,7 +6509,7 @@ class RootDialogObserver {
static $btnWallpaper = AppInterface && createButton({
icon: BxIcon.DOWNLOAD,
label: t("wallpaper"),
- style: 64 | 8 | 128 | 2048 | 4096,
+ style: 64 | 8 | 128 | 4096 | 8192,
onClick: (e) => {
window.BX_EXPOSED.dialogRoutes?.closeAll();
let $btn = e.target.closest("button"), details = parseDetailsPath($btn.dataset.path);
diff --git a/dist/better-xcloud.user.js b/dist/better-xcloud.user.js
index ef32ab3..8b4cca8 100755
--- a/dist/better-xcloud.user.js
+++ b/dist/better-xcloud.user.js
@@ -1,7 +1,7 @@
// ==UserScript==
// @name Better xCloud
// @namespace https://github.com/redphx
-// @version 6.0.7
+// @version 6.1.0-beta
// @description Improve Xbox Cloud Gaming (xCloud) experience
// @author redphx
// @license MIT
@@ -107,7 +107,7 @@ class UserAgent {
});
}
}
-var SCRIPT_VERSION = "6.0.7", SCRIPT_VARIANT = "full", AppInterface = window.AppInterface;
+var SCRIPT_VERSION = "6.1.0-beta", SCRIPT_VARIANT = "full", AppInterface = window.AppInterface;
UserAgent.init();
var userAgent = window.navigator.userAgent.toLowerCase(), isTv = userAgent.includes("smart-tv") || userAgent.includes("smarttv") || /\baft.*\b/.test(userAgent), isVr = window.navigator.userAgent.includes("VR") && window.navigator.userAgent.includes("OculusBrowser"), browserHasTouchSupport = "ontouchstart" in window || navigator.maxTouchPoints > 0, userAgentHasTouchSupport = !isTv && !isVr && browserHasTouchSupport, STATES = {
supportedRegion: !0,
@@ -179,11 +179,14 @@ var GamepadKeyName = {
101: ["Left Stick Down", "⇂"],
102: ["Left Stick Left", "↼"],
103: ["Left Stick Right", "⇀"],
+ 104: ["Left Stick", "⇱"],
11: ["R3", "↻"],
200: ["Right Stick Up", "↿"],
201: ["Right Stick Down", "⇃"],
202: ["Right Stick Left", "↽"],
- 203: ["Right Stick Right", "⇁"]
+ 203: ["Right Stick Right", "⇁"],
+ 204: ["Right Stick", "⇲"],
+ 17: ["Screenshot", "📸"]
};
class BxEventBus {
listeners = new Map;
@@ -298,6 +301,7 @@ var SUPPORTED_LANGUAGES = {
"zh-CN": "中文(简体)",
"zh-TW": "中文(繁體)"
}, Texts = {
+ "slightly-increase-input-latency": "Slightly increase input latency",
achievements: "Achievements",
activate: "Activate",
activated: "Activated",
@@ -357,12 +361,10 @@ var SUPPORTED_LANGUAGES = {
"continent-south-america": "South America",
contrast: "Contrast",
controller: "Controller",
+ "controller-customization": "Controller customization",
"controller-friendly-ui": "Controller-friendly UI",
- "controller-mapping": "Controller mapping",
- "controller-mapping-in-game": "In-game controller mapping",
"controller-shortcuts": "Controller shortcuts",
"controller-shortcuts-connect-note": "Connect a controller to use this feature",
- "controller-shortcuts-in-game": "In-game controller shortcuts",
"controller-shortcuts-xbox-note": "Button to open the Guide menu",
"controller-vibration": "Controller vibration",
copy: "Copy",
@@ -373,12 +375,12 @@ var SUPPORTED_LANGUAGES = {
default: "Default",
"default-preset-note": "You can't modify default presets. Create a new one to customize it.",
delete: "Delete",
+ "detect-controller-button": "Detect controller button",
device: "Device",
"device-unsupported-touch": "Your device doesn't have touch support",
"device-vibration": "Device vibration",
"device-vibration-not-using-gamepad": "On when not using gamepad",
disable: "Disable",
- "disable-byog-feature": 'Disable "Stream your own game" feature',
"disable-features": "Disable features",
"disable-home-context-menu": "Disable context menu in Home page",
"disable-post-stream-feedback-dialog": "Disable post-stream feedback dialog",
@@ -424,6 +426,10 @@ var SUPPORTED_LANGUAGES = {
"how-to-improve-app-performance": "How to improve app's performance",
ignore: "Ignore",
import: "Import",
+ "in-game-controller-customization": "In-game controller customization",
+ "in-game-controller-shortcuts": "In-game controller shortcuts",
+ "in-game-keyboard-shortcuts": "In-game keyboard shortcuts",
+ "in-game-shortcuts": "In-game shortcuts",
increase: "Increase",
"install-android": "Better xCloud app for Android",
invites: "Invites",
@@ -431,12 +437,13 @@ var SUPPORTED_LANGUAGES = {
jitter: "Jitter",
"keyboard-key": "Keyboard key",
"keyboard-shortcuts": "Keyboard shortcuts",
- "keyboard-shortcuts-in-game": "In-game keyboard shortcuts",
korea: "Korea",
language: "Language",
large: "Large",
layout: "Layout",
"left-stick": "Left stick",
+ "left-stick-deadzone": "Left stick deadzone",
+ "left-trigger-range": "Left trigger range",
"limit-fps": "Limit FPS",
"load-failed-message": "Failed to run Better xCloud",
"loading-screen": "Loading screen",
@@ -444,7 +451,6 @@ var SUPPORTED_LANGUAGES = {
"lowest-quality": "Lowest quality",
manage: "Manage",
"map-mouse-to": "Map mouse to",
- mapping: "Mapping",
"may-not-work-properly": "May not work properly!",
menu: "Menu",
microphone: "Microphone",
@@ -501,6 +507,7 @@ var SUPPORTED_LANGUAGES = {
"preferred-game-language": "Preferred game's language",
preset: "Preset",
press: "Press",
+ "press-any-button": "Press any button...",
"press-esc-to-cancel": "Press Esc to cancel",
"press-key-to-toggle-mkb": [
e => `Press ${e.key} to toggle this feature`,
@@ -556,6 +563,8 @@ var SUPPORTED_LANGUAGES = {
"renderer-configuration": "Renderer configuration",
"right-click-to-unbind": "Right-click on a key to unbind it",
"right-stick": "Right stick",
+ "right-stick-deadzone": "Right stick deadzone",
+ "right-trigger-range": "Right trigger range",
"rocket-always-hide": "Always hide",
"rocket-always-show": "Always show",
"rocket-animation": "Rocket animation",
@@ -565,7 +574,6 @@ var SUPPORTED_LANGUAGES = {
screen: "Screen",
"screenshot-apply-filters": "Apply video filters to screenshots",
"section-all-games": "All games",
- "section-byog": "Stream your own game",
"section-most-popular": "Most popular",
"section-native-mkb": "Play with mouse & keyboard",
"section-news": "News",
@@ -655,7 +663,6 @@ var SUPPORTED_LANGUAGES = {
e => `觸控遊玩佈局由 ${e.name} 提供`
],
"touch-controller": "Touch controller",
- "transparent-background": "Transparent background",
"true-achievements": "TrueAchievements",
ui: "UI",
"unexpected-behavior": "May cause unexpected behavior",
@@ -775,10 +782,11 @@ var ButtonStyleClass = {
64: "bx-focusable",
128: "bx-full-width",
256: "bx-full-height",
- 512: "bx-tall",
- 1024: "bx-circular",
- 2048: "bx-normal-case",
- 4096: "bx-normal-link"
+ 512: "bx-auto-height",
+ 1024: "bx-tall",
+ 2048: "bx-circular",
+ 4096: "bx-normal-case",
+ 8192: "bx-normal-link"
};
function createElement(elmName, props = {}, ..._) {
let $elm, hasNs = "xmlns" in props;
@@ -888,6 +896,23 @@ function renderPresetsList($select, allPresets, selectedValue, options = {}) {
if ($optGroup.hasChildNodes()) $select.appendChild($optGroup);
}
}
+function calculateSelectBoxes($root) {
+ let selects = Array.from($root.querySelectorAll("div.bx-select:not([data-calculated]) select"));
+ for (let $select of selects) {
+ let $parent = $select.parentElement;
+ if ($parent.classList.contains("bx-full-width")) {
+ $parent.dataset.calculated = "true";
+ continue;
+ }
+ let rect = $select.getBoundingClientRect(), $label, width = Math.ceil(rect.width);
+ if (!width) continue;
+ if ($label = $parent.querySelector($select.multiple ? ".bx-select-value" : "div"), $parent.isControllerFriendly) {
+ if ($select.multiple) width += 20;
+ if ($select.querySelector("optgroup")) width -= 15;
+ } else width += 10;
+ $select.style.left = "0", $label.style.minWidth = width + "px", $parent.dataset.calculated = "true";
+ }
+}
var FILE_SIZE_UNITS = ["B", "KB", "MB", "GB", "TB"];
function humanFileSize(size) {
let i = size == 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024));
@@ -1070,9 +1095,10 @@ class LocalDb {
static instance;
static getInstance = () => LocalDb.instance ?? (LocalDb.instance = new LocalDb);
static DB_NAME = "BetterXcloud";
- static DB_VERSION = 3;
+ static DB_VERSION = 4;
static TABLE_VIRTUAL_CONTROLLERS = "virtual_controllers";
static TABLE_CONTROLLER_SHORTCUTS = "controller_shortcuts";
+ static TABLE_CONTROLLER_CUSTOMIZATIONS = "controller_customizations";
static TABLE_CONTROLLER_SETTINGS = "controller_settings";
static TABLE_KEYBOARD_SHORTCUTS = "keyboard_shortcuts";
db;
@@ -1097,6 +1123,10 @@ class LocalDb {
if (!db.objectStoreNames.contains(LocalDb.TABLE_CONTROLLER_SETTINGS)) db.createObjectStore(LocalDb.TABLE_CONTROLLER_SETTINGS, {
keyPath: "id"
});
+ if (!db.objectStoreNames.contains(LocalDb.TABLE_CONTROLLER_CUSTOMIZATIONS)) db.createObjectStore(LocalDb.TABLE_CONTROLLER_CUSTOMIZATIONS, {
+ keyPath: "id",
+ autoIncrement: !0
+ });
if (!db.objectStoreNames.contains(LocalDb.TABLE_KEYBOARD_SHORTCUTS)) db.createObjectStore(LocalDb.TABLE_KEYBOARD_SHORTCUTS, {
keyPath: "id",
autoIncrement: !0
@@ -2080,10 +2110,9 @@ function hashCode(str) {
return hash;
}
function renderString(str, obj) {
- return str.replace(/\$\{.+?\}/g, (match) => {
- let key = match.substring(2, match.length - 1);
- if (key in obj) return obj[key];
- return match;
+ return str.replace(/\$\{([A-Za-z0-9_$]+)\}|\$([A-Za-z0-9_$]+)\$/g, (match, p1, p2) => {
+ let name = p1 || p2;
+ return name in obj ? obj[name] : match;
});
}
function ceilToNearest(value, interval) {
@@ -2185,27 +2214,27 @@ class StreamStatsCollector {
current: -1,
grades: [40, 75, 100],
toString() {
- return this.current === -1 ? "???" : this.current.toString().padStart(3, " ");
+ return this.current === -1 ? "???" : this.current.toString().padStart(3);
}
},
jit: {
current: 0,
grades: [30, 40, 60],
toString() {
- return `${this.current.toFixed(1)}ms`.padStart(6, " ");
+ return `${this.current.toFixed(1)}ms`.padStart(6);
}
},
fps: {
current: 0,
toString() {
let maxFps = getPref("video.maxFps");
- return maxFps < 60 ? `${maxFps}/${this.current}`.padStart(5, " ") : this.current.toString();
+ return maxFps < 60 ? `${maxFps}/${this.current}`.padStart(5) : this.current.toString();
}
},
btr: {
current: 0,
toString() {
- return `${this.current.toFixed(1)} Mbps`.padStart(9, " ");
+ return `${this.current.toFixed(1)} Mbps`.padStart(9);
}
},
fl: {
@@ -2229,13 +2258,13 @@ class StreamStatsCollector {
total: 0,
grades: [6, 9, 12],
toString() {
- return isNaN(this.current) ? "??ms" : `${this.current.toFixed(1)}ms`.padStart(6, " ");
+ return isNaN(this.current) ? "??ms" : `${this.current.toFixed(1)}ms`.padStart(6);
}
},
dl: {
total: 0,
toString() {
- return humanFileSize(this.total).padStart(8, " ");
+ return humanFileSize(this.total).padStart(8);
}
},
ul: {
@@ -2702,12 +2731,39 @@ class ControllerShortcutsTable extends BasePresetsTable {
BxLogger.info(this.LOG_TAG, "constructor()");
}
}
+class ControllerCustomizationsTable extends BasePresetsTable {
+ static instance;
+ static getInstance = () => ControllerCustomizationsTable.instance ?? (ControllerCustomizationsTable.instance = new ControllerCustomizationsTable(LocalDb.TABLE_CONTROLLER_CUSTOMIZATIONS));
+ TABLE_PRESETS = LocalDb.TABLE_CONTROLLER_CUSTOMIZATIONS;
+ DEFAULT_PRESETS = {
+ [-1]: {
+ id: -1,
+ name: "ABXY ⇄ BAYX",
+ data: {
+ mapping: {
+ 0: 1,
+ 1: 0,
+ 2: 3,
+ 3: 2
+ },
+ settings: {
+ leftStickDeadzone: [0, 100],
+ rightStickDeadzone: [0, 100],
+ leftTriggerRange: [0, 100],
+ rightTriggerRange: [0, 100],
+ vibrationIntensity: 100
+ }
+ }
+ }
+ };
+ DEFAULT_PRESET_ID = 0;
+}
class ControllerSettingsTable extends BaseLocalTable {
static instance;
static getInstance = () => ControllerSettingsTable.instance ?? (ControllerSettingsTable.instance = new ControllerSettingsTable(LocalDb.TABLE_CONTROLLER_SETTINGS));
static DEFAULT_DATA = {
shortcutPresetId: -1,
- vibrationIntensity: 50
+ customizationPresetId: 0
};
async getControllerData(id) {
let setting = await this.get(id);
@@ -2718,8 +2774,8 @@ class ControllerSettingsTable extends BaseLocalTable {
let all = await this.getAll(), results = {};
for (let key in all) {
if (!all[key]) continue;
- let settings = all[key].data;
- settings.vibrationIntensity /= 100, results[key] = settings;
+ let settings = Object.assign(all[key].data, ControllerSettingsTable.DEFAULT_DATA);
+ results[key] = settings;
}
return results;
}
@@ -2788,24 +2844,65 @@ class StreamSettings {
mkbPreset: null,
keyboardShortcuts: {}
};
+ static CONTROLLER_CUSTOMIZATION_MAPPING = {
+ 0: "A",
+ 1: "B",
+ 2: "X",
+ 3: "Y",
+ 12: "DPadUp",
+ 15: "DPadRight",
+ 13: "DPadDown",
+ 14: "DPadLeft",
+ 4: "LeftShoulder",
+ 5: "RightShoulder",
+ 6: "LeftTrigger",
+ 7: "RightTrigger",
+ 10: "LeftThumb",
+ 11: "RightThumb",
+ 104: "LeftStickAxes",
+ 204: "RightStickAxes",
+ 8: "View",
+ 9: "Menu",
+ 17: "Share"
+ };
static getPref(key) {
return getPref(key);
}
static async refreshControllerSettings() {
- let settings = StreamSettings.settings, controllers = {}, settingsTable = ControllerSettingsTable.getInstance(), shortcutsTable = ControllerShortcutsTable.getInstance(), gamepads = window.navigator.getGamepads();
+ let settings = StreamSettings.settings, controllers = {}, settingsTable = ControllerSettingsTable.getInstance(), shortcutsTable = ControllerShortcutsTable.getInstance(), mappingTable = ControllerCustomizationsTable.getInstance(), gamepads = window.navigator.getGamepads();
for (let gamepad of gamepads) {
if (!gamepad?.connected) continue;
if (gamepad.id === VIRTUAL_GAMEPAD_ID) continue;
- let settingsData = await settingsTable.getControllerData(gamepad.id), shortcutsMapping, preset = await shortcutsTable.getPreset(settingsData.shortcutPresetId);
- if (!preset) shortcutsMapping = null;
- else shortcutsMapping = preset.data.mapping;
+ let settingsData = await settingsTable.getControllerData(gamepad.id), shortcutsPreset = await shortcutsTable.getPreset(settingsData.shortcutPresetId), shortcutsMapping = !shortcutsPreset ? null : shortcutsPreset.data.mapping, customizationPreset = await mappingTable.getPreset(settingsData.customizationPresetId), customizationData = StreamSettings.convertControllerCustomization(customizationPreset?.data);
controllers[gamepad.id] = {
- vibrationIntensity: settingsData.vibrationIntensity,
- shortcuts: shortcutsMapping
+ shortcuts: shortcutsMapping,
+ customization: customizationData
};
}
settings.controllers = controllers, settings.controllerPollingRate = StreamSettings.getPref("controller.pollingRate"), await StreamSettings.refreshDeviceVibration();
}
+ static preCalculateControllerRange(obj, target, values) {
+ if (values && Array.isArray(values)) {
+ let [from, to] = values;
+ if (from > 1 || to < 100) obj[target] = [from / 100, to / 100];
+ }
+ }
+ static convertControllerCustomization(customization) {
+ if (!customization) return null;
+ let converted = {
+ mapping: {},
+ ranges: {},
+ vibrationIntensity: 1
+ }, gamepadKey;
+ for (gamepadKey in customization.mapping) {
+ let gamepadStr = StreamSettings.CONTROLLER_CUSTOMIZATION_MAPPING[gamepadKey];
+ if (!gamepadStr) continue;
+ let mappedKey = customization.mapping[gamepadKey];
+ if (typeof mappedKey === "number") converted.mapping[gamepadStr] = StreamSettings.CONTROLLER_CUSTOMIZATION_MAPPING[mappedKey];
+ else converted.mapping[gamepadStr] = !1;
+ }
+ return StreamSettings.preCalculateControllerRange(converted.ranges, "LeftTrigger", customization.settings.leftTriggerRange), StreamSettings.preCalculateControllerRange(converted.ranges, "RightTrigger", customization.settings.rightTriggerRange), StreamSettings.preCalculateControllerRange(converted.ranges, "LeftThumb", customization.settings.leftStickDeadzone), StreamSettings.preCalculateControllerRange(converted.ranges, "RightThumb", customization.settings.rightStickDeadzone), converted.vibrationIntensity = customization.settings.vibrationIntensity / 100, converted;
+ }
static async refreshDeviceVibration() {
if (!STATES.browser.capabilities.deviceVibration) return;
let mode = StreamSettings.getPref("deviceVibration.mode"), intensity = 0;
@@ -2855,6 +2952,43 @@ class StreamSettings {
window.addEventListener("gamepadconnected", listener), window.addEventListener("gamepaddisconnected", listener), StreamSettings.refreshAllSettings();
}
}
+var BxIcon = {
+ BETTER_XCLOUD: "",
+ TRUE_ACHIEVEMENTS: "",
+ STREAM_SETTINGS: "",
+ STREAM_STATS: "",
+ CLOSE: "",
+ CONTROLLER: "",
+ CREATE_SHORTCUT: "",
+ DISPLAY: "",
+ EYE: "",
+ EYE_SLASH: "",
+ HOME: "",
+ NATIVE_MKB: "",
+ NEW: "",
+ MANAGE: "",
+ COPY: "",
+ TRASH: "",
+ CURSOR_TEXT: "",
+ POWER: "",
+ QUESTION: "",
+ REFRESH: "",
+ REMOTE_PLAY: "",
+ CARET_LEFT: "",
+ CARET_RIGHT: "",
+ SCREENSHOT: "",
+ SPEAKER_MUTED: "",
+ TOUCH_CONTROL_ENABLE: "",
+ TOUCH_CONTROL_DISABLE: "",
+ MICROPHONE: "",
+ MICROPHONE_MUTED: "",
+ BATTERY: "",
+ PLAYTIME: "",
+ SERVER: "",
+ DOWNLOAD: "",
+ UPLOAD: "",
+ AUDIO: ""
+};
class MkbPopup {
static instance;
static getInstance = () => MkbPopup.instance ?? (MkbPopup.instance = new MkbPopup);
@@ -2880,7 +3014,7 @@ class MkbPopup {
}
createActivateButton() {
let options = {
- style: 1 | 512 | 128,
+ style: 1 | 1024 | 128,
label: t("activate"),
onClick: this.onActivate
}, shortcutKey = StreamSettings.findKeyboardShortcut("mkb.toggle");
@@ -2899,6 +3033,7 @@ class MkbPopup {
}
}), createButton({
label: t("manage"),
+ icon: BxIcon.MANAGE,
style: 64,
onClick: () => {
let dialog = SettingsDialog.getInstance();
@@ -3411,16 +3546,22 @@ class NavigationDialogManager {
LOG_TAG = "NavigationDialogManager";
static GAMEPAD_POLLING_INTERVAL = 50;
static GAMEPAD_KEYS = [
- 12,
- 13,
- 14,
- 15,
0,
1,
+ 2,
+ 3,
+ 12,
+ 15,
+ 13,
+ 14,
4,
5,
6,
- 7
+ 7,
+ 10,
+ 11,
+ 8,
+ 9
];
static GAMEPAD_DIRECTION_MAP = {
12: 1,
@@ -3450,31 +3591,14 @@ class NavigationDialogManager {
dialog = null;
dialogsStack = [];
constructor() {
- if (BxLogger.info(this.LOG_TAG, "constructor()"), this.$overlay = CE("div", { class: "bx-navigation-dialog-overlay bx-gone" }), this.$overlay.addEventListener("click", (e) => {
+ BxLogger.info(this.LOG_TAG, "constructor()"), this.$overlay = CE("div", { class: "bx-navigation-dialog-overlay bx-gone" }), this.$overlay.addEventListener("click", (e) => {
e.preventDefault(), e.stopPropagation(), this.dialog?.isCancellable() && this.hide();
- }), document.documentElement.appendChild(this.$overlay), this.$container = CE("div", { class: "bx-navigation-dialog bx-gone" }), document.documentElement.appendChild(this.$container), window.addEventListener(BxEvent.XCLOUD_GUIDE_MENU_SHOWN, (e) => this.hide()), getPref("ui.controllerFriendly"))
- new MutationObserver((mutationList) => {
- if (mutationList.length === 0 || mutationList[0].addedNodes.length === 0) return;
- let $dialog = mutationList[0].addedNodes[0];
- if (!$dialog || !($dialog instanceof HTMLElement)) return;
- this.calculateSelectBoxes($dialog);
- }).observe(this.$container, { childList: !0 });
- }
- calculateSelectBoxes($root) {
- let selects = Array.from($root.querySelectorAll(".bx-select:not([data-calculated]) select"));
- for (let $select of selects) {
- let $parent = $select.parentElement;
- if ($parent.classList.contains("bx-full-width")) {
- $parent.dataset.calculated = "true";
- return;
- }
- let rect = $select.getBoundingClientRect(), $label, width = Math.ceil(rect.width);
- if (!width) return;
- if ($select.multiple) $label = $parent.querySelector(".bx-select-value"), width += 20;
- else $label = $parent.querySelector("div");
- if ($select.querySelector("optgroup")) width -= 15;
- $label.style.minWidth = width + "px", $parent.dataset.calculated = "true";
- }
+ }), document.documentElement.appendChild(this.$overlay), this.$container = CE("div", { class: "bx-navigation-dialog bx-gone" }), document.documentElement.appendChild(this.$container), window.addEventListener(BxEvent.XCLOUD_GUIDE_MENU_SHOWN, (e) => this.hide()), new MutationObserver((mutationList) => {
+ if (mutationList.length === 0 || mutationList[0].addedNodes.length === 0) return;
+ let $dialog = mutationList[0].addedNodes[0];
+ if (!$dialog || !($dialog instanceof HTMLElement)) return;
+ calculateSelectBoxes($dialog);
+ }).observe(this.$container, { childList: !0 });
}
updateActiveInput(input) {
document.documentElement.dataset.activeInput = input;
@@ -3537,7 +3661,7 @@ class NavigationDialogManager {
}
}
this.clearGamepadHoldingInterval();
- }, 200);
+ }, 100);
continue;
}
if (releasedButton === null) {
@@ -3607,7 +3731,12 @@ class NavigationDialogManager {
if (!$focusing || $focusing === this.$container) return null;
if (checked.includes($focusing)) return null;
checked.push($focusing);
- let $target = $focusing, $parent = $target.parentElement, nearby = $target.nearby || {}, orientation = this.getOrientation($target), siblingProperty = NavigationDialogManager.SIBLING_PROPERTY_MAP[orientation][direction];
+ let $target = $focusing, $parent = $target.parentElement, nearby = $target.nearby || {}, orientation = this.getOrientation($target);
+ if (nearby[1] && direction === 1) return nearby[1];
+ else if (nearby[3] && direction === 3) return nearby[3];
+ else if (nearby[4] && direction === 4) return nearby[4];
+ else if (nearby[2] && direction === 2) return nearby[2];
+ let siblingProperty = NavigationDialogManager.SIBLING_PROPERTY_MAP[orientation][direction];
if (siblingProperty) {
let $sibling = $target;
while ($sibling[siblingProperty]) {
@@ -3848,43 +3977,8 @@ class TouchController {
});
}
}
-var BxIcon = {
- BETTER_XCLOUD: "",
- TRUE_ACHIEVEMENTS: "",
- STREAM_SETTINGS: "",
- STREAM_STATS: "",
- CLOSE: "",
- CONTROLLER: "",
- CREATE_SHORTCUT: "",
- DISPLAY: "",
- EYE: "",
- EYE_SLASH: "",
- HOME: "",
- NATIVE_MKB: "",
- NEW: "",
- COPY: "",
- TRASH: "",
- CURSOR_TEXT: "",
- POWER: "",
- QUESTION: "",
- REFRESH: "",
- REMOTE_PLAY: "",
- CARET_LEFT: "",
- CARET_RIGHT: "",
- SCREENSHOT: "",
- SPEAKER_MUTED: "",
- TOUCH_CONTROL_ENABLE: "",
- TOUCH_CONTROL_DISABLE: "",
- MICROPHONE: "",
- MICROPHONE_MUTED: "",
- BATTERY: "",
- PLAYTIME: "",
- SERVER: "",
- DOWNLOAD: "",
- UPLOAD: "",
- AUDIO: ""
-};
class BxSelectElement extends HTMLSelectElement {
+ isControllerFriendly;
optionsList;
indicatorsList;
$indicators;
@@ -3896,21 +3990,36 @@ class BxSelectElement extends HTMLSelectElement {
$label;
$checkBox;
static create($select, forceFriendly = !1) {
- if (!forceFriendly && !getPref("ui.controllerFriendly")) return $select.classList.add("bx-select"), $select;
+ let isControllerFriendly = forceFriendly || getPref("ui.controllerFriendly");
+ if ($select.multiple && !isControllerFriendly) return $select.classList.add("bx-select"), $select;
$select.removeAttribute("tabindex");
- let $wrapper = CE("div", { class: "bx-select" }), $btnPrev = createButton({
- label: "<",
- style: 64
- }), $btnNext = createButton({
- label: ">",
- style: 64
- });
- setNearby($wrapper, {
- orientation: "horizontal",
- focus: $btnNext
+ let $wrapper = CE("div", {
+ class: "bx-select",
+ _dataset: {
+ controllerFriendly: isControllerFriendly
+ }
});
+ if ($select.classList.contains("bx-full-width")) $wrapper.classList.add("bx-full-width");
let $content, self = $wrapper;
- if (self.isMultiple = $select.multiple, self.visibleIndex = $select.selectedIndex, self.$select = $select, self.optionsList = Array.from($select.querySelectorAll("option")), self.$indicators = CE("div", { class: "bx-select-indicators" }), self.indicatorsList = [], self.$btnNext = $btnNext, self.$btnPrev = $btnPrev, self.isMultiple) $content = CE("button", {
+ self.isControllerFriendly = isControllerFriendly, self.isMultiple = $select.multiple, self.visibleIndex = $select.selectedIndex, self.$select = $select, self.optionsList = Array.from($select.querySelectorAll("option")), self.$indicators = CE("div", { class: "bx-select-indicators" }), self.indicatorsList = [];
+ let $btnPrev, $btnNext;
+ if (isControllerFriendly) {
+ $btnPrev = createButton({
+ label: "<",
+ style: 64
+ }), $btnNext = createButton({
+ label: ">",
+ style: 64
+ }), setNearby($wrapper, {
+ orientation: "horizontal",
+ focus: $btnNext
+ }), self.$btnNext = $btnNext, self.$btnPrev = $btnPrev;
+ let boundOnPrevNext = BxSelectElement.onPrevNext.bind(self);
+ $btnPrev.addEventListener("click", boundOnPrevNext), $btnNext.addEventListener("click", boundOnPrevNext);
+ } else $select.addEventListener("change", (e) => {
+ self.visibleIndex = $select.selectedIndex, BxSelectElement.resetIndicators.call(self), BxSelectElement.render.call(self);
+ });
+ if (self.isMultiple) $content = CE("button", {
class: "bx-select-value bx-focusable",
tabindex: 0
}, CE("div", {}, self.$checkBox = CE("input", { type: "checkbox" }), self.$label = CE("span", {}, "")), self.$indicators), $content.addEventListener("click", (e) => {
@@ -3920,8 +4029,7 @@ class BxSelectElement extends HTMLSelectElement {
$option && ($option.selected = e.target.checked), BxEvent.dispatch($select, "input");
});
else $content = CE("div", {}, self.$label = CE("label", { for: $select.id + "_checkbox" }, ""), self.$indicators);
- let boundOnPrevNext = BxSelectElement.onPrevNext.bind(self);
- return $select.addEventListener("input", BxSelectElement.render.bind(self)), $btnPrev.addEventListener("click", boundOnPrevNext), $btnNext.addEventListener("click", boundOnPrevNext), new MutationObserver((mutationList, observer2) => {
+ return $select.addEventListener("input", BxSelectElement.render.bind(self)), new MutationObserver((mutationList, observer2) => {
mutationList.forEach((mutation) => {
if (mutation.type === "childList" || mutation.type === "attributes") self.visibleIndex = $select.selectedIndex, self.optionsList = Array.from($select.querySelectorAll("option")), BxSelectElement.resetIndicators.call(self), BxSelectElement.render.call(self);
});
@@ -3929,7 +4037,7 @@ class BxSelectElement extends HTMLSelectElement {
subtree: !0,
childList: !0,
attributes: !0
- }), self.append($select, $btnPrev, $content, $btnNext), BxSelectElement.resetIndicators.call(self), BxSelectElement.render.call(self), Object.defineProperty(self, "value", {
+ }), self.append($select, $btnPrev || "", $content, $btnNext || ""), BxSelectElement.resetIndicators.call(self), BxSelectElement.render.call(self), Object.defineProperty(self, "value", {
get() {
return $select.value;
},
@@ -3978,7 +4086,6 @@ class BxSelectElement extends HTMLSelectElement {
$btnNext,
$btnPrev,
$checkBox,
- visibleIndex,
optionsList,
indicatorsList
} = this;
@@ -3987,7 +4094,7 @@ class BxSelectElement extends HTMLSelectElement {
let $option = BxSelectElement.getOptionAtIndex.call(this, this.visibleIndex), content = "";
if ($option) {
let $parent = $option.parentElement, hasLabel = $parent instanceof HTMLOptGroupElement || this.$select.querySelector("optgroup");
- if (content = $option.textContent || "", content && hasLabel) {
+ if (content = $option.dataset.label || $option.textContent || "", content && hasLabel) {
let groupLabel = $parent instanceof HTMLOptGroupElement ? $parent.label : " ";
$label.innerHTML = "";
let fragment = document.createDocumentFragment();
@@ -3996,12 +4103,12 @@ class BxSelectElement extends HTMLSelectElement {
} else $label.textContent = content;
if ($label.classList.toggle("bx-line-through", $option && $option.disabled), this.isMultiple) $checkBox.checked = $option?.selected || !1, $checkBox.classList.toggle("bx-gone", !content);
let disableButtons = optionsList.length <= 1;
- $btnPrev.classList.toggle("bx-inactive", disableButtons), $btnNext.classList.toggle("bx-inactive", disableButtons);
+ $btnPrev?.classList.toggle("bx-inactive", disableButtons), $btnNext?.classList.toggle("bx-inactive", disableButtons);
for (let i = 0;i < optionsList.length; i++) {
let $option2 = optionsList[i], $indicator = indicatorsList[i];
if (!$option2 || !$indicator) continue;
if (clearDataSet($indicator), $option2.selected) $indicator.dataset.selected = "true";
- if ($option2.index === visibleIndex) $indicator.dataset.highlighted = "true";
+ if ($option2.index === this.visibleIndex) $indicator.dataset.highlighted = "true";
}
}
static normalizeIndex(index) {
@@ -4022,178 +4129,24 @@ class BxSelectElement extends HTMLSelectElement {
else BxEvent.dispatch($select, "input");
}
}
-var controller_shortcuts_default = `if (window.BX_EXPOSED.disableGamepadPolling) {
-this.inputConfiguration.useIntervalWorkerThreadForInput && this.intervalWorker ? this.intervalWorker.scheduleTimer(50) : this.pollGamepadssetTimeoutTimerID = setTimeout(this.pollGamepads, 50);
-return;
-}
-const currentGamepad = \${gamepadVar};
-if (currentGamepad.buttons[17] && currentGamepad.buttons[17].pressed) {
-window.dispatchEvent(new Event(BxEvent.CAPTURE_SCREENSHOT));
-}
-const btnHome = currentGamepad.buttons[16];
-if (btnHome) {
-if (!this.bxHomeStates) {
-this.bxHomeStates = {};
-}
-let intervalMs = 0;
-let hijack = false;
-if (btnHome.pressed) {
-hijack = true;
-intervalMs = 16;
-this.gamepadIsIdle.set(currentGamepad.index, false);
-if (this.bxHomeStates[currentGamepad.index]) {
-const lastTimestamp = this.bxHomeStates[currentGamepad.index].timestamp;
-if (currentGamepad.timestamp !== lastTimestamp) {
-this.bxHomeStates[currentGamepad.index].timestamp = currentGamepad.timestamp;
-const handled = window.BX_EXPOSED.handleControllerShortcut(currentGamepad);
-if (handled) {
-this.bxHomeStates[currentGamepad.index].shortcutPressed += 1;
-}
-}
-} else {
-window.BX_EXPOSED.resetControllerShortcut(currentGamepad.index);
-this.bxHomeStates[currentGamepad.index] = {
-shortcutPressed: 0,
-timestamp: currentGamepad.timestamp,
-};
-}
-} else if (this.bxHomeStates[currentGamepad.index]) {
-hijack = true;
-const info = structuredClone(this.bxHomeStates[currentGamepad.index]);
-this.bxHomeStates[currentGamepad.index] = null;
-if (info.shortcutPressed === 0) {
-const fakeGamepadMappings = [{
-GamepadIndex: currentGamepad.index,
-A: 0,
-B: 0,
-X: 0,
-Y: 0,
-LeftShoulder: 0,
-RightShoulder: 0,
-LeftTrigger: 0,
-RightTrigger: 0,
-View: 0,
-Menu: 0,
-LeftThumb: 0,
-RightThumb: 0,
-DPadUp: 0,
-DPadDown: 0,
-DPadLeft: 0,
-DPadRight: 0,
-Nexus: 1,
-LeftThumbXAxis: 0,
-LeftThumbYAxis: 0,
-RightThumbXAxis: 0,
-RightThumbYAxis: 0,
-PhysicalPhysicality: 0,
-VirtualPhysicality: 0,
-Dirty: true,
-Virtual: false,
-}];
-const isLongPress = (currentGamepad.timestamp - info.timestamp) >= 500;
-intervalMs = isLongPress ? 500 : 100;
-this.inputSink.onGamepadInput(performance.now() - intervalMs, fakeGamepadMappings);
-} else {
-intervalMs = window.BX_STREAM_SETTINGS.controllerPollingRate;
-}
-}
-if (hijack && intervalMs) {
-this.inputConfiguration.useIntervalWorkerThreadForInput && this.intervalWorker ? this.intervalWorker.scheduleTimer(intervalMs) : this.pollGamepadssetTimeoutTimerID = setTimeout(this.pollGamepads, intervalMs);
-return;
-}
-}`;
-var expose_stream_session_default = `window.BX_EXPOSED.streamSession = this;
-const orgSetMicrophoneState = this.setMicrophoneState.bind(this);
-this.setMicrophoneState = state => {
-orgSetMicrophoneState(state);
-window.BxEventBus.Stream.emit('microphone.state.changed', { state });
-};
-window.dispatchEvent(new Event(BxEvent.STREAM_SESSION_READY));
-let updateDimensionsStr = this.updateDimensions.toString();
-if (updateDimensionsStr.startsWith('function ')) {
-updateDimensionsStr = updateDimensionsStr.substring(9);
-}
-const renderTargetVar = updateDimensionsStr.match(/if\\((\\w+)\\){/)[1];
-updateDimensionsStr = updateDimensionsStr.replaceAll(renderTargetVar + '.scroll', 'scroll');
-updateDimensionsStr = updateDimensionsStr.replace(\`if(\${renderTargetVar}){\`, \`
-if (\${renderTargetVar}) {
-const scrollWidth = \${renderTargetVar}.dataset.width ? parseInt(\${renderTargetVar}.dataset.width) : \${renderTargetVar}.scrollWidth;
-const scrollHeight = \${renderTargetVar}.dataset.height ? parseInt(\${renderTargetVar}.dataset.height) : \${renderTargetVar}.scrollHeight;
-\`);
-eval(\`this.updateDimensions = function \${updateDimensionsStr}\`);`;
-var local_co_op_enable_default = `this.orgOnGamepadChanged = this.onGamepadChanged;
-this.orgOnGamepadInput = this.onGamepadInput;
-let match;
-let onGamepadChangedStr = this.onGamepadChanged.toString();
-if (onGamepadChangedStr.startsWith('function ')) {
-onGamepadChangedStr = onGamepadChangedStr.substring(9);
-}
-onGamepadChangedStr = onGamepadChangedStr.replaceAll('0', 'arguments[1]');
-eval(\`this.patchedOnGamepadChanged = function \${onGamepadChangedStr}\`);
-let onGamepadInputStr = this.onGamepadInput.toString();
-if (onGamepadInputStr.startsWith('function ')) {
-onGamepadInputStr = onGamepadInputStr.substring(9);
-}
-match = onGamepadInputStr.match(/(\\w+\\.GamepadIndex)/);
-if (match) {
-const gamepadIndexVar = match[0];
-onGamepadInputStr = onGamepadInputStr.replace('this.gamepadStates.get(', \`this.gamepadStates.get(\${gamepadIndexVar},\`);
-eval(\`this.patchedOnGamepadInput = function \${onGamepadInputStr}\`);
-BxLogger.info('supportLocalCoOp', '✅ Successfully patched local co-op support');
-} else {
-BxLogger.error('supportLocalCoOp', '❌ Unable to patch local co-op support');
-}
-this.toggleLocalCoOp = enable => {
-BxLogger.info('toggleLocalCoOp', enable ? 'Enabled' : 'Disabled');
-this.onGamepadChanged = enable ? this.patchedOnGamepadChanged : this.orgOnGamepadChanged;
-this.onGamepadInput = enable ? this.patchedOnGamepadInput : this.orgOnGamepadInput;
-const gamepads = window.navigator.getGamepads();
-for (const gamepad of gamepads) {
-if (!gamepad?.connected) {
-continue;
-}
-if (gamepad.id.includes('Better xCloud')) {
-continue;
-}
-window.dispatchEvent(new GamepadEvent('gamepaddisconnected', { gamepad }));
-window.dispatchEvent(new GamepadEvent('gamepadconnected', { gamepad }));
-}
-};
-window.BX_EXPOSED.toggleLocalCoOp = this.toggleLocalCoOp.bind(this);`;
-var remote_play_enable_default = `connectMode: window.BX_REMOTE_PLAY_CONFIG ? "xhome-connect" : "cloud-connect",
-remotePlayServerId: (window.BX_REMOTE_PLAY_CONFIG && window.BX_REMOTE_PLAY_CONFIG.serverId) || '',`;
-var remote_play_keep_alive_default = `const msg = JSON.parse(e);
-if (msg.reason === 'WarningForBeingIdle' && !window.location.pathname.includes('/launch/')) {
-try {
-this.sendKeepAlive();
-return;
-} catch (ex) { console.log(ex); }
-}`;
-var vibration_adjust_default = `const gamepad = e.gamepad;
-if (gamepad?.connected) {
-const gamepadSettings = window.BX_STREAM_SETTINGS.controllers[gamepad.id];
-if (gamepadSettings) {
-const intensity = gamepadSettings.vibrationIntensity;
-if (intensity === 0) {
-return void(e.repeat = 0);
-} else if (intensity < 1) {
-e.leftMotorPercent *= intensity;
-e.rightMotorPercent *= intensity;
-e.leftTriggerMotorPercent *= intensity;
-e.rightTriggerMotorPercent *= intensity;
-}
-}
-}`;
+var controller_customization_default = "var shareButtonPressed=currentGamepad.buttons[17]?.pressed,shareButtonHandled=!1,xCloudGamepad=$xCloudGamepadVar$;if(currentGamepad.id in window.BX_STREAM_SETTINGS.controllers){let controller=window.BX_STREAM_SETTINGS.controllers[currentGamepad.id];if(controller?.customization){let{mapping,ranges}=controller.customization,pressedButtons={},releasedButtons={},isModified=!1;if(ranges.LeftTrigger){let[from,to]=ranges.LeftTrigger;xCloudGamepad.LeftTrigger=xCloudGamepad.LeftTrigger>to?1:xCloudGamepad.LeftTrigger,xCloudGamepad.LeftTrigger=xCloudGamepad.LeftTriggerto?1:xCloudGamepad.RightTrigger,xCloudGamepad.RightTrigger=xCloudGamepad.RightTriggerto?1:range;if(newRange=newRangeto?1:range;if(newRange=newRange=0.1)pressedButtons[targetX]=rangeX,pressedButtons[targetY]=rangeY}releasedButtons[sourceX]=0,releasedButtons[sourceY]=0,isModified=!0}else if(typeof mappedKey===\"string\"){let pressed=!1,value=0;if(key===\"LeftTrigger\"||key===\"RightTrigger\"){let currentRange=xCloudGamepad[key];if(mappedKey===\"LeftTrigger\"||mappedKey===\"RightTrigger\")pressed=currentRange>=0.1,value=currentRange;else pressed=!0,value=currentRange>=0.9?1:0}else if(xCloudGamepad[key])pressed=!0,value=xCloudGamepad[key];if(pressed)pressedButtons[mappedKey]=value,releasedButtons[key]=0,isModified=!0}else if(mappedKey===!1)pressedButtons[key]=0,isModified=!0}isModified&&Object.assign(xCloudGamepad,releasedButtons,pressedButtons)}}if(shareButtonPressed&&!shareButtonHandled)window.dispatchEvent(new Event(BxEvent.CAPTURE_SCREENSHOT));\n";
+var poll_gamepad_default = "var self=this;if(window.BX_EXPOSED.disableGamepadPolling){self.inputConfiguration.useIntervalWorkerThreadForInput&&self.intervalWorker?self.intervalWorker.scheduleTimer(50):self.pollGamepadssetTimeoutTimerID=window.setTimeout(self.pollGamepads,50);return}var currentGamepad=$gamepadVar$,btnHome=currentGamepad.buttons[16];if(btnHome){if(!self.bxHomeStates)self.bxHomeStates={};let intervalMs=0,hijack=!1;if(btnHome.pressed)if(hijack=!0,intervalMs=16,self.gamepadIsIdle.set(currentGamepad.index,!1),self.bxHomeStates[currentGamepad.index]){let lastTimestamp=self.bxHomeStates[currentGamepad.index].timestamp;if(currentGamepad.timestamp!==lastTimestamp){if(self.bxHomeStates[currentGamepad.index].timestamp=currentGamepad.timestamp,window.BX_EXPOSED.handleControllerShortcut(currentGamepad))self.bxHomeStates[currentGamepad.index].shortcutPressed+=1}}else window.BX_EXPOSED.resetControllerShortcut(currentGamepad.index),self.bxHomeStates[currentGamepad.index]={shortcutPressed:0,timestamp:currentGamepad.timestamp};else if(self.bxHomeStates[currentGamepad.index]){hijack=!0;let info=structuredClone(self.bxHomeStates[currentGamepad.index]);if(self.bxHomeStates[currentGamepad.index]=null,info.shortcutPressed===0){let fakeGamepadMappings=[{GamepadIndex:currentGamepad.index,A:0,B:0,X:0,Y:0,LeftShoulder:0,RightShoulder:0,LeftTrigger:0,RightTrigger:0,View:0,Menu:0,LeftThumb:0,RightThumb:0,DPadUp:0,DPadDown:0,DPadLeft:0,DPadRight:0,Nexus:1,LeftThumbXAxis:0,LeftThumbYAxis:0,RightThumbXAxis:0,RightThumbYAxis:0,PhysicalPhysicality:0,VirtualPhysicality:0,Dirty:!0,Virtual:!1}];intervalMs=currentGamepad.timestamp-info.timestamp>=500?500:100,self.inputSink.onGamepadInput(performance.now()-intervalMs,fakeGamepadMappings)}else intervalMs=window.BX_STREAM_SETTINGS.controllerPollingRate}if(hijack&&intervalMs){self.inputConfiguration.useIntervalWorkerThreadForInput&&self.intervalWorker?self.intervalWorker.scheduleTimer(intervalMs):self.pollGamepadssetTimeoutTimerID=setTimeout(self.pollGamepads,intervalMs);return}}\n";
+var expose_stream_session_default = 'var self=this;window.BX_EXPOSED.streamSession=self;var orgSetMicrophoneState=self.setMicrophoneState.bind(self);self.setMicrophoneState=(state)=>{orgSetMicrophoneState(state),window.BxEventBus.Stream.emit("microphone.state.changed",{state})};window.dispatchEvent(new Event(BxEvent.STREAM_SESSION_READY));var updateDimensionsStr=self.updateDimensions.toString();if(updateDimensionsStr.startsWith("function "))updateDimensionsStr=updateDimensionsStr.substring(9);var renderTargetVar=updateDimensionsStr.match(/if\\((\\w+)\\){/)[1];updateDimensionsStr=updateDimensionsStr.replaceAll(renderTargetVar+".scroll","scroll");updateDimensionsStr=updateDimensionsStr.replace(`if(${renderTargetVar}){`,`\nif (${renderTargetVar}) {\nconst scrollWidth = ${renderTargetVar}.dataset.width ? parseInt(${renderTargetVar}.dataset.width) : ${renderTargetVar}.scrollWidth;\nconst scrollHeight = ${renderTargetVar}.dataset.height ? parseInt(${renderTargetVar}.dataset.height) : ${renderTargetVar}.scrollHeight;\n`);eval(`this.updateDimensions = function ${updateDimensionsStr}`);\n';
+var local_co_op_enable_default = 'this.orgOnGamepadChanged=this.onGamepadChanged;this.orgOnGamepadInput=this.onGamepadInput;var match,onGamepadChangedStr=this.onGamepadChanged.toString();if(onGamepadChangedStr.startsWith("function "))onGamepadChangedStr=onGamepadChangedStr.substring(9);onGamepadChangedStr=onGamepadChangedStr.replaceAll("0","arguments[1]");eval(`this.patchedOnGamepadChanged = function ${onGamepadChangedStr}`);var onGamepadInputStr=this.onGamepadInput.toString();if(onGamepadInputStr.startsWith("function "))onGamepadInputStr=onGamepadInputStr.substring(9);match=onGamepadInputStr.match(/(\\w+\\.GamepadIndex)/);if(match){let gamepadIndexVar=match[0];onGamepadInputStr=onGamepadInputStr.replace("this.gamepadStates.get(",`this.gamepadStates.get(${gamepadIndexVar},`),eval(`this.patchedOnGamepadInput = function ${onGamepadInputStr}`),BxLogger.info("supportLocalCoOp","✅ Successfully patched local co-op support")}else BxLogger.error("supportLocalCoOp","❌ Unable to patch local co-op support");this.toggleLocalCoOp=(enable)=>{BxLogger.info("toggleLocalCoOp",enable?"Enabled":"Disabled"),this.onGamepadChanged=enable?this.patchedOnGamepadChanged:this.orgOnGamepadChanged,this.onGamepadInput=enable?this.patchedOnGamepadInput:this.orgOnGamepadInput;let gamepads=window.navigator.getGamepads();for(let gamepad of gamepads){if(!gamepad?.connected)continue;if(gamepad.id.includes("Better xCloud"))continue;window.dispatchEvent(new GamepadEvent("gamepaddisconnected",{gamepad})),window.dispatchEvent(new GamepadEvent("gamepadconnected",{gamepad}))}};window.BX_EXPOSED.toggleLocalCoOp=this.toggleLocalCoOp.bind(null);\n';
+var remote_play_keep_alive_default = `try{if(JSON.parse(e).reason==="WarningForBeingIdle"&&!window.location.pathname.includes("/launch/")){this.sendKeepAlive();return}}catch(ex){console.log(ex)}`;
+var vibration_adjust_default = `if(e?.gamepad?.connected){let gamepadSettings=window.BX_STREAM_SETTINGS.controllers[e.gamepad.id];if(gamepadSettings?.customization){let intensity=gamepadSettings.customization.vibrationIntensity;if(intensity<=0){e.repeat=0;return}else if(intensity<1)e.leftMotorPercent*=intensity,e.rightMotorPercent*=intensity,e.leftTriggerMotorPercent*=intensity,e.rightTriggerMotorPercent*=intensity}}`;
class PatcherUtils {
- static indexOf(txt, searchString, startIndex, maxRange) {
+ static indexOf(txt, searchString, startIndex, maxRange = 0, after = !1) {
+ if (startIndex < 0) return -1;
let index = txt.indexOf(searchString, startIndex);
if (index < 0 || maxRange && index - startIndex > maxRange) return -1;
- return index;
+ return after ? index + searchString.length : index;
}
- static lastIndexOf(txt, searchString, startIndex, maxRange) {
+ static lastIndexOf(txt, searchString, startIndex, maxRange = 0, after = !1) {
+ if (startIndex < 0) return -1;
let index = txt.lastIndexOf(searchString, startIndex);
if (index < 0 || maxRange && startIndex - index > maxRange) return -1;
- return index;
+ return after ? index + searchString.length : index;
}
static insertAt(txt, index, insertString) {
return txt.substring(0, index) + insertString + txt.substring(index);
@@ -4262,7 +4215,9 @@ var LOG_TAG2 = "Patcher", PATCHES = {
remotePlayConnectMode(str) {
let text = 'connectMode:"cloud-connect",';
if (!str.includes(text)) return !1;
- return str.replace(text, remote_play_enable_default);
+ let newCode = `connectMode: window.BX_REMOTE_PLAY_CONFIG ? "xhome-connect" : "cloud-connect",
+remotePlayServerId: (window.BX_REMOTE_PLAY_CONFIG && window.BX_REMOTE_PLAY_CONFIG.serverId) || '',`;
+ return str.replace(text, newCode);
},
remotePlayDisableAchievementToast(str) {
let text = ".AchievementUnlock:{";
@@ -4293,14 +4248,16 @@ var LOG_TAG2 = "Patcher", PATCHES = {
if (setTimeoutIndex < 0) return !1;
let codeBlock = str.substring(index, setTimeoutIndex), tmp = str.substring(setTimeoutIndex, setTimeoutIndex + 150), tmpPatched = tmp.replaceAll("Math.max(0,4-", "Math.max(0,window.BX_STREAM_SETTINGS.controllerPollingRate - ");
if (str = PatcherUtils.replaceWith(str, setTimeoutIndex, tmp, tmpPatched), getPref("block.tracking")) codeBlock = codeBlock.replace("this.inputPollingIntervalStats.addValue", ""), codeBlock = codeBlock.replace("this.inputPollingDurationStats.addValue", "");
- let match = codeBlock.match(/this\.gamepadTimestamps\.set\((\w+)\.index/);
- if (match) {
- let gamepadVar = match[1], newCode = renderString(controller_shortcuts_default, {
- gamepadVar
- });
- codeBlock = codeBlock.replace("this.gamepadTimestamps.set", newCode + "this.gamepadTimestamps.set");
- }
- return str = str.substring(0, index) + codeBlock + str.substring(setTimeoutIndex), str;
+ let match = codeBlock.match(/this\.gamepadTimestamps\.set\(([A-Za-z0-9_$]+)\.index/);
+ if (!match) return !1;
+ let newCode = renderString(poll_gamepad_default, {
+ gamepadVar: match[1]
+ });
+ if (codeBlock = codeBlock.replace("this.gamepadTimestamps.set", newCode + "this.gamepadTimestamps.set"), match = codeBlock.match(/let ([A-Za-z0-9_$]+)=this\.gamepadMappings\.find/), !match) return !1;
+ let xCloudGamepadVar = match[1], inputFeedbackManager = PatcherUtils.indexOf(codeBlock, "this.inputFeedbackManager.onGamepadConnected(", 0, 1e4), backetIndex = PatcherUtils.indexOf(codeBlock, "}", inputFeedbackManager, 100);
+ if (backetIndex < 0) return !1;
+ let customizationCode = ";";
+ return customizationCode += renderString(controller_customization_default, { xCloudGamepadVar }), codeBlock = PatcherUtils.insertAt(codeBlock, backetIndex, customizationCode), str = str.substring(0, index) + codeBlock + str.substring(setTimeoutIndex), str;
},
enableXcloudLogger(str) {
let text = "this.telemetryProvider=e}log(e,t,r){";
@@ -5094,7 +5051,8 @@ class BaseProfileManagerDialog extends NavigationDialog {
title;
presetsDb;
allPresets;
- currentPresetId = 0;
+ currentPresetId = null;
+ activatedPresetId = null;
$presets;
$header;
$defaultNote;
@@ -5106,11 +5064,12 @@ class BaseProfileManagerDialog extends NavigationDialog {
this.title = title, this.presetsDb = presetsDb;
}
updateButtonStates() {
- let isDefaultPreset = this.currentPresetId <= 0;
+ let isDefaultPreset = this.currentPresetId === null || this.currentPresetId <= 0;
this.$btnRename.disabled = isDefaultPreset, this.$btnDelete.disabled = isDefaultPreset, this.$defaultNote.classList.toggle("bx-gone", !isDefaultPreset);
}
async renderPresetsList() {
- this.allPresets = await this.presetsDb.getPresets(), renderPresetsList(this.$presets, this.allPresets, this.currentPresetId, { selectedIndicator: !0 });
+ if (this.allPresets = await this.presetsDb.getPresets(), this.currentPresetId === null) this.currentPresetId = this.allPresets.default[0];
+ renderPresetsList(this.$presets, this.allPresets, this.activatedPresetId, { selectedIndicator: !0 });
}
promptNewName(action, value = "") {
let newName = "";
@@ -5121,9 +5080,12 @@ class BaseProfileManagerDialog extends NavigationDialog {
return newName ? newName : !1;
}
async renderDialog() {
- this.$presets = CE("select", { tabindex: -1 });
+ this.$presets = CE("select", {
+ class: "bx-full-width",
+ tabindex: -1
+ });
let $select = BxSelectElement.create(this.$presets);
- $select.classList.add("bx-full-width"), $select.addEventListener("input", (e) => {
+ $select.addEventListener("input", (e) => {
this.switchPreset(parseInt($select.value));
});
let $header = CE("div", {
@@ -5172,15 +5134,20 @@ class BaseProfileManagerDialog extends NavigationDialog {
}));
this.$header = $header, this.$container = CE("div", { class: "bx-centered-dialog" }, CE("div", { class: "bx-dialog-title" }, CE("p", {}, this.title), createButton({
icon: BxIcon.CLOSE,
- style: 64 | 1024 | 8,
+ style: 64 | 2048 | 8,
onClick: (e) => this.hide()
})), CE("div", {}, $header, this.$defaultNote = CE("div", { class: "bx-default-preset-note bx-gone" }, t("default-preset-note"))), CE("div", { class: "bx-dialog-content" }, this.$content));
}
async refresh() {
- await this.renderPresetsList(), this.switchPreset(this.currentPresetId);
+ await this.renderPresetsList(), this.$presets.value = this.currentPresetId.toString(), BxEvent.dispatch(this.$presets, "input", { manualTrigger: !0 });
}
async onBeforeMount(configs = {}) {
- if (configs?.id) this.currentPresetId = configs.id;
+ await this.renderPresetsList();
+ let valid = !1;
+ if (typeof configs?.id === "number") {
+ if (configs.id in this.allPresets.data) this.currentPresetId = configs.id, this.activatedPresetId = configs.id, valid = !0;
+ }
+ if (!valid) this.currentPresetId = this.allPresets.default[0], this.activatedPresetId = null;
this.refresh();
}
getDialog() {
@@ -5259,7 +5226,10 @@ class ControllerShortcutsManagerDialog extends BaseProfileManagerDialog {
];
constructor(title) {
super(title, ControllerShortcutsTable.getInstance());
- let PREF_CONTROLLER_FRIENDLY_UI = getPref("ui.controllerFriendly"), $baseSelect = CE("select", { autocomplete: "off" }, CE("option", { value: "" }, "---"));
+ let $baseSelect = CE("select", {
+ class: "bx-full-width",
+ autocomplete: "off"
+ }, CE("option", { value: "" }, "---"));
for (let groupLabel in SHORTCUT_ACTIONS) {
let items = SHORTCUT_ACTIONS[groupLabel];
if (!items) continue;
@@ -5275,15 +5245,6 @@ class ControllerShortcutsManagerDialog extends BaseProfileManagerDialog {
let $content = CE("div", {
class: "bx-controller-shortcuts-manager-container"
}), onActionChanged = (e) => {
- let $target = e.target, action = $target.value;
- if (!PREF_CONTROLLER_FRIENDLY_UI) {
- let $fakeSelect = $target.previousElementSibling, fakeText = "---";
- if (action) {
- let $selectedOption = $target.options[$target.selectedIndex];
- fakeText = $selectedOption.parentElement.label + " ❯ " + $selectedOption.text;
- }
- $fakeSelect.firstElementChild.text = fakeText;
- }
if (!e.ignoreOnChange) this.updatePreset();
}, fragment = document.createDocumentFragment();
fragment.appendChild(CE("p", { class: "bx-shortcut-note" }, CE("span", { class: "bx-prompt" }, ""), ": " + t("controller-shortcuts-xbox-note")));
@@ -5293,12 +5254,10 @@ class ControllerShortcutsManagerDialog extends BaseProfileManagerDialog {
_nearby: {
orientation: "horizontal"
}
- }), $label = CE("label", { class: "bx-prompt" }, `${""}${prompt2}`), $div = CE("div", { class: "bx-shortcut-actions" }), $fakeSelect = null;
- if (!PREF_CONTROLLER_FRIENDLY_UI) $fakeSelect = CE("select", { autocomplete: "off" }, CE("option", {}, "---")), $div.appendChild($fakeSelect);
- let $select = BxSelectElement.create($baseSelect.cloneNode(!0));
- $select.dataset.button = button.toString(), $select.classList.add("bx-full-width"), $select.addEventListener("input", onActionChanged), this.selectActions[button] = [$select, $fakeSelect], $div.appendChild($select), setNearby($row, {
+ }), $label = CE("label", { class: "bx-prompt" }, `${""}${prompt2}`), $select = BxSelectElement.create($baseSelect.cloneNode(!0));
+ $select.dataset.button = button.toString(), $select.addEventListener("input", onActionChanged), this.selectActions[button] = $select, setNearby($row, {
focus: $select
- }), $row.append($label, $div), fragment.appendChild($row);
+ }), $row.append($label, $select), fragment.appendChild($row);
}
$content.appendChild(fragment), this.$content = $content;
}
@@ -5311,8 +5270,8 @@ class ControllerShortcutsManagerDialog extends BaseProfileManagerDialog {
this.currentPresetId = id;
let isDefaultPreset = id <= 0, actions = preset.data, button;
for (button in this.selectActions) {
- let [$select, $fakeSelect] = this.selectActions[button];
- $select.value = actions.mapping[button] || "", $select.disabled = isDefaultPreset, $fakeSelect && ($fakeSelect.disabled = isDefaultPreset), BxEvent.dispatch($select, "input", {
+ let $select = this.selectActions[button];
+ $select.value = actions.mapping[button] || "", $select.disabled = isDefaultPreset, BxEvent.dispatch($select, "input", {
ignoreOnChange: !0,
manualTrigger: !0
});
@@ -5322,12 +5281,294 @@ class ControllerShortcutsManagerDialog extends BaseProfileManagerDialog {
updatePreset() {
let newData = deepClone(this.BLANK_PRESET_DATA), button;
for (button in this.selectActions) {
- let [$select, _] = this.selectActions[button], action = $select.value;
+ let action = this.selectActions[button].value;
if (!action) continue;
newData.mapping[button] = action;
}
let preset = this.allPresets.data[this.currentPresetId];
- preset.data = newData, this.presetsDb.updatePreset(preset), StreamSettings.refreshControllerSettings();
+ preset.data = newData, this.presetsDb.updatePreset(preset);
+ }
+ onBeforeUnmount() {
+ StreamSettings.refreshControllerSettings(), super.onBeforeUnmount();
+ }
+}
+class BxDualNumberStepper extends HTMLInputElement {
+ controlValues;
+ controlMin;
+ controlMinDiff;
+ controlMax;
+ steps;
+ options;
+ onChange;
+ $text;
+ $rangeFrom;
+ $rangeTo;
+ $activeRange;
+ onRangeInput;
+ setValue;
+ getValue;
+ normalizeValue;
+ static create(key, values, options, onChange) {
+ options.suffix = options.suffix || "", options.disabled = !!options.disabled;
+ let $text, $rangeFrom, $rangeTo, self = CE("div", {
+ class: "bx-dual-number-stepper",
+ id: `bx_setting_${escapeCssSelector(key)}`
+ }, $text = CE("span"));
+ if (self.$text = $text, self.onChange = onChange, self.onRangeInput = BxDualNumberStepper.onRangeInput.bind(self), self.controlMin = options.min, self.controlMax = options.max, self.controlMinDiff = options.minDiff, self.options = options, self.steps = Math.max(options.steps || 1, 1), options.disabled) return self.disabled = !0, self;
+ return $rangeFrom = CE("input", {
+ type: "range",
+ min: self.controlMin,
+ max: self.controlMax,
+ step: self.steps,
+ tabindex: 0
+ }), $rangeTo = $rangeFrom.cloneNode(), self.$rangeFrom = $rangeFrom, self.$rangeTo = $rangeTo, self.$activeRange = $rangeFrom, self.getValue = BxDualNumberStepper.getValues.bind(self), self.setValue = BxDualNumberStepper.setValues.bind(self), $rangeFrom.addEventListener("input", self.onRangeInput), $rangeTo.addEventListener("input", self.onRangeInput), self.addEventListener("input", self.onRangeInput), self.append(CE("div", {}, $rangeFrom, $rangeTo)), BxDualNumberStepper.setValues.call(self, values), self.addEventListener("contextmenu", BxDualNumberStepper.onContextMenu), setNearby(self, {
+ focus: $rangeFrom,
+ orientation: "vertical"
+ }), Object.defineProperty(self, "value", {
+ get() {
+ return self.controlValues;
+ },
+ set(value) {
+ let from, to;
+ if (typeof value === "string") {
+ let tmp = value.split(",");
+ from = parseInt(tmp[0]), to = parseInt(tmp[1]);
+ } else if (Array.isArray(value)) [from, to] = value;
+ if (typeof from !== "undefined" && typeof to !== "undefined") BxDualNumberStepper.setValues.call(self, [from, to]);
+ }
+ }), self;
+ }
+ static setValues(values) {
+ let from, to;
+ if (values) [from, to] = BxDualNumberStepper.normalizeValues.call(this, values);
+ else from = this.controlMin, to = this.controlMax, values = [from, to];
+ this.controlValues = [from, to], this.$text.textContent = BxDualNumberStepper.updateTextValue.call(this), this.$rangeFrom.value = from.toString(), this.$rangeTo.value = to.toString();
+ let ratio = 100 / (this.controlMax - this.controlMin);
+ this.style.setProperty("--from", ratio * (from - this.controlMin) + "%"), this.style.setProperty("--to", ratio * (to - this.controlMin) + "%");
+ }
+ static getValues() {
+ return this.controlValues || [this.controlMin, this.controlMax];
+ }
+ static normalizeValues(values) {
+ let [from, to] = values;
+ if (this.$activeRange === this.$rangeFrom) to = Math.min(this.controlMax, to), from = Math.min(from, to), from = Math.min(to - this.controlMinDiff, from);
+ else from = Math.max(this.controlMin, from), to = Math.max(from, to), to = Math.max(this.controlMinDiff + from, to);
+ return to = Math.min(this.controlMax, to), from = Math.min(from, to), [from, to];
+ }
+ static onRangeInput(e) {
+ this.$activeRange = e.target;
+ let values = BxDualNumberStepper.normalizeValues.call(this, [parseInt(this.$rangeFrom.value), parseInt(this.$rangeTo.value)]);
+ if (BxDualNumberStepper.setValues.call(this, values), !e.ignoreOnChange && this.onChange) this.onChange(e, values);
+ }
+ static onContextMenu(e) {
+ e.preventDefault();
+ }
+ static updateTextValue() {
+ let values = this.controlValues, textContent = null;
+ if (this.options.customTextValue) textContent = this.options.customTextValue(values, this.controlMin, this.controlMax);
+ if (textContent === null) {
+ let [from, to] = values;
+ if (from === this.controlMin && to === this.controlMax) textContent = t("default");
+ else {
+ let pad = to.toString().length;
+ textContent = `${from.toString().padStart(pad)} - ${to.toString().padEnd(pad)}${this.options.suffix}`;
+ }
+ }
+ return textContent;
+ }
+}
+class ControllerCustomizationsManagerDialog extends BaseProfileManagerDialog {
+ static instance;
+ static getInstance = () => ControllerCustomizationsManagerDialog.instance ?? (ControllerCustomizationsManagerDialog.instance = new ControllerCustomizationsManagerDialog(t("controller-customization")));
+ $vibrationIntensity;
+ $leftTriggerRange;
+ $rightTriggerRange;
+ $leftStickDeadzone;
+ $rightStickDeadzone;
+ $btnDetect;
+ BLANK_PRESET_DATA = {
+ mapping: {},
+ settings: {
+ leftTriggerRange: [0, 100],
+ rightTriggerRange: [0, 100],
+ leftStickDeadzone: [0, 100],
+ rightStickDeadzone: [0, 100],
+ vibrationIntensity: 100
+ }
+ };
+ selectsMap = {};
+ selectsOrder = [];
+ isDetectingButton = !1;
+ detectIntervalId = null;
+ BUTTONS_ORDER = [
+ 0,
+ 1,
+ 2,
+ 3,
+ 12,
+ 15,
+ 13,
+ 14,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 104,
+ 204,
+ 17
+ ];
+ constructor(title) {
+ super(title, ControllerCustomizationsTable.getInstance());
+ this.render();
+ }
+ render() {
+ let isControllerFriendly = getPref("ui.controllerFriendly"), $rows = CE("div", { class: "bx-buttons-grid" }), $baseSelect = CE("select", { class: "bx-full-width" }, CE("option", { value: "" }, "---"), CE("option", { value: "false", _dataset: { label: "🚫" } }, isControllerFriendly ? "🚫" : t("off"))), $baseButtonSelect = $baseSelect.cloneNode(!0), $baseStickSelect = $baseSelect.cloneNode(!0), onButtonChanged = (e) => {
+ if (!e.ignoreOnChange) this.updatePreset();
+ }, boundUpdatePreset = this.updatePreset.bind(this);
+ for (let gamepadKey of this.BUTTONS_ORDER) {
+ if (gamepadKey === 17) continue;
+ let name = GamepadKeyName[gamepadKey][isControllerFriendly ? 1 : 0];
+ (gamepadKey === 104 || gamepadKey === 204 ? $baseStickSelect : $baseButtonSelect).appendChild(CE("option", {
+ value: gamepadKey,
+ _dataset: { label: GamepadKeyName[gamepadKey][1] }
+ }, name));
+ }
+ for (let gamepadKey of this.BUTTONS_ORDER) {
+ let [buttonName, buttonPrompt] = GamepadKeyName[gamepadKey], $clonedSelect = (gamepadKey === 104 || gamepadKey === 204 ? $baseStickSelect : $baseButtonSelect).cloneNode(!0);
+ $clonedSelect.querySelector(`option[value="${gamepadKey}"]`)?.remove();
+ let $select = BxSelectElement.create($clonedSelect);
+ $select.dataset.index = gamepadKey.toString(), $select.addEventListener("input", onButtonChanged), this.selectsMap[gamepadKey] = $select, this.selectsOrder.push(gamepadKey);
+ let $row = CE("div", {
+ class: "bx-controller-key-row",
+ _nearby: { orientation: "horizontal" }
+ }, CE("label", { title: buttonName }, buttonPrompt), $select);
+ $rows.append($row);
+ }
+ if (getPref("ui.controllerFriendly")) for (let i = 0;i < this.selectsOrder.length; i++) {
+ let $select = this.selectsMap[this.selectsOrder[i]], directions = {
+ 1: i - 2,
+ 3: i + 2,
+ 4: i - 1,
+ 2: i + 1
+ };
+ for (let dir in directions) {
+ let idx = directions[dir];
+ if (typeof this.selectsOrder[idx] === "undefined") continue;
+ let $targetSelect = this.selectsMap[this.selectsOrder[idx]];
+ setNearby($select, {
+ [dir]: $targetSelect
+ });
+ }
+ }
+ let params = {
+ min: 0,
+ minDiff: 1,
+ max: 100,
+ steps: 1
+ };
+ this.$content = CE("div", { class: "bx-controller-customizations-container" }, this.$btnDetect = createButton({
+ label: t("detect-controller-button"),
+ classes: ["bx-btn-detect"],
+ style: 4096 | 64 | 128,
+ onClick: () => {
+ this.startDetectingButton();
+ }
+ }), $rows, createSettingRow(t("vibration-intensity"), this.$vibrationIntensity = BxNumberStepper.create("controller_vibration_intensity", 50, 0, 100, {
+ steps: 10,
+ suffix: "%",
+ exactTicks: 20,
+ customTextValue: (value) => {
+ return value = parseInt(value), value === 0 ? t("off") : value + "%";
+ }
+ }, boundUpdatePreset)), createSettingRow(t("left-trigger-range"), this.$leftTriggerRange = BxDualNumberStepper.create("left-trigger-range", this.BLANK_PRESET_DATA.settings.leftTriggerRange, params, boundUpdatePreset)), createSettingRow(t("right-trigger-range"), this.$rightTriggerRange = BxDualNumberStepper.create("right-trigger-range", this.BLANK_PRESET_DATA.settings.rightTriggerRange, params, boundUpdatePreset)), createSettingRow(t("left-stick-deadzone"), this.$leftStickDeadzone = BxDualNumberStepper.create("left-stick-deadzone", this.BLANK_PRESET_DATA.settings.leftStickDeadzone, params, boundUpdatePreset)), createSettingRow(t("right-stick-deadzone"), this.$rightStickDeadzone = BxDualNumberStepper.create("right-stick-deadzone", this.BLANK_PRESET_DATA.settings.rightStickDeadzone, params, boundUpdatePreset)));
+ }
+ startDetectingButton() {
+ this.isDetectingButton = !0;
+ let { $btnDetect } = this;
+ $btnDetect.classList.add("bx-monospaced", "bx-blink-me"), $btnDetect.disabled = !0;
+ let count = 4;
+ $btnDetect.textContent = `[${count}] ${t("press-any-button")}`, this.detectIntervalId = window.setInterval(() => {
+ if (count -= 1, count === 0) {
+ this.stopDetectingButton(), $btnDetect.focus();
+ return;
+ }
+ $btnDetect.textContent = `[${count}] ${t("press-any-button")}`;
+ }, 1000);
+ }
+ stopDetectingButton() {
+ let { $btnDetect } = this;
+ $btnDetect.classList.remove("bx-monospaced", "bx-blink-me"), $btnDetect.textContent = t("detect-controller-button"), $btnDetect.disabled = !1, this.isDetectingButton = !1, this.detectIntervalId && window.clearInterval(this.detectIntervalId), this.detectIntervalId = null;
+ }
+ async onBeforeMount() {
+ this.stopDetectingButton(), super.onBeforeMount(...arguments);
+ }
+ onBeforeUnmount() {
+ this.stopDetectingButton(), StreamSettings.refreshControllerSettings(), super.onBeforeUnmount();
+ }
+ handleGamepad(button) {
+ if (!this.isDetectingButton) return super.handleGamepad(button);
+ if (button in this.BUTTONS_ORDER) {
+ this.stopDetectingButton();
+ let $select = this.selectsMap[button], $label = $select.previousElementSibling;
+ if ($label.addEventListener("animationend", () => {
+ $label.classList.remove("bx-horizontal-shaking");
+ }, { once: !0 }), $label.classList.add("bx-horizontal-shaking"), getPref("ui.controllerFriendly"))
+ this.dialogManager.focus($select);
+ }
+ return !0;
+ }
+ switchPreset(id) {
+ let preset = this.allPresets.data[id];
+ if (!preset) {
+ this.currentPresetId = 0;
+ return;
+ }
+ let {
+ $btnDetect,
+ $vibrationIntensity,
+ $leftStickDeadzone,
+ $rightStickDeadzone,
+ $leftTriggerRange,
+ $rightTriggerRange,
+ selectsMap
+ } = this, presetData = preset.data;
+ this.currentPresetId = id;
+ let isDefaultPreset = id <= 0;
+ this.updateButtonStates(), $btnDetect.classList.toggle("bx-gone", isDefaultPreset);
+ let buttonIndex;
+ for (buttonIndex in selectsMap) {
+ buttonIndex = buttonIndex;
+ let $select = selectsMap[buttonIndex];
+ if (!$select) continue;
+ let mappedButton = presetData.mapping[buttonIndex];
+ $select.value = typeof mappedButton === "undefined" ? "" : mappedButton.toString(), $select.disabled = isDefaultPreset, BxEvent.dispatch($select, "input", {
+ ignoreOnChange: !0,
+ manualTrigger: !0
+ });
+ }
+ presetData.settings = Object.assign(this.BLANK_PRESET_DATA.settings, presetData.settings), $vibrationIntensity.value = presetData.settings.vibrationIntensity.toString(), $vibrationIntensity.dataset.disabled = isDefaultPreset.toString(), $leftStickDeadzone.dataset.disabled = $rightStickDeadzone.dataset.disabled = $leftTriggerRange.dataset.disabled = $rightTriggerRange.dataset.disabled = isDefaultPreset.toString(), $leftStickDeadzone.setValue(presetData.settings.leftStickDeadzone), $rightStickDeadzone.setValue(presetData.settings.rightStickDeadzone), $leftTriggerRange.setValue(presetData.settings.leftTriggerRange), $rightTriggerRange.setValue(presetData.settings.rightTriggerRange);
+ }
+ updatePreset() {
+ let newData = deepClone(this.BLANK_PRESET_DATA), gamepadKey;
+ for (gamepadKey in this.selectsMap) {
+ let value = this.selectsMap[gamepadKey].value;
+ if (!value) continue;
+ let mapTo = value === "false" ? !1 : parseInt(value);
+ newData.mapping[gamepadKey] = mapTo;
+ }
+ Object.assign(newData.settings, {
+ vibrationIntensity: parseInt(this.$vibrationIntensity.value),
+ leftStickDeadzone: this.$leftStickDeadzone.getValue(),
+ rightStickDeadzone: this.$rightStickDeadzone.getValue(),
+ leftTriggerRange: this.$leftTriggerRange.getValue(),
+ rightTriggerRange: this.$rightTriggerRange.getValue()
+ });
+ let preset = this.allPresets.data[this.currentPresetId];
+ preset.data = newData, this.presetsDb.updatePreset(preset);
}
}
class ControllerExtraSettings extends HTMLElement {
@@ -5335,7 +5576,7 @@ class ControllerExtraSettings extends HTMLElement {
controllerIds;
$selectControllers;
$selectShortcuts;
- $vibrationIntensity;
+ $selectCustomization;
updateLayout;
switchController;
getCurrentControllerId;
@@ -5346,39 +5587,44 @@ class ControllerExtraSettings extends HTMLElement {
});
$container.updateLayout = ControllerExtraSettings.updateLayout.bind($container), $container.switchController = ControllerExtraSettings.switchController.bind($container), $container.getCurrentControllerId = ControllerExtraSettings.getCurrentControllerId.bind($container), $container.saveSettings = ControllerExtraSettings.saveSettings.bind($container);
let $selectControllers = BxSelectElement.create(CE("select", {
+ class: "bx-full-width",
autocomplete: "off",
_on: {
input: (e) => {
$container.switchController($selectControllers.value);
}
}
- }));
- $selectControllers.classList.add("bx-full-width");
- let $selectShortcuts = BxSelectElement.create(CE("select", {
+ })), $selectShortcuts = BxSelectElement.create(CE("select", {
autocomplete: "off",
- _on: {
- input: $container.saveSettings
- }
- })), $vibrationIntensity = BxNumberStepper.create("controller_vibration_intensity", 50, 0, 100, {
- steps: 10,
- suffix: "%",
- exactTicks: 20,
- customTextValue: (value) => {
- return value = parseInt(value), value === 0 ? t("off") : value + "%";
- }
- }, $container.saveSettings);
- return $container.append(CE("span", {}, t("no-controllers-connected")), CE("div", { class: "bx-controller-extra-wrapper" }, $selectControllers, CE("div", { class: "bx-sub-content-box" }, createSettingRow(t("controller-shortcuts-in-game"), CE("div", {
+ _on: { input: $container.saveSettings }
+ })), $selectCustomization = BxSelectElement.create(CE("select", {
+ autocomplete: "off",
+ _on: { input: $container.saveSettings }
+ }));
+ return $container.append(CE("span", {}, t("no-controllers-connected")), CE("div", { class: "bx-controller-extra-wrapper" }, $selectControllers, CE("div", { class: "bx-sub-content-box" }, createSettingRow(t("in-game-controller-shortcuts"), CE("div", {
class: "bx-preset-row",
- _nearby: {
- orientation: "horizontal"
- }
+ _nearby: { orientation: "horizontal" }
}, $selectShortcuts, createButton({
- label: t("manage"),
- style: 64,
+ title: t("manage"),
+ icon: BxIcon.MANAGE,
+ style: 64 | 1 | 512,
onClick: () => ControllerShortcutsManagerDialog.getInstance().show({
id: parseInt($container.$selectShortcuts.value)
})
- })), { multiLines: !0 }), createSettingRow(t("vibration-intensity"), $vibrationIntensity)))), $container.$selectControllers = $selectControllers, $container.$selectShortcuts = $selectShortcuts, $container.$vibrationIntensity = $vibrationIntensity, $container.updateLayout(), window.addEventListener("gamepadconnected", $container.updateLayout), window.addEventListener("gamepaddisconnected", $container.updateLayout), this.onMountedCallbacks.push(() => {
+ })), { multiLines: !0 }), createSettingRow(t("in-game-controller-customization"), CE("div", {
+ class: "bx-preset-row",
+ _nearby: { orientation: "horizontal" }
+ }, $selectCustomization, createButton({
+ title: t("manage"),
+ icon: BxIcon.MANAGE,
+ style: 64 | 1 | 512,
+ onClick: () => ControllerCustomizationsManagerDialog.getInstance().show({
+ id: $container.$selectCustomization.value ? parseInt($container.$selectCustomization.value) : null
+ })
+ })), {
+ multiLines: !0,
+ $note: CE("div", { class: "bx-settings-dialog-note" }, "ⓘ " + t("slightly-increase-input-latency"))
+ })))), $container.$selectControllers = $selectControllers, $container.$selectShortcuts = $selectShortcuts, $container.$selectCustomization = $selectCustomization, $container.updateLayout(), window.addEventListener("gamepadconnected", $container.updateLayout), window.addEventListener("gamepaddisconnected", $container.updateLayout), this.onMountedCallbacks.push(() => {
$container.updateLayout();
}), $container;
}
@@ -5393,16 +5639,18 @@ class ControllerExtraSettings extends HTMLElement {
this.$selectControllers.appendChild($fragment);
let allShortcutPresets = await ControllerShortcutsTable.getInstance().getPresets();
renderPresetsList(this.$selectShortcuts, allShortcutPresets, null, { addOffValue: !0 });
+ let allCustomizationPresets = await ControllerCustomizationsTable.getInstance().getPresets();
+ renderPresetsList(this.$selectCustomization, allCustomizationPresets, null, { addOffValue: !0 });
for (let name of this.controllerIds) {
let $option = CE("option", { value: name }, name);
$fragment.appendChild($option);
}
- BxEvent.dispatch(this.$selectControllers, "input");
+ BxEvent.dispatch(this.$selectControllers, "input"), calculateSelectBoxes(this);
}
static async switchController(id) {
if (this.currentControllerId = id, !this.getCurrentControllerId()) return;
let controllerSettings = await ControllerSettingsTable.getInstance().getControllerData(this.currentControllerId);
- this.$selectShortcuts.value = controllerSettings.shortcutPresetId.toString(), this.$vibrationIntensity.value = controllerSettings.vibrationIntensity.toString();
+ this.$selectShortcuts.value = controllerSettings.shortcutPresetId.toString(), this.$selectCustomization.value = controllerSettings.customizationPresetId.toString();
}
static getCurrentControllerId() {
if (this.currentControllerId) {
@@ -5419,7 +5667,7 @@ class ControllerExtraSettings extends HTMLElement {
id: this.currentControllerId,
data: {
shortcutPresetId: parseInt(this.$selectShortcuts.value),
- vibrationIntensity: parseInt(this.$vibrationIntensity.value)
+ customizationPresetId: parseInt(this.$selectCustomization.value)
}
};
await ControllerSettingsTable.getInstance().put(data), StreamSettings.refreshControllerSettings();
@@ -5525,7 +5773,7 @@ class SuggestionsSetting {
_nearby: {
orientation: "vertical"
}
- }, BxSelectElement.create($select, !0), $suggestedSettings, $btnApply, BX_FLAGS.DeviceInfo.deviceType.includes("android") && CE("a", {
+ }, BxSelectElement.create($select), $suggestedSettings, $btnApply, BX_FLAGS.DeviceInfo.deviceType.includes("android") && CE("a", {
class: "bx-suggest-link bx-focusable",
href: "https://better-xcloud.github.io/guide/android-webview-tweaks/",
target: "_blank",
@@ -5764,9 +6012,7 @@ class MkbMappingManagerDialog extends BaseProfileManagerDialog {
}), $elm.dataset.buttonIndex = buttonIndex.toString(), $elm.dataset.keySlot = i.toString(), $elm.addEventListener("mouseup", this.onBindingKey), $fragment.appendChild($elm), this.allKeyElements.push($elm);
let $keyRow = CE("div", {
class: "bx-mkb-key-row",
- _nearby: {
- orientation: "horizontal"
- }
+ _nearby: { orientation: "horizontal" }
}, CE("label", { title: buttonName }, buttonPrompt), $fragment);
$rows.appendChild($keyRow);
}
@@ -5818,7 +6064,10 @@ class MkbMappingManagerDialog extends BaseProfileManagerDialog {
name: oldPreset.name,
data: presetData
};
- this.presetsDb.updatePreset(newPreset), this.allPresets.data[this.currentPresetId] = newPreset, StreamSettings.refreshMkbSettings();
+ this.presetsDb.updatePreset(newPreset), this.allPresets.data[this.currentPresetId] = newPreset;
+ }
+ onBeforeUnmount() {
+ StreamSettings.refreshMkbSettings(), super.onBeforeUnmount();
}
}
class KeyboardShortcutsManagerDialog extends BaseProfileManagerDialog {
@@ -5895,7 +6144,10 @@ class KeyboardShortcutsManagerDialog extends BaseProfileManagerDialog {
name: oldPreset.name,
data: presetData
};
- this.presetsDb.updatePreset(newPreset), this.allPresets.data[this.currentPresetId] = newPreset, StreamSettings.refreshKeyboardShortcuts();
+ this.presetsDb.updatePreset(newPreset), this.allPresets.data[this.currentPresetId] = newPreset;
+ }
+ onBeforeUnmount() {
+ StreamSettings.refreshKeyboardShortcuts(), super.onBeforeUnmount();
}
}
class MkbExtraSettings extends HTMLElement {
@@ -5925,8 +6177,9 @@ class MkbExtraSettings extends HTMLElement {
orientation: "horizontal"
}
}, $mappingPresets, createButton({
- label: t("manage"),
- style: 64,
+ title: t("manage"),
+ icon: BxIcon.MANAGE,
+ style: 64 | 1 | 512,
onClick: () => MkbMappingManagerDialog.getInstance().show({
id: parseInt($container.$mappingPresets.value)
})
@@ -5934,14 +6187,15 @@ class MkbExtraSettings extends HTMLElement {
createSettingRow(t("virtual-controller-slot"), SettingElement.fromPref("mkb.p1.slot", STORAGE.Global, () => {
EmulatedMkbHandler.getInstance()?.updateGamepadSlots();
}))
- ] : [], createSettingRow(t("keyboard-shortcuts-in-game"), CE("div", {
+ ] : [], createSettingRow(t("in-game-keyboard-shortcuts"), CE("div", {
class: "bx-preset-row",
_nearby: {
orientation: "horizontal"
}
}, $shortcutsPresets, createButton({
- label: t("manage"),
- style: 64,
+ title: t("manage"),
+ icon: BxIcon.MANAGE,
+ style: 64 | 1 | 512,
onClick: () => KeyboardShortcutsManagerDialog.getInstance().show({
id: parseInt($container.$shortcutsPresets.value)
})
@@ -6293,20 +6547,6 @@ class SettingsDialog extends NavigationDialog {
}]
}];
TAB_CONTROLLER_ITEMS = [
- STATES.browser.capabilities.deviceVibration && {
- group: "device",
- label: t("device"),
- items: [{
- pref: "deviceVibration.mode",
- multiLines: !0,
- unsupported: !STATES.browser.capabilities.deviceVibration,
- onChange: () => StreamSettings.refreshControllerSettings()
- }, {
- pref: "deviceVibration.intensity",
- unsupported: !STATES.browser.capabilities.deviceVibration,
- onChange: () => StreamSettings.refreshControllerSettings()
- }]
- },
{
group: "controller",
label: t("controller"),
@@ -6358,6 +6598,20 @@ class SettingsDialog extends NavigationDialog {
});
}
}]
+ },
+ STATES.browser.capabilities.deviceVibration && {
+ group: "device",
+ label: t("device"),
+ items: [{
+ pref: "deviceVibration.mode",
+ multiLines: !0,
+ unsupported: !STATES.browser.capabilities.deviceVibration,
+ onChange: () => StreamSettings.refreshControllerSettings()
+ }, {
+ pref: "deviceVibration.intensity",
+ unsupported: !STATES.browser.capabilities.deviceVibration,
+ onChange: () => StreamSettings.refreshControllerSettings()
+ }]
}
];
TAB_MKB_ITEMS = () => [
@@ -6498,9 +6752,8 @@ class SettingsDialog extends NavigationDialog {
}
let $child, children = Array.from(this.$tabContents.children);
for ($child of children)
- if ($child.dataset.tabGroup === $svg.dataset.group) {
- if ($child.classList.remove("bx-gone"), getPref("ui.controllerFriendly")) this.dialogManager.calculateSelectBoxes($child);
- } else $child.classList.add("bx-gone");
+ if ($child.dataset.tabGroup === $svg.dataset.group) $child.classList.remove("bx-gone"), calculateSelectBoxes($child);
+ else $child.classList.add("bx-gone");
for (let $child2 of Array.from(this.$tabs.children))
$child2.classList.remove("bx-active");
$svg.classList.add("bx-active");
@@ -6534,7 +6787,6 @@ class SettingsDialog extends NavigationDialog {
}
}, $control = CE("select", {
id: `bx_setting_${escapeCssSelector(setting.pref)}`,
- title: setting.label,
tabindex: 0
});
$control.name = $control.id, $control.addEventListener("input", (e) => {
@@ -6633,7 +6885,7 @@ class SettingsDialog extends NavigationDialog {
label = createButton({
label,
url: "https://github.com/redphx/better-xcloud/releases",
- style: 2048 | 16 | 64
+ style: 4096 | 16 | 64
});
}
if (label) {
@@ -6904,7 +7156,7 @@ class TrueAchievements {
label: t("true-achievements"),
url: "#",
icon: BxIcon.TRUE_ACHIEVEMENTS,
- style: 64 | 8 | 128 | 4096,
+ style: 64 | 8 | 128 | 8192,
onClick: this.onClick
}), this.$button = createButton({
label: t("true-achievements"),
@@ -7218,7 +7470,7 @@ class HeaderSection {
classes: ["bx-header-remote-play-button", "bx-gone"],
icon: BxIcon.REMOTE_PLAY,
title: t("remote-play"),
- style: 8 | 64 | 1024,
+ style: 8 | 64 | 2048,
onClick: (e) => RemotePlayManager.getInstance()?.togglePopup()
}), this.$btnSettings = createButton({
classes: ["bx-header-settings-button"],
@@ -8164,7 +8416,7 @@ function getOsNameFromResolution(resolution) {
return osName;
}
function addCss() {
- let css = ':root{--bx-title-font:Bahnschrift,Arial,Helvetica,sans-serif;--bx-title-font-semibold:Bahnschrift Semibold,Arial,Helvetica,sans-serif;--bx-normal-font:"Segoe UI",Arial,Helvetica,sans-serif;--bx-monospaced-font:Consolas,"Courier New",Courier,monospace;--bx-promptfont-font:promptfont;--bx-button-height:40px;--bx-default-button-color:#2d3036;--bx-default-button-rgb:45,48,54;--bx-default-button-hover-color:#515863;--bx-default-button-hover-rgb:81,88,99;--bx-default-button-active-color:#222428;--bx-default-button-active-rgb:34,36,40;--bx-default-button-disabled-color:#8e8e8e;--bx-default-button-disabled-rgb:142,142,142;--bx-primary-button-color:#008746;--bx-primary-button-rgb:0,135,70;--bx-primary-button-hover-color:#04b358;--bx-primary-button-hover-rgb:4,179,88;--bx-primary-button-active-color:#044e2a;--bx-primary-button-active-rgb:4,78,42;--bx-primary-button-disabled-color:#448262;--bx-primary-button-disabled-rgb:68,130,98;--bx-warning-button-color:#c16e04;--bx-warning-button-rgb:193,110,4;--bx-warning-button-hover-color:#fa9005;--bx-warning-button-hover-rgb:250,144,5;--bx-warning-button-active-color:#965603;--bx-warning-button-active-rgb:150,86,3;--bx-warning-button-disabled-color:#a2816c;--bx-warning-button-disabled-rgb:162,129,108;--bx-danger-button-color:#c10404;--bx-danger-button-rgb:193,4,4;--bx-danger-button-hover-color:#e61d1d;--bx-danger-button-hover-rgb:230,29,29;--bx-danger-button-active-color:#a26c6c;--bx-danger-button-active-rgb:162,108,108;--bx-danger-button-disabled-color:#df5656;--bx-danger-button-disabled-rgb:223,86,86;--bx-fullscreen-text-z-index:9999;--bx-toast-z-index:6000;--bx-key-binding-dialog-z-index:5010;--bx-key-binding-dialog-overlay-z-index:5000;--bx-stats-bar-z-index:4010;--bx-navigation-dialog-z-index:3010;--bx-navigation-dialog-overlay-z-index:3000;--bx-mkb-pointer-lock-msg-z-index:2000;--bx-game-bar-z-index:1000;--bx-screenshot-animation-z-index:200;--bx-wait-time-box-z-index:100}@font-face{font-family:\'promptfont\';src:url("https://redphx.github.io/better-xcloud/fonts/promptfont.otf")}div[class^=HUDButton-module__hiddenContainer] ~ div:not([class^=HUDButton-module__hiddenContainer]){opacity:0;pointer-events:none !important;position:absolute;top:-9999px;left:-9999px}@media screen and (max-width:640px){header a[href="/play"]{display:none}}.bx-full-width{width:100% !important}.bx-full-height{height:100% !important}.bx-no-scroll{overflow:hidden !important}.bx-hide-scroll-bar{scrollbar-width:none}.bx-hide-scroll-bar::-webkit-scrollbar{display:none}.bx-gone{display:none !important}.bx-offscreen{position:absolute !important;top:-9999px !important;left:-9999px !important;visibility:hidden !important}.bx-hidden{visibility:hidden !important}.bx-invisible{opacity:0}.bx-unclickable{pointer-events:none}.bx-pixel{width:1px !important;height:1px !important}.bx-no-margin{margin:0 !important}.bx-no-padding{padding:0 !important}.bx-prompt{font-family:var(--bx-promptfont-font) !important}.bx-line-through{text-decoration:line-through !important}.bx-normal-case{text-transform:none !important}.bx-normal-link{text-transform:none !important;text-align:left !important;font-weight:400 !important;font-family:var(--bx-normal-font) !important}select[multiple],select[multiple]:focus{overflow:auto;border:none}select[multiple] option,select[multiple]:focus option{padding:4px 6px}select[multiple] option:checked,select[multiple]:focus option:checked{background:#1a7bc0 linear-gradient(0deg,#1a7bc0 0%,#1a7bc0 100%)}select[multiple] option:checked::before,select[multiple]:focus option:checked::before{content:\'☑️\';font-size:12px;display:inline-block;margin-right:6px;height:100%;line-height:100%;vertical-align:middle}#headerArea,#uhfSkipToMain,.uhf-footer{display:none}div[class*=NotFocusedDialog]{position:absolute !important;top:-9999px !important;left:-9999px !important;width:0 !important;height:0 !important}#game-stream video:not([src]){visibility:hidden}div[class*=SupportedInputsBadge]:not(:has(:nth-child(2))),div[class*=SupportedInputsBadge] svg:first-of-type{display:none}.bx-game-tile-wait-time{position:absolute;top:0;left:0;z-index:1;background:rgba(0,0,0,0.5);display:flex;border-radius:4px 0 4px 0;align-items:center;padding:4px 8px}.bx-game-tile-wait-time svg{width:14px;height:16px;margin-right:2px}.bx-game-tile-wait-time span{display:inline-block;height:16px;line-height:16px;font-size:12px;font-weight:bold;margin-left:2px}.bx-game-tile-wait-time[data-duration=short]{background-color:rgba(0,133,133,0.75)}.bx-game-tile-wait-time[data-duration=medium]{background-color:rgba(213,133,0,0.75)}.bx-game-tile-wait-time[data-duration=long]{background-color:rgba(150,0,0,0.75)}.bx-fullscreen-text{position:fixed;top:0;bottom:0;left:0;right:0;background:rgba(0,0,0,0.8);z-index:var(--bx-fullscreen-text-z-index);line-height:100vh;color:#fff;text-align:center;font-weight:400;font-family:var(--bx-normal-font);font-size:1.3rem;user-select:none;-webkit-user-select:none}#root section[class*=DeviceCodePage-module__page]{margin-left:20px !important;margin-right:20px !important;margin-top:20px !important;max-width:800px !important}#root div[class*=DeviceCodePage-module__back]{display:none}.bx-blink-me{animation:bx-blinker 1s linear infinite}@-moz-keyframes bx-blinker{100%{opacity:0}}@-webkit-keyframes bx-blinker{100%{opacity:0}}@-o-keyframes bx-blinker{100%{opacity:0}}@keyframes bx-blinker{100%{opacity:0}}.bx-button{--button-rgb:var(--bx-default-button-rgb);--button-hover-rgb:var(--bx-default-button-hover-rgb);--button-active-rgb:var(--bx-default-button-active-rgb);--button-disabled-rgb:var(--bx-default-button-disabled-rgb);background-color:rgb(var(--button-rgb));user-select:none;-webkit-user-select:none;color:#fff;font-family:var(--bx-title-font-semibold);font-size:14px;border:none;font-weight:400;height:var(--bx-button-height);border-radius:4px;padding:0 8px;text-transform:uppercase;cursor:pointer;overflow:hidden}.bx-button:not([disabled]):active{background-color:rgb(var(--button-active-rgb))}.bx-button:focus{outline:none !important}.bx-button:not([disabled]):not(:active):hover,.bx-button:not([disabled]):not(:active).bx-focusable:focus{background-color:rgb(var(--button-hover-rgb))}.bx-button:disabled{cursor:default;background-color:rgb(var(--button-disabled-rgb))}.bx-button.bx-ghost{background-color:transparent}.bx-button.bx-ghost:not([disabled]):not(:active):hover,.bx-button.bx-ghost:not([disabled]):not(:active).bx-focusable:focus{background-color:rgb(var(--button-hover-rgb))}.bx-button.bx-primary{--button-rgb:var(--bx-primary-button-rgb)}.bx-button.bx-primary:not([disabled]):active{--button-active-rgb:var(--bx-primary-button-active-rgb)}.bx-button.bx-primary:not([disabled]):not(:active):hover,.bx-button.bx-primary:not([disabled]):not(:active).bx-focusable:focus{--button-hover-rgb:var(--bx-primary-button-hover-rgb)}.bx-button.bx-primary:disabled{--button-disabled-rgb:var(--bx-primary-button-disabled-rgb)}.bx-button.bx-warning{--button-rgb:var(--bx-warning-button-rgb)}.bx-button.bx-warning:not([disabled]):active{--button-active-rgb:var(--bx-warning-button-active-rgb)}.bx-button.bx-warning:not([disabled]):not(:active):hover,.bx-button.bx-warning:not([disabled]):not(:active).bx-focusable:focus{--button-hover-rgb:var(--bx-warning-button-hover-rgb)}.bx-button.bx-warning:disabled{--button-disabled-rgb:var(--bx-warning-button-disabled-rgb)}.bx-button.bx-danger{--button-rgb:var(--bx-danger-button-rgb)}.bx-button.bx-danger:not([disabled]):active{--button-active-rgb:var(--bx-danger-button-active-rgb)}.bx-button.bx-danger:not([disabled]):not(:active):hover,.bx-button.bx-danger:not([disabled]):not(:active).bx-focusable:focus{--button-hover-rgb:var(--bx-danger-button-hover-rgb)}.bx-button.bx-danger:disabled{--button-disabled-rgb:var(--bx-danger-button-disabled-rgb)}.bx-button.bx-frosted{--button-alpha:.2;background-color:rgba(var(--button-rgb), var(--button-alpha));backdrop-filter:blur(4px) brightness(1.5)}.bx-button.bx-frosted:not([disabled]):not(:active):hover,.bx-button.bx-frosted:not([disabled]):not(:active).bx-focusable:focus{background-color:rgba(var(--button-hover-rgb), var(--button-alpha))}.bx-button.bx-drop-shadow{box-shadow:0 0 4px rgba(0,0,0,0.502)}.bx-button.bx-tall{height:calc(var(--bx-button-height) * 1.5) !important}.bx-button.bx-circular{border-radius:var(--bx-button-height);width:var(--bx-button-height);height:var(--bx-button-height)}.bx-button svg{display:inline-block;width:16px;height:var(--bx-button-height)}.bx-button span{display:inline-block;line-height:var(--bx-button-height);vertical-align:middle;color:#fff;overflow:hidden;white-space:nowrap}.bx-button span:not(:only-child){margin-left:10px}.bx-button.bx-button-multi-lines{height:auto;text-align:left;padding:10px 0}.bx-button.bx-button-multi-lines span{line-height:unset;display:block}.bx-button.bx-button-multi-lines span:last-of-type{text-transform:none;font-weight:normal;font-family:"Segoe Sans Variable Text";font-size:12px;margin-top:4px}.bx-focusable{position:relative;overflow:visible}.bx-focusable::after{border:2px solid transparent;border-radius:10px}.bx-focusable:focus::after{content:\'\';border-color:#fff;position:absolute;top:-6px;left:-6px;right:-6px;bottom:-6px}html[data-active-input=touch] .bx-focusable:focus::after,html[data-active-input=mouse] .bx-focusable:focus::after{border-color:transparent !important}.bx-focusable.bx-circular::after{border-radius:var(--bx-button-height)}a.bx-button{display:inline-block}a.bx-button.bx-full-width{text-align:center}button.bx-inactive{pointer-events:none;opacity:.2;background:transparent !important}.bx-header-remote-play-button{height:auto;margin-right:8px !important}.bx-header-remote-play-button svg{width:24px;height:24px}.bx-header-settings-button{line-height:30px;font-size:14px;text-transform:uppercase;position:relative}.bx-header-settings-button[data-update-available]::before{content:\'🌟\' !important;line-height:var(--bx-button-height);display:inline-block;margin-left:4px}.bx-key-binding-dialog-overlay{position:fixed;inset:0;z-index:var(--bx-key-binding-dialog-overlay-z-index);background:#000;opacity:50%}.bx-key-binding-dialog{display:flex;flex-flow:column;max-height:90vh;position:fixed;top:50%;left:50%;margin-right:-50%;transform:translate(-50%,-50%);min-width:420px;padding:16px;border-radius:8px;z-index:var(--bx-key-binding-dialog-z-index);background:#1a1b1e;color:#fff;font-weight:400;font-size:16px;font-family:var(--bx-normal-font);box-shadow:0 0 6px #000;user-select:none;-webkit-user-select:none}.bx-key-binding-dialog *:focus{outline:none !important}.bx-key-binding-dialog h2{margin-bottom:12px;color:#fff;display:block;font-family:var(--bx-title-font);font-size:32px;font-weight:400;line-height:var(--bx-button-height)}.bx-key-binding-dialog > div{overflow:auto;padding:2px 0}.bx-key-binding-dialog > button{padding:8px 32px;margin:10px auto 0;border:none;border-radius:4px;display:block;background-color:#2d3036;text-align:center;color:#fff;text-transform:uppercase;font-family:var(--bx-title-font);font-weight:400;line-height:18px;font-size:14px}@media (hover:hover){.bx-key-binding-dialog > button:hover{background-color:#515863}}.bx-key-binding-dialog > button:focus{background-color:#515863}.bx-key-binding-dialog ul{margin-bottom:1rem}.bx-key-binding-dialog ul li{display:none}.bx-key-binding-dialog ul[data-flags*="[1]"] > li[data-flag="1"],.bx-key-binding-dialog ul[data-flags*="[2]"] > li[data-flag="2"],.bx-key-binding-dialog ul[data-flags*="[4]"] > li[data-flag="4"],.bx-key-binding-dialog ul[data-flags*="[8]"] > li[data-flag="8"]{display:list-item}@media screen and (max-width:450px){.bx-key-binding-dialog{min-width:100%}}.bx-navigation-dialog{position:absolute;z-index:var(--bx-navigation-dialog-z-index);font-family:var(--bx-title-font)}.bx-navigation-dialog *:focus{outline:none !important}.bx-navigation-dialog select:disabled{-webkit-appearance:none;text-align-last:right;text-align:right;color:#fff;background:#131416;border:none;border-radius:4px;padding:0 5px}.bx-navigation-dialog-overlay{position:fixed;background:rgba(11,11,11,0.89);top:0;left:0;right:0;bottom:0;z-index:var(--bx-navigation-dialog-overlay-z-index)}.bx-navigation-dialog-overlay[data-is-playing="true"]{background:transparent}.bx-centered-dialog{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);color:#fff;background:#1a1b1e;border-radius:10px;width:450px;max-width:calc(100vw - 20px);margin:0 0 0 auto;padding:16px;max-height:95vh;flex-direction:column;overflow:hidden;display:flex;flex-direction:column}.bx-centered-dialog .bx-dialog-title{display:flex;flex-direction:row;align-items:center;margin-bottom:10px}.bx-centered-dialog .bx-dialog-title p{padding:0;margin:0;flex:1;font-size:1.2rem;font-weight:bold}.bx-centered-dialog .bx-dialog-title button{flex-shrink:0}.bx-centered-dialog .bx-dialog-content{flex:1;overflow:auto;overflow-x:hidden}.bx-centered-dialog .bx-dialog-preset-tools{display:flex;margin-bottom:12px;gap:6px}.bx-centered-dialog .bx-dialog-preset-tools select{flex:1}.bx-centered-dialog .bx-default-preset-note{font-size:12px;font-style:italic;text-align:center;margin-bottom:10px}.bx-centered-dialog input,.bx-settings-dialog input{accent-color:var(--bx-primary-button-color)}.bx-centered-dialog input:focus,.bx-settings-dialog input:focus{accent-color:var(--bx-danger-button-color)}.bx-centered-dialog select:disabled,.bx-settings-dialog select:disabled{-webkit-appearance:none;background:transparent;text-align-last:right;border:none;color:#fff}.bx-centered-dialog select option:disabled,.bx-settings-dialog select option:disabled{display:none}.bx-centered-dialog input[type=checkbox]:focus,.bx-settings-dialog input[type=checkbox]:focus,.bx-centered-dialog select:focus,.bx-settings-dialog select:focus{filter:drop-shadow(1px 0 0 #fff) drop-shadow(-1px 0 0 #fff) drop-shadow(0 1px 0 #fff) drop-shadow(0 -1px 0 #fff)}.bx-centered-dialog a,.bx-settings-dialog a{color:#1c9d1c;text-decoration:none}.bx-centered-dialog a:hover,.bx-settings-dialog a:hover,.bx-centered-dialog a:focus,.bx-settings-dialog a:focus{color:#5dc21e}.bx-centered-dialog label,.bx-settings-dialog label{margin:0}.bx-controller-shortcuts-manager-container .bx-shortcut-note{margin-top:10px;font-size:14px;text-align:center}.bx-controller-shortcuts-manager-container .bx-shortcut-row{display:flex;gap:10px;margin-bottom:10px;align-items:center}.bx-controller-shortcuts-manager-container .bx-shortcut-row label.bx-prompt{flex-shrink:0;font-size:32px;margin:0}.bx-controller-shortcuts-manager-container .bx-shortcut-row label.bx-prompt::first-letter{letter-spacing:6px}.bx-controller-shortcuts-manager-container .bx-shortcut-row .bx-shortcut-actions{flex:1;position:relative}.bx-controller-shortcuts-manager-container .bx-shortcut-row .bx-shortcut-actions select{width:100%;height:100%;min-height:38px;display:block}.bx-controller-shortcuts-manager-container .bx-shortcut-row .bx-shortcut-actions select:first-of-type{position:absolute;top:0;left:0}.bx-controller-shortcuts-manager-container .bx-shortcut-row .bx-shortcut-actions select:last-of-type{opacity:0;z-index:calc(var(--bx-settings-z-index) + 1)}.bx-controller-shortcuts-manager-container select:disabled{text-align:left;text-align-last:left}.bx-keyboard-shortcuts-manager-container{display:flex;flex-direction:column;gap:16px}.bx-keyboard-shortcuts-manager-container fieldset{background:#2a2a2a;border:1px solid #2a2a2a;border-radius:4px;padding:4px}.bx-keyboard-shortcuts-manager-container legend{width:auto;padding:4px 8px;margin:0 4px 4px;background:#004f87;box-shadow:0 2px 0 #071e3d;border-radius:4px;font-size:14px;font-weight:bold;text-transform:uppercase}.bx-keyboard-shortcuts-manager-container .bx-settings-row{background:none;padding:10px}.bx-settings-dialog{display:flex;position:fixed;top:0;right:0;bottom:0;opacity:.98;user-select:none;-webkit-user-select:none}.bx-settings-dialog .bx-focusable::after{border-radius:4px}.bx-settings-dialog .bx-focusable:focus::after{top:0;left:0;right:0;bottom:0}.bx-settings-dialog .bx-settings-reload-note{font-size:.8rem;display:block;padding:8px;font-style:italic;font-weight:normal;height:var(--bx-button-height)}.bx-settings-tabs-container{position:fixed;width:48px;max-height:100vh;display:flex;flex-direction:column}.bx-settings-tabs-container > div:last-of-type{display:flex;flex-direction:column;align-items:end}.bx-settings-tabs-container > div:last-of-type button{flex-shrink:0;border-top-right-radius:0;border-bottom-right-radius:0;margin-top:8px;height:unset;padding:8px 10px}.bx-settings-tabs-container > div:last-of-type button svg{width:16px;height:16px}.bx-settings-tabs{display:flex;flex-direction:column;border-radius:0 0 0 8px;box-shadow:0 0 6px #000;overflow:overlay;flex:1}.bx-settings-tabs svg{width:24px;height:24px;padding:10px;flex-shrink:0;box-sizing:content-box;background:#131313;cursor:pointer;border-left:4px solid #1e1e1e}.bx-settings-tabs svg.bx-active{background:#222;border-color:#008746}.bx-settings-tabs svg:not(.bx-active):hover{background:#2f2f2f;border-color:#484848}.bx-settings-tabs svg:focus{border-color:#fff}.bx-settings-tabs svg[data-group=global][data-need-refresh=true]{background:var(--bx-danger-button-color) !important}.bx-settings-tabs svg[data-group=global][data-need-refresh=true]:hover{background:var(--bx-danger-button-hover-color) !important}.bx-settings-tab-contents{flex-direction:column;padding:10px;margin-left:48px;width:450px;max-width:calc(100vw - tabsWidth);background:#1a1b1e;color:#fff;font-weight:400;font-size:16px;font-family:var(--bx-title-font);text-align:center;box-shadow:0 0 6px #000;overflow:overlay;z-index:1}.bx-settings-tab-contents > div[data-tab-group=mkb]{display:flex;flex-direction:column;height:100%;overflow:hidden}.bx-settings-tab-contents .bx-top-buttons{display:flex;flex-direction:column;gap:8px;margin-bottom:8px}.bx-settings-tab-contents .bx-top-buttons .bx-button{display:block}.bx-settings-tab-contents h2{margin:16px 0 8px 0;display:flex;align-items:center}.bx-settings-tab-contents h2:first-of-type{margin-top:0}.bx-settings-tab-contents h2 span{display:inline-block;font-size:20px;font-weight:bold;text-align:left;flex:1;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;min-height:var(--bx-button-height);align-content:center}@media (max-width:500px){.bx-settings-tab-contents{width:calc(100vw - 48px)}}.bx-settings-row{display:flex;gap:10px;padding:16px 10px;margin:0;background:#2a2a2a;border-bottom:1px solid #343434}.bx-settings-row:hover,.bx-settings-row:focus-within{background-color:#242424}.bx-settings-row:not(:has(> input[type=checkbox])){flex-wrap:wrap}.bx-settings-row > span.bx-settings-label{font-size:14px;display:block;text-align:left;align-self:center;margin-bottom:0 !important;flex:1}.bx-settings-row > span.bx-settings-label + *{margin:0 0 0 auto}.bx-settings-row[data-multi-lines="true"]{flex-direction:column}.bx-settings-row[data-multi-lines="true"] > span.bx-settings-label{align-self:start}.bx-settings-row[data-multi-lines="true"] > span.bx-settings-label + *{margin:unset}.bx-settings-dialog-note{display:block;color:#afafb0;font-size:12px;font-weight:lighter;font-style:italic}.bx-settings-dialog-note:not(:has(a)){margin-top:4px}.bx-settings-dialog-note a{display:inline-block;padding:4px}.bx-settings-custom-user-agent{display:block;width:100%;padding:6px}.bx-donation-link{display:block;text-align:center;text-decoration:none;height:20px;line-height:20px;font-size:14px;margin-top:10px;margin-bottom:10px}.bx-debug-info button{margin-top:10px}.bx-debug-info pre{margin-top:10px;cursor:copy;color:#fff;padding:8px;border:1px solid #2d2d2d;background:#212121;white-space:break-spaces;text-align:left}.bx-debug-info pre:hover{background:#272727}.bx-settings-app-version{margin-top:10px;text-align:center;color:#747474;font-size:12px}.bx-note-unsupported{display:block;font-size:12px;font-style:italic;font-weight:normal;color:#828282}.bx-settings-tab-contents > div *:not(.bx-settings-row):has(+ .bx-settings-row) + .bx-settings-row:has(+ .bx-settings-row){border-top-left-radius:6px;border-top-right-radius:6px}.bx-settings-tab-contents > div .bx-settings-row:not(:has(+ .bx-settings-row)){border:none;border-bottom-left-radius:6px;border-bottom-right-radius:6px}.bx-settings-tab-contents > div *:not(.bx-settings-row):has(+ .bx-settings-row) + .bx-settings-row:not(:has(+ .bx-settings-row)){border:none;border-radius:6px}.bx-suggest-toggler{text-align:left;display:flex;border-radius:4px;overflow:hidden;background:#003861;height:45px;align-items:center}.bx-suggest-toggler label{flex:1;align-content:center;padding:0 10px;background:#004f87;height:100%}.bx-suggest-toggler span{display:inline-block;align-self:center;padding:10px;width:45px;text-align:center}.bx-suggest-toggler:hover,.bx-suggest-toggler:focus{cursor:pointer;background:#005da1}.bx-suggest-toggler:hover label,.bx-suggest-toggler:focus label{background:#006fbe}.bx-suggest-toggler[bx-open] span{transform:rotate(90deg)}.bx-suggest-toggler[bx-open]+ .bx-suggest-box{display:block}.bx-suggest-box{display:none}.bx-suggest-wrapper{display:flex;flex-direction:column;gap:10px;margin:10px}.bx-suggest-note{font-size:11px;color:#8c8c8c;font-style:italic;font-weight:100}.bx-suggest-link{font-size:14px;display:inline-block;margin-top:4px;padding:4px}.bx-suggest-row{display:flex;flex-direction:row;gap:10px}.bx-suggest-row label{flex:1;overflow:overlay;border-radius:4px}.bx-suggest-row label .bx-suggest-label{background:#323232;padding:4px 10px;font-size:12px;text-align:left}.bx-suggest-row label .bx-suggest-value{padding:6px;font-size:14px}.bx-suggest-row label .bx-suggest-value.bx-suggest-change{background-color:var(--bx-warning-color)}.bx-suggest-row.bx-suggest-ok input{visibility:hidden}.bx-suggest-row.bx-suggest-ok .bx-suggest-label{background-color:#008114}.bx-suggest-row.bx-suggest-ok .bx-suggest-value{background-color:#13a72a}.bx-suggest-row.bx-suggest-change .bx-suggest-label{background-color:#a65e08}.bx-suggest-row.bx-suggest-change .bx-suggest-value{background-color:#d57f18}.bx-suggest-row.bx-suggest-change:hover label{cursor:pointer}.bx-suggest-row.bx-suggest-change:hover .bx-suggest-label{background-color:#995707}.bx-suggest-row.bx-suggest-change:hover .bx-suggest-value{background-color:#bd7115}.bx-suggest-row.bx-suggest-change input:not(:checked) + label{opacity:.5}.bx-suggest-row.bx-suggest-change input:not(:checked) + label .bx-suggest-label{background-color:#2a2a2a}.bx-suggest-row.bx-suggest-change input:not(:checked) + label .bx-suggest-value{background-color:#393939}.bx-suggest-row.bx-suggest-change:hover input:not(:checked) + label{opacity:1}.bx-suggest-row.bx-suggest-change:hover input:not(:checked) + label .bx-suggest-label{background-color:#202020}.bx-suggest-row.bx-suggest-change:hover input:not(:checked) + label .bx-suggest-value{background-color:#303030}.bx-sub-content-box{background:#161616;padding:10px;box-shadow:0 0 12px #0f0f0f inset;border-radius:10px}.bx-settings-row .bx-sub-content-box{background:#202020;padding:12px;box-shadow:0 0 4px #000 inset;border-radius:6px}.bx-controller-extra-settings[data-has-gamepad=true] > :first-child{display:none}.bx-controller-extra-settings[data-has-gamepad=true] > :last-child{display:block}.bx-controller-extra-settings[data-has-gamepad=false] > :first-child{display:block}.bx-controller-extra-settings[data-has-gamepad=false] > :last-child{display:none}.bx-controller-extra-settings .bx-controller-extra-wrapper{flex:1;min-width:1px}.bx-controller-extra-settings .bx-sub-content-box{flex:1;text-align:left;display:flex;flex-direction:column;margin-top:10px}.bx-controller-extra-settings .bx-sub-content-box > label{font-size:14px}.bx-preset-row{display:flex;gap:8px}.bx-preset-row .bx-select{flex:1}.bx-toast{user-select:none;-webkit-user-select:none;position:fixed;left:50%;top:24px;transform:translate(-50%,0);background:#000;border-radius:16px;color:#fff;z-index:var(--bx-toast-z-index);font-family:var(--bx-normal-font);border:2px solid #fff;display:flex;align-items:center;opacity:0;overflow:clip;transition:opacity .2s ease-in}.bx-toast.bx-show{opacity:.85}.bx-toast.bx-hide{opacity:0;pointer-events:none}.bx-toast-msg{font-size:14px;display:inline-block;padding:12px 16px;white-space:pre}.bx-toast-status{font-weight:bold;font-size:14px;text-transform:uppercase;display:inline-block;background:#515863;padding:12px 16px;color:#fff;white-space:pre}.bx-wait-time-box{position:fixed;top:0;right:0;background-color:rgba(0,0,0,0.8);color:#fff;z-index:var(--bx-wait-time-box-z-index);padding:12px;border-radius:0 0 0 8px}.bx-wait-time-box label{display:block;text-transform:uppercase;text-align:right;font-size:12px;font-weight:bold;margin:0}.bx-wait-time-box span{display:block;font-family:var(--bx-monospaced-font);text-align:right;font-size:16px;margin-bottom:10px}.bx-wait-time-box span:last-of-type{margin-bottom:0}.bx-remote-play-container{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);color:#fff;background:#1a1b1e;border-radius:10px;width:420px;max-width:calc(100vw - 20px);margin:0 0 0 auto;padding:16px}.bx-remote-play-container > .bx-button{display:table;margin:0 0 0 auto}.bx-remote-play-settings{margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid #2d2d2d}.bx-remote-play-settings > div{display:flex}.bx-remote-play-settings label{flex:1}.bx-remote-play-settings label p{margin:4px 0 0;padding:0;color:#888;font-size:12px}.bx-remote-play-resolution{display:block}.bx-remote-play-resolution input[type="radio"]{accent-color:var(--bx-primary-button-color);margin-right:6px}.bx-remote-play-resolution input[type="radio"]:focus{accent-color:var(--bx-primary-button-hover-color)}.bx-remote-play-device-wrapper{display:flex;margin-bottom:12px}.bx-remote-play-device-wrapper:last-child{margin-bottom:2px}.bx-remote-play-device-info{flex:1;padding:4px 0}.bx-remote-play-device-name{font-size:20px;font-weight:bold;display:inline-block;vertical-align:middle}.bx-remote-play-console-type{font-size:12px;background:#004c87;color:#fff;display:inline-block;border-radius:14px;padding:2px 10px;margin-left:8px;vertical-align:middle}.bx-remote-play-power-state{color:#888;font-size:12px}.bx-remote-play-connect-button{min-height:100%;margin:4px 0}.bx-remote-play-buttons{display:flex;justify-content:space-between}select.bx-select{min-height:30px}div.bx-select{display:flex;align-items:center;flex:0 1 auto;gap:8px}div.bx-select select{position:absolute !important;top:-9999px !important;left:-9999px !important;visibility:hidden !important}div.bx-select select:disabled ~ button{display:none}div.bx-select select:disabled ~ div{background:#131416;color:#fff;pointer-events:none}div.bx-select select:disabled ~ div .bx-select-indicators{visibility:hidden}div.bx-select > div,div.bx-select button.bx-select-value{min-width:120px;text-align:left;line-height:24px;vertical-align:middle;background:#fff;color:#000;border-radius:4px;padding:2px 8px;display:flex;flex:1;flex-direction:column}div.bx-select > div{min-height:24px;box-sizing:content-box}div.bx-select > div input{display:inline-block;margin-right:8px}div.bx-select > div label{margin-bottom:0;font-size:14px;width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;min-height:15px}div.bx-select > div label span{display:block;font-size:10px;font-weight:bold;text-align:left;line-height:initial;white-space:pre;min-height:15px;align-content:center}div.bx-select button.bx-select-value{border:none;cursor:pointer;min-height:30px;font-size:.9rem;align-items:center}div.bx-select button.bx-select-value > div{display:flex;width:100%}div.bx-select button.bx-select-value span{flex:1;text-align:left;display:inline-block}div.bx-select button.bx-select-value input{margin:0 4px;accent-color:var(--bx-primary-button-color);pointer-events:none}div.bx-select button.bx-select-value:hover input,div.bx-select button.bx-select-value:focus input{accent-color:var(--bx-danger-button-color)}div.bx-select button.bx-select-value:hover::after,div.bx-select button.bx-select-value:focus::after{border-color:#4d4d4d !important}div.bx-select button.bx-button{border:none;height:24px;width:24px;padding:0;line-height:24px;color:#fff;border-radius:4px;font-weight:bold;font-size:12px;font-family:var(--bx-monospaced-font);flex-shrink:0}div.bx-select button.bx-button span{line-height:unset}.bx-select-indicators{display:flex;height:4px;gap:2px;margin-bottom:2px}.bx-select-indicators span{content:\' \';display:inline-block;flex:1;background:#cfcfcf;border-radius:4px}.bx-select-indicators span[data-highlighted]{background:#9c9c9c}.bx-select-indicators span[data-selected]{background:#aacfe7}.bx-select-indicators span[data-highlighted][data-selected]{background:#5fa3d0}.bx-guide-home-achievements-progress{display:flex;gap:10px;flex-direction:row}.bx-guide-home-achievements-progress .bx-button{margin-bottom:0 !important}body[data-bx-media-type=tv] .bx-guide-home-achievements-progress{flex-direction:column}body:not([data-bx-media-type=tv]) .bx-guide-home-achievements-progress{flex-direction:row}body:not([data-bx-media-type=tv]) .bx-guide-home-achievements-progress > button:first-of-type{flex:1}body:not([data-bx-media-type=tv]) .bx-guide-home-achievements-progress > button:last-of-type{width:40px}body:not([data-bx-media-type=tv]) .bx-guide-home-achievements-progress > button:last-of-type span{display:none}.bx-guide-home-buttons > div{display:flex;flex-direction:row;gap:12px}body[data-bx-media-type=tv] .bx-guide-home-buttons > div{flex-direction:column}body[data-bx-media-type=tv] .bx-guide-home-buttons > div button{margin-bottom:0 !important}body:not([data-bx-media-type=tv]) .bx-guide-home-buttons > div button span{display:none}.bx-guide-home-buttons[data-is-playing="true"] button[data-state=\'normal\']{display:none}.bx-guide-home-buttons[data-is-playing="false"] button[data-state=\'playing\']{display:none}div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module]{overflow:visible}.bx-stream-menu-button-on{fill:#000 !important;background-color:#2d2d2d !important;color:#000 !important}.bx-stream-refresh-button{top:calc(env(safe-area-inset-top, 0px) + 10px + 50px) !important}body[data-media-type=default] .bx-stream-refresh-button{left:calc(env(safe-area-inset-left, 0px) + 11px) !important}body[data-media-type=tv] .bx-stream-refresh-button{top:calc(var(--gds-focus-borderSize) + 80px) !important}.bx-stream-home-button{top:calc(env(safe-area-inset-top, 0px) + 10px + 50px * 2) !important}body[data-media-type=default] .bx-stream-home-button{left:calc(env(safe-area-inset-left, 0px) + 12px) !important}body[data-media-type=tv] .bx-stream-home-button{top:calc(var(--gds-focus-borderSize) + 80px * 2) !important}div[data-testid=media-container][data-position=center]{display:flex}div[data-testid=media-container][data-position=top] video,div[data-testid=media-container][data-position=top] canvas{top:0}div[data-testid=media-container][data-position=bottom] video,div[data-testid=media-container][data-position=bottom] canvas{bottom:0}#game-stream video{margin:auto;align-self:center;background:#000;position:absolute;left:0;right:0}#game-stream canvas{align-self:center;margin:auto;position:absolute;left:0;right:0}#game-stream.bx-taking-screenshot:before{animation:bx-anim-taking-screenshot .5s ease;content:\' \';position:absolute;width:100%;height:100%;z-index:var(--bx-screenshot-animation-z-index)}#gamepass-dialog-root div[class^=Guide-module__guide] .bx-button{overflow:visible;margin-bottom:12px}@-moz-keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}@-webkit-keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}@-o-keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}@keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}.bx-number-stepper{text-align:center}.bx-number-stepper > div{display:flex;align-items:center}.bx-number-stepper > div span{flex:1;display:inline-block;min-width:40px;font-family:var(--bx-monospaced-font);font-size:13px;margin:0 4px}.bx-number-stepper > div button{flex-shrink:0;border:none;width:24px;height:24px;margin:0;line-height:24px;background-color:var(--bx-default-button-color);color:#fff;border-radius:4px;font-weight:bold;font-size:14px;font-family:var(--bx-monospaced-font)}@media (hover:hover){.bx-number-stepper > div button:hover{background-color:var(--bx-default-button-hover-color)}}.bx-number-stepper > div button:active{background-color:var(--bx-default-button-hover-color)}.bx-number-stepper > div button:disabled + span{font-family:var(--bx-title-font)}.bx-number-stepper input[type="range"]{display:block;margin:8px 0 2px auto;min-width:180px;width:100%;color:#959595 !important}.bx-number-stepper input[type=range]:disabled,.bx-number-stepper button:disabled{display:none}.bx-number-stepper[data-disabled=true] input[type=range],.bx-number-stepper[disabled=true] input[type=range],.bx-number-stepper[data-disabled=true] button,.bx-number-stepper[disabled=true] button{display:none}#bx-game-bar{z-index:var(--bx-game-bar-z-index);position:fixed;bottom:0;width:40px;height:90px;overflow:visible;cursor:pointer}#bx-game-bar > svg{display:none;pointer-events:none;position:absolute;height:28px;margin-top:16px}@media (hover:hover){#bx-game-bar:hover > svg{display:block}}#bx-game-bar .bx-game-bar-container{opacity:0;position:absolute;display:flex;overflow:hidden;background:rgba(26,27,30,0.91);box-shadow:0 0 6px #1c1c1c;transition:opacity .1s ease-in}#bx-game-bar .bx-game-bar-container.bx-show{opacity:.9}#bx-game-bar .bx-game-bar-container.bx-show + svg{display:none !important}#bx-game-bar .bx-game-bar-container.bx-hide{opacity:0;pointer-events:none}#bx-game-bar .bx-game-bar-container button{width:60px;height:60px;border-radius:0}#bx-game-bar .bx-game-bar-container button svg{width:28px;height:28px;transition:transform .08s ease 0s}#bx-game-bar .bx-game-bar-container button:hover{border-radius:0}#bx-game-bar .bx-game-bar-container button:active svg{transform:scale(.75)}#bx-game-bar .bx-game-bar-container button.bx-activated{background-color:#fff}#bx-game-bar .bx-game-bar-container button.bx-activated svg{filter:invert(1)}#bx-game-bar .bx-game-bar-container div[data-activated] button{display:none}#bx-game-bar .bx-game-bar-container div[data-activated=\'false\'] button:first-of-type{display:block}#bx-game-bar .bx-game-bar-container div[data-activated=\'true\'] button:last-of-type{display:block}#bx-game-bar[data-position="bottom-left"]{left:0;direction:ltr}#bx-game-bar[data-position="bottom-left"] .bx-game-bar-container{border-radius:0 10px 10px 0}#bx-game-bar[data-position="bottom-right"]{right:0;direction:rtl}#bx-game-bar[data-position="bottom-right"] .bx-game-bar-container{direction:ltr;border-radius:10px 0 0 10px}.bx-badges{margin-left:0;user-select:none;-webkit-user-select:none}.bx-badge{border:none;display:inline-block;line-height:24px;color:#fff;font-family:var(--bx-title-font-semibold);font-size:14px;font-weight:400;margin:0 8px 8px 0;box-shadow:0 0 6px #000;border-radius:4px}.bx-badge-name{background-color:#2d3036;border-radius:4px 0 0 4px}.bx-badge-name svg{width:16px;height:16px}.bx-badge-value{background-color:#808080;border-radius:0 4px 4px 0}.bx-badge-name,.bx-badge-value{display:inline-block;padding:0 8px;line-height:30px;vertical-align:bottom}.bx-badge-battery[data-charging=true] span:first-of-type::after{content:\' ⚡️\'}div[class^=StreamMenu-module__container] .bx-badges{position:absolute;max-width:500px}#gamepass-dialog-root .bx-badges{position:fixed;top:60px;left:460px;max-width:500px}@media (min-width:568px) and (max-height:480px){#gamepass-dialog-root .bx-badges{position:unset;top:unset;left:unset;margin:8px 0}}.bx-stats-bar{display:flex;flex-direction:row;gap:8px;user-select:none;-webkit-user-select:none;position:fixed;top:0;background-color:#000;color:#fff;font-family:var(--bx-monospaced-font);font-size:.9rem;padding-left:8px;z-index:var(--bx-stats-bar-z-index);text-wrap:nowrap}.bx-stats-bar[data-stats*="[time]"] > .bx-stat-time,.bx-stats-bar[data-stats*="[play]"] > .bx-stat-play,.bx-stats-bar[data-stats*="[batt]"] > .bx-stat-batt,.bx-stats-bar[data-stats*="[fps]"] > .bx-stat-fps,.bx-stats-bar[data-stats*="[ping]"] > .bx-stat-ping,.bx-stats-bar[data-stats*="[jit]"] > .bx-stat-jit,.bx-stats-bar[data-stats*="[btr]"] > .bx-stat-btr,.bx-stats-bar[data-stats*="[dt]"] > .bx-stat-dt,.bx-stats-bar[data-stats*="[pl]"] > .bx-stat-pl,.bx-stats-bar[data-stats*="[fl]"] > .bx-stat-fl,.bx-stats-bar[data-stats*="[dl]"] > .bx-stat-dl,.bx-stats-bar[data-stats*="[ul]"] > .bx-stat-ul{display:inline-flex;align-items:baseline}.bx-stats-bar[data-stats$="[time]"] > .bx-stat-time,.bx-stats-bar[data-stats$="[play]"] > .bx-stat-play,.bx-stats-bar[data-stats$="[batt]"] > .bx-stat-batt,.bx-stats-bar[data-stats$="[fps]"] > .bx-stat-fps,.bx-stats-bar[data-stats$="[ping]"] > .bx-stat-ping,.bx-stats-bar[data-stats$="[jit]"] > .bx-stat-jit,.bx-stats-bar[data-stats$="[btr]"] > .bx-stat-btr,.bx-stats-bar[data-stats$="[dt]"] > .bx-stat-dt,.bx-stats-bar[data-stats$="[pl]"] > .bx-stat-pl,.bx-stats-bar[data-stats$="[fl]"] > .bx-stat-fl,.bx-stats-bar[data-stats$="[dl]"] > .bx-stat-dl,.bx-stats-bar[data-stats$="[ul]"] > .bx-stat-ul{border-right:none}.bx-stats-bar::before{display:none;content:\'👀\';vertical-align:middle;margin-right:8px}.bx-stats-bar[data-display=glancing]::before{display:inline-block}.bx-stats-bar[data-position=top-left]{left:0;border-radius:0 0 4px 0}.bx-stats-bar[data-position=top-right]{right:0;border-radius:0 0 0 4px}.bx-stats-bar[data-position=top-center]{transform:translate(-50%,0);left:50%;border-radius:0 0 4px 4px}.bx-stats-bar[data-shadow=true]{background:none;filter:drop-shadow(1px 0 0 rgba(0,0,0,0.941)) drop-shadow(-1px 0 0 rgba(0,0,0,0.941)) drop-shadow(0 1px 0 rgba(0,0,0,0.941)) drop-shadow(0 -1px 0 rgba(0,0,0,0.941))}.bx-stats-bar > div{display:none;border-right:1px solid #fff;padding-right:8px}.bx-stats-bar label{margin:0 8px 0 0;font-family:var(--bx-title-font);font-size:70%;font-weight:bold;vertical-align:middle;cursor:help}.bx-stats-bar span{display:inline-block;text-align:right;vertical-align:middle;white-space:pre}.bx-stats-bar span[data-grade=good]{color:#6bffff}.bx-stats-bar span[data-grade=ok]{color:#fff16b}.bx-stats-bar span[data-grade=bad]{color:#ff5f5f}.bx-mkb-settings{display:flex;flex-direction:column;flex:1;padding-bottom:10px;overflow:hidden}.bx-mkb-pointer-lock-msg{user-select:none;-webkit-user-select:none;position:fixed;left:50%;bottom:40px;transform:translateX(-50%);margin:auto;background:#151515;z-index:var(--bx-mkb-pointer-lock-msg-z-index);color:#fff;font-weight:400;font-family:"Segoe UI",Arial,Helvetica,sans-serif;font-size:1.3rem;padding:12px;border-radius:8px;align-items:center;box-shadow:0 0 6px #000;min-width:300px;opacity:.9;display:flex;flex-direction:column;gap:10px}.bx-mkb-pointer-lock-msg:hover{opacity:1}.bx-mkb-pointer-lock-msg > p{margin:0;width:100%;font-size:22px;margin-bottom:4px;font-weight:bold;text-align:left}.bx-mkb-pointer-lock-msg > div{width:100%;display:flex;flex-direction:row;gap:10px}.bx-mkb-pointer-lock-msg > div button:first-of-type{flex-shrink:1}.bx-mkb-pointer-lock-msg > div button:last-of-type{flex-grow:1}.bx-mkb-key-row{display:flex;margin-bottom:10px;align-items:center;gap:20px}.bx-mkb-key-row label{margin-bottom:0;font-family:var(--bx-promptfont-font);font-size:32px;text-align:center}.bx-mkb-settings.bx-editing .bx-mkb-key-row button{background:#393939;border-radius:4px;border:none}.bx-mkb-settings.bx-editing .bx-mkb-key-row button:hover{background:#333;cursor:pointer}.bx-mkb-action-buttons > div{text-align:right;display:none}.bx-mkb-action-buttons button{margin-left:8px}.bx-mkb-settings:not(.bx-editing) .bx-mkb-action-buttons > div:first-child{display:block}.bx-mkb-settings.bx-editing .bx-mkb-action-buttons > div:last-child{display:block}.bx-mkb-note{display:block;margin:0 0 10px;font-size:12px;text-align:center}button.bx-binding-button{flex:1;min-height:38px;border:none;border-radius:4px;font-size:14px;color:#fff;display:flex;align-items:center;align-self:center;padding:0 6px}button.bx-binding-button:disabled{background:#131416;padding:0 8px}button.bx-binding-button:not(:disabled){border:2px solid transparent;border-top:none;border-bottom:4px solid #252525;background:#3b3b3b;cursor:pointer}button.bx-binding-button:not(:disabled):hover,button.bx-binding-button:not(:disabled).bx-focusable:focus{background:#20b217;border-bottom-color:#186c13}button.bx-binding-button:not(:disabled):active{background:#16900f;border-bottom:3px solid #0c4e08;border-left-width:2px;border-right-width:2px}button.bx-binding-button:not(:disabled).bx-focusable:focus::after{top:-6px;left:-8px;right:-8px;bottom:-10px}.bx-settings-row .bx-binding-button-wrapper button.bx-binding-button{min-width:60px}.bx-product-details-buttons{display:flex;gap:10px;flex-direction:row}.bx-product-details-buttons button{max-width:max-content;margin:10px 0 0 0;display:flex}@media (min-width:568px) and (max-height:480px){.bx-product-details-buttons{flex-direction:column}.bx-product-details-buttons button{margin:8px 0 0 10px}}', PREF_HIDE_SECTIONS = getPref("ui.hideSections"), selectorToHide = [];
+ let css = ':root{--bx-title-font:Bahnschrift,Arial,Helvetica,sans-serif;--bx-title-font-semibold:Bahnschrift Semibold,Arial,Helvetica,sans-serif;--bx-normal-font:"Segoe UI",Arial,Helvetica,sans-serif;--bx-monospaced-font:Consolas,"Courier New",Courier,monospace;--bx-promptfont-font:promptfont;--bx-button-height:40px;--bx-default-button-color:#2d3036;--bx-default-button-rgb:45,48,54;--bx-default-button-hover-color:#515863;--bx-default-button-hover-rgb:81,88,99;--bx-default-button-active-color:#222428;--bx-default-button-active-rgb:34,36,40;--bx-default-button-disabled-color:#8e8e8e;--bx-default-button-disabled-rgb:142,142,142;--bx-primary-button-color:#008746;--bx-primary-button-rgb:0,135,70;--bx-primary-button-hover-color:#04b358;--bx-primary-button-hover-rgb:4,179,88;--bx-primary-button-active-color:#044e2a;--bx-primary-button-active-rgb:4,78,42;--bx-primary-button-disabled-color:#448262;--bx-primary-button-disabled-rgb:68,130,98;--bx-warning-button-color:#c16e04;--bx-warning-button-rgb:193,110,4;--bx-warning-button-hover-color:#fa9005;--bx-warning-button-hover-rgb:250,144,5;--bx-warning-button-active-color:#965603;--bx-warning-button-active-rgb:150,86,3;--bx-warning-button-disabled-color:#a2816c;--bx-warning-button-disabled-rgb:162,129,108;--bx-danger-button-color:#c10404;--bx-danger-button-rgb:193,4,4;--bx-danger-button-hover-color:#e61d1d;--bx-danger-button-hover-rgb:230,29,29;--bx-danger-button-active-color:#a26c6c;--bx-danger-button-active-rgb:162,108,108;--bx-danger-button-disabled-color:#df5656;--bx-danger-button-disabled-rgb:223,86,86;--bx-fullscreen-text-z-index:9999;--bx-toast-z-index:6000;--bx-key-binding-dialog-z-index:5010;--bx-key-binding-dialog-overlay-z-index:5000;--bx-stats-bar-z-index:4010;--bx-navigation-dialog-z-index:3010;--bx-navigation-dialog-overlay-z-index:3000;--bx-mkb-pointer-lock-msg-z-index:2000;--bx-game-bar-z-index:1000;--bx-screenshot-animation-z-index:200;--bx-wait-time-box-z-index:100}@font-face{font-family:\'promptfont\';src:url("https://redphx.github.io/better-xcloud/fonts/promptfont.otf");unicode-range:U+2196-E011}div[class^=HUDButton-module__hiddenContainer] ~ div:not([class^=HUDButton-module__hiddenContainer]){opacity:0;pointer-events:none !important;position:absolute;top:-9999px;left:-9999px}@media screen and (max-width:640px){header a[href="/play"]{display:none}}.bx-full-width{width:100% !important}.bx-full-height{height:100% !important}.bx-auto-height{height:auto !important}.bx-no-scroll{overflow:hidden !important}.bx-hide-scroll-bar{scrollbar-width:none}.bx-hide-scroll-bar::-webkit-scrollbar{display:none}.bx-gone{display:none !important}.bx-offscreen{position:absolute !important;top:-9999px !important;left:-9999px !important;visibility:hidden !important}.bx-hidden{visibility:hidden !important}.bx-invisible{opacity:0}.bx-unclickable{pointer-events:none}.bx-pixel{width:1px !important;height:1px !important}.bx-no-margin{margin:0 !important}.bx-no-padding{padding:0 !important}.bx-prompt{font-family:var(--bx-promptfont-font) !important}.bx-monospaced{font-family:var(--bx-monospaced-font) !important}.bx-line-through{text-decoration:line-through !important}.bx-normal-case{text-transform:none !important}.bx-normal-link{text-transform:none !important;text-align:left !important;font-weight:400 !important;font-family:var(--bx-normal-font) !important}select[multiple],select[multiple]:focus{overflow:auto;border:none}select[multiple] option,select[multiple]:focus option{padding:4px 6px}select[multiple] option:checked,select[multiple]:focus option:checked{background:#1a7bc0 linear-gradient(0deg,#1a7bc0 0%,#1a7bc0 100%)}select[multiple] option:checked::before,select[multiple]:focus option:checked::before{content:\'☑️\';font-size:12px;display:inline-block;margin-right:6px;height:100%;line-height:100%;vertical-align:middle}#headerArea,#uhfSkipToMain,.uhf-footer{display:none}div[class*=NotFocusedDialog]{position:absolute !important;top:-9999px !important;left:-9999px !important;width:0 !important;height:0 !important}#game-stream video:not([src]){visibility:hidden}div[class*=SupportedInputsBadge]:not(:has(:nth-child(2))),div[class*=SupportedInputsBadge] svg:first-of-type{display:none}.bx-game-tile-wait-time{position:absolute;top:0;left:0;z-index:1;background:rgba(0,0,0,0.5);display:flex;border-radius:4px 0 4px 0;align-items:center;padding:4px 8px}.bx-game-tile-wait-time svg{width:14px;height:16px;margin-right:2px}.bx-game-tile-wait-time span{display:inline-block;height:16px;line-height:16px;font-size:12px;font-weight:bold;margin-left:2px}.bx-game-tile-wait-time[data-duration=short]{background-color:rgba(0,133,133,0.75)}.bx-game-tile-wait-time[data-duration=medium]{background-color:rgba(213,133,0,0.75)}.bx-game-tile-wait-time[data-duration=long]{background-color:rgba(150,0,0,0.75)}.bx-fullscreen-text{position:fixed;top:0;bottom:0;left:0;right:0;background:rgba(0,0,0,0.8);z-index:var(--bx-fullscreen-text-z-index);line-height:100vh;color:#fff;text-align:center;font-weight:400;font-family:var(--bx-normal-font);font-size:1.3rem;user-select:none;-webkit-user-select:none}#root section[class*=DeviceCodePage-module__page]{margin-left:20px !important;margin-right:20px !important;margin-top:20px !important;max-width:800px !important}#root div[class*=DeviceCodePage-module__back]{display:none}.bx-blink-me{animation:bx-blinker 1s linear infinite}.bx-horizontal-shaking{animation:bx-horizontal-shaking .4s ease-in-out 2}@-moz-keyframes bx-blinker{100%{opacity:0}}@-webkit-keyframes bx-blinker{100%{opacity:0}}@-o-keyframes bx-blinker{100%{opacity:0}}@keyframes bx-blinker{100%{opacity:0}}@-moz-keyframes bx-horizontal-shaking{0%{transform:translateX(0)}25%{transform:translateX(5px)}50%{transform:translateX(-5px)}75%{transform:translateX(5px)}100%{transform:translateX(0)}}@-webkit-keyframes bx-horizontal-shaking{0%{transform:translateX(0)}25%{transform:translateX(5px)}50%{transform:translateX(-5px)}75%{transform:translateX(5px)}100%{transform:translateX(0)}}@-o-keyframes bx-horizontal-shaking{0%{transform:translateX(0)}25%{transform:translateX(5px)}50%{transform:translateX(-5px)}75%{transform:translateX(5px)}100%{transform:translateX(0)}}@keyframes bx-horizontal-shaking{0%{transform:translateX(0)}25%{transform:translateX(5px)}50%{transform:translateX(-5px)}75%{transform:translateX(5px)}100%{transform:translateX(0)}}.bx-button{--button-rgb:var(--bx-default-button-rgb);--button-hover-rgb:var(--bx-default-button-hover-rgb);--button-active-rgb:var(--bx-default-button-active-rgb);--button-disabled-rgb:var(--bx-default-button-disabled-rgb);background-color:rgb(var(--button-rgb));user-select:none;-webkit-user-select:none;color:#fff;font-family:var(--bx-title-font-semibold);font-size:14px;border:none;font-weight:400;height:var(--bx-button-height);border-radius:4px;padding:0 8px;text-transform:uppercase;cursor:pointer;overflow:hidden}.bx-button:not([disabled]):active{background-color:rgb(var(--button-active-rgb))}.bx-button:focus{outline:none !important}.bx-button:not([disabled]):not(:active):hover,.bx-button:not([disabled]):not(:active).bx-focusable:focus{background-color:rgb(var(--button-hover-rgb))}.bx-button:disabled{cursor:default;background-color:rgb(var(--button-disabled-rgb))}.bx-button.bx-ghost{background-color:transparent}.bx-button.bx-ghost:not([disabled]):not(:active):hover,.bx-button.bx-ghost:not([disabled]):not(:active).bx-focusable:focus{background-color:rgb(var(--button-hover-rgb))}.bx-button.bx-primary{--button-rgb:var(--bx-primary-button-rgb)}.bx-button.bx-primary:not([disabled]):active{--button-active-rgb:var(--bx-primary-button-active-rgb)}.bx-button.bx-primary:not([disabled]):not(:active):hover,.bx-button.bx-primary:not([disabled]):not(:active).bx-focusable:focus{--button-hover-rgb:var(--bx-primary-button-hover-rgb)}.bx-button.bx-primary:disabled{--button-disabled-rgb:var(--bx-primary-button-disabled-rgb)}.bx-button.bx-warning{--button-rgb:var(--bx-warning-button-rgb)}.bx-button.bx-warning:not([disabled]):active{--button-active-rgb:var(--bx-warning-button-active-rgb)}.bx-button.bx-warning:not([disabled]):not(:active):hover,.bx-button.bx-warning:not([disabled]):not(:active).bx-focusable:focus{--button-hover-rgb:var(--bx-warning-button-hover-rgb)}.bx-button.bx-warning:disabled{--button-disabled-rgb:var(--bx-warning-button-disabled-rgb)}.bx-button.bx-danger{--button-rgb:var(--bx-danger-button-rgb)}.bx-button.bx-danger:not([disabled]):active{--button-active-rgb:var(--bx-danger-button-active-rgb)}.bx-button.bx-danger:not([disabled]):not(:active):hover,.bx-button.bx-danger:not([disabled]):not(:active).bx-focusable:focus{--button-hover-rgb:var(--bx-danger-button-hover-rgb)}.bx-button.bx-danger:disabled{--button-disabled-rgb:var(--bx-danger-button-disabled-rgb)}.bx-button.bx-frosted{--button-alpha:.2;background-color:rgba(var(--button-rgb), var(--button-alpha));backdrop-filter:blur(4px) brightness(1.5)}.bx-button.bx-frosted:not([disabled]):not(:active):hover,.bx-button.bx-frosted:not([disabled]):not(:active).bx-focusable:focus{background-color:rgba(var(--button-hover-rgb), var(--button-alpha))}.bx-button.bx-drop-shadow{box-shadow:0 0 4px rgba(0,0,0,0.502)}.bx-button.bx-tall{height:calc(var(--bx-button-height) * 1.5) !important}.bx-button.bx-circular{border-radius:var(--bx-button-height);width:var(--bx-button-height);height:var(--bx-button-height)}.bx-button svg{display:inline-block;width:16px;height:var(--bx-button-height)}.bx-button span{display:inline-block;line-height:var(--bx-button-height);vertical-align:middle;color:#fff;overflow:hidden;white-space:nowrap}.bx-button span:not(:only-child){margin-left:10px}.bx-button.bx-button-multi-lines{height:auto;text-align:left;padding:10px 0}.bx-button.bx-button-multi-lines span{line-height:unset;display:block}.bx-button.bx-button-multi-lines span:last-of-type{text-transform:none;font-weight:normal;font-family:"Segoe Sans Variable Text";font-size:12px;margin-top:4px}.bx-focusable{position:relative;overflow:visible}.bx-focusable::after{border:2px solid transparent;border-radius:10px}.bx-focusable:focus::after{content:\'\';border-color:#fff;position:absolute;top:-6px;left:-6px;right:-6px;bottom:-6px}html[data-active-input=touch] .bx-focusable:focus::after,html[data-active-input=mouse] .bx-focusable:focus::after{border-color:transparent !important}.bx-focusable.bx-circular::after{border-radius:var(--bx-button-height)}a.bx-button{display:inline-block}a.bx-button.bx-full-width{text-align:center}button.bx-inactive{pointer-events:none;opacity:.2;background:transparent !important}.bx-header-remote-play-button{height:auto;margin-right:8px !important}.bx-header-remote-play-button svg{width:24px;height:24px}.bx-header-settings-button{line-height:30px;font-size:14px;text-transform:uppercase;position:relative}.bx-header-settings-button[data-update-available]::before{content:\'🌟\' !important;line-height:var(--bx-button-height);display:inline-block;margin-left:4px}.bx-key-binding-dialog-overlay{position:fixed;inset:0;z-index:var(--bx-key-binding-dialog-overlay-z-index);background:#000;opacity:50%}.bx-key-binding-dialog{display:flex;flex-flow:column;max-height:90vh;position:fixed;top:50%;left:50%;margin-right:-50%;transform:translate(-50%,-50%);min-width:420px;padding:16px;border-radius:8px;z-index:var(--bx-key-binding-dialog-z-index);background:#1a1b1e;color:#fff;font-weight:400;font-size:16px;font-family:var(--bx-normal-font);box-shadow:0 0 6px #000;user-select:none;-webkit-user-select:none}.bx-key-binding-dialog *:focus{outline:none !important}.bx-key-binding-dialog h2{margin-bottom:12px;color:#fff;display:block;font-family:var(--bx-title-font);font-size:32px;font-weight:400;line-height:var(--bx-button-height)}.bx-key-binding-dialog > div{overflow:auto;padding:2px 0}.bx-key-binding-dialog > button{padding:8px 32px;margin:10px auto 0;border:none;border-radius:4px;display:block;background-color:#2d3036;text-align:center;color:#fff;text-transform:uppercase;font-family:var(--bx-title-font);font-weight:400;line-height:18px;font-size:14px}@media (hover:hover){.bx-key-binding-dialog > button:hover{background-color:#515863}}.bx-key-binding-dialog > button:focus{background-color:#515863}.bx-key-binding-dialog ul{margin-bottom:1rem}.bx-key-binding-dialog ul li{display:none}.bx-key-binding-dialog ul[data-flags*="[1]"] > li[data-flag="1"],.bx-key-binding-dialog ul[data-flags*="[2]"] > li[data-flag="2"],.bx-key-binding-dialog ul[data-flags*="[4]"] > li[data-flag="4"],.bx-key-binding-dialog ul[data-flags*="[8]"] > li[data-flag="8"]{display:list-item}@media screen and (max-width:450px){.bx-key-binding-dialog{min-width:100%}}.bx-navigation-dialog{position:absolute;z-index:var(--bx-navigation-dialog-z-index);font-family:var(--bx-title-font)}.bx-navigation-dialog *:focus{outline:none !important}.bx-navigation-dialog select:disabled{-webkit-appearance:none;text-align-last:right;text-align:right;color:#fff;background:#131416;border:none;border-radius:4px;padding:0 5px}.bx-navigation-dialog .bx-focusable::after{border-radius:4px}.bx-navigation-dialog .bx-focusable:focus::after{top:0;left:0;right:0;bottom:0}.bx-navigation-dialog-overlay{position:fixed;background:rgba(11,11,11,0.89);top:0;left:0;right:0;bottom:0;z-index:var(--bx-navigation-dialog-overlay-z-index)}.bx-navigation-dialog-overlay[data-is-playing="true"]{background:transparent}.bx-centered-dialog{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);color:#fff;background:#1a1b1e;border-radius:10px;min-width:min(calc(100vw - 20px), 500px);max-width:calc(100vw - 20px);margin:0 0 0 auto;padding:16px;max-height:95vh;flex-direction:column;overflow:hidden;display:flex;flex-direction:column}.bx-centered-dialog .bx-dialog-title{display:flex;flex-direction:row;align-items:center;margin-bottom:10px}.bx-centered-dialog .bx-dialog-title p{padding:0;margin:0;flex:1;font-size:1.2rem;font-weight:bold}.bx-centered-dialog .bx-dialog-title button{flex-shrink:0}.bx-centered-dialog .bx-dialog-content{flex:1;padding:6px;overflow:auto;overflow-x:hidden}.bx-centered-dialog .bx-dialog-preset-tools{display:flex;margin-bottom:12px;gap:6px}.bx-centered-dialog .bx-dialog-preset-tools button{align-self:center;min-height:50px}.bx-centered-dialog .bx-default-preset-note{font-size:12px;font-style:italic;text-align:center;margin-bottom:10px}.bx-centered-dialog input,.bx-settings-dialog input{accent-color:var(--bx-primary-button-color)}.bx-centered-dialog input:focus,.bx-settings-dialog input:focus{accent-color:var(--bx-danger-button-color)}.bx-centered-dialog select:disabled,.bx-settings-dialog select:disabled{-webkit-appearance:none;background:transparent;text-align-last:right;border:none;color:#fff}.bx-centered-dialog select option:disabled,.bx-settings-dialog select option:disabled{display:none}.bx-centered-dialog input[type=checkbox]:focus,.bx-settings-dialog input[type=checkbox]:focus,.bx-centered-dialog select:focus,.bx-settings-dialog select:focus{filter:drop-shadow(1px 0 0 #fff) drop-shadow(-1px 0 0 #fff) drop-shadow(0 1px 0 #fff) drop-shadow(0 -1px 0 #fff)}.bx-centered-dialog a,.bx-settings-dialog a{color:#1c9d1c;text-decoration:none}.bx-centered-dialog a:hover,.bx-settings-dialog a:hover,.bx-centered-dialog a:focus,.bx-settings-dialog a:focus{color:#5dc21e}.bx-centered-dialog label,.bx-settings-dialog label{margin:0}.bx-controller-shortcuts-manager-container .bx-shortcut-note{margin-top:10px;font-size:14px;text-align:center}.bx-controller-shortcuts-manager-container .bx-shortcut-row{display:flex;gap:10px;margin-bottom:10px;align-items:center}.bx-controller-shortcuts-manager-container .bx-shortcut-row label.bx-prompt{flex-shrink:0;font-size:32px;margin:0}.bx-controller-shortcuts-manager-container .bx-shortcut-row label.bx-prompt::first-letter{letter-spacing:6px}.bx-controller-shortcuts-manager-container select:disabled{text-align:left;text-align-last:left}.bx-keyboard-shortcuts-manager-container{display:flex;flex-direction:column;gap:16px}.bx-keyboard-shortcuts-manager-container fieldset{background:#2a2a2a;border:1px solid #2a2a2a;border-radius:4px;padding:4px}.bx-keyboard-shortcuts-manager-container legend{width:auto;padding:4px 8px;margin:0 4px 4px;background:#004f87;box-shadow:0 2px 0 #071e3d;border-radius:4px;font-size:14px;font-weight:bold;text-transform:uppercase}.bx-keyboard-shortcuts-manager-container .bx-settings-row{background:none;padding:10px}.bx-settings-dialog{display:flex;position:fixed;top:0;right:0;bottom:0;opacity:.98;user-select:none;-webkit-user-select:none}.bx-settings-dialog .bx-settings-reload-note{font-size:.8rem;display:block;padding:8px;font-style:italic;font-weight:normal;height:var(--bx-button-height)}.bx-settings-tabs-container{position:fixed;width:48px;max-height:100vh;display:flex;flex-direction:column}.bx-settings-tabs-container > div:last-of-type{display:flex;flex-direction:column;align-items:end}.bx-settings-tabs-container > div:last-of-type button{flex-shrink:0;border-top-right-radius:0;border-bottom-right-radius:0;margin-top:8px;height:unset;padding:8px 10px}.bx-settings-tabs-container > div:last-of-type button svg{width:16px;height:16px}.bx-settings-tabs{display:flex;flex-direction:column;border-radius:0 0 0 8px;box-shadow:0 0 6px #000;overflow:overlay;flex:1}.bx-settings-tabs svg{width:24px;height:24px;padding:10px;flex-shrink:0;box-sizing:content-box;background:#131313;cursor:pointer;border-left:4px solid #1e1e1e}.bx-settings-tabs svg.bx-active{background:#222;border-color:#008746}.bx-settings-tabs svg:not(.bx-active):hover{background:#2f2f2f;border-color:#484848}.bx-settings-tabs svg:focus{border-color:#fff}.bx-settings-tabs svg[data-group=global][data-need-refresh=true]{background:var(--bx-danger-button-color) !important}.bx-settings-tabs svg[data-group=global][data-need-refresh=true]:hover{background:var(--bx-danger-button-hover-color) !important}.bx-settings-tab-contents{flex-direction:column;padding:10px;margin-left:48px;width:450px;max-width:calc(100vw - tabsWidth);background:#1a1b1e;color:#fff;font-weight:400;font-size:16px;font-family:var(--bx-title-font);text-align:center;box-shadow:0 0 6px #000;overflow:overlay;z-index:1}.bx-settings-tab-contents > div[data-tab-group=mkb]{display:flex;flex-direction:column;height:100%;overflow:hidden}.bx-settings-tab-contents .bx-top-buttons{display:flex;flex-direction:column;gap:8px;margin-bottom:8px}.bx-settings-tab-contents .bx-top-buttons .bx-button{display:block}.bx-settings-tab-contents h2{margin:16px 0 8px 0;display:flex;align-items:center}.bx-settings-tab-contents h2:first-of-type{margin-top:0}.bx-settings-tab-contents h2 span{display:inline-block;font-size:20px;font-weight:bold;text-align:left;flex:1;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;min-height:var(--bx-button-height);align-content:center}@media (max-width:500px){.bx-settings-tab-contents{width:calc(100vw - 48px)}}.bx-settings-row{display:flex;gap:10px;padding:16px 10px;margin:0;background:#2a2a2a;border-bottom:1px solid #343434}.bx-settings-row:hover,.bx-settings-row:focus-within{background-color:#242424}.bx-settings-row:not(:has(> input[type=checkbox])){flex-wrap:wrap}.bx-settings-row > span.bx-settings-label{font-size:14px;display:block;text-align:left;align-self:center;margin-bottom:0 !important;flex:1}.bx-settings-row > span.bx-settings-label + *{margin:0 0 0 auto}.bx-settings-row[data-multi-lines="true"]{flex-direction:column}.bx-settings-row[data-multi-lines="true"] > span.bx-settings-label{align-self:start}.bx-settings-row[data-multi-lines="true"] > span.bx-settings-label + *{margin:unset}.bx-settings-dialog-note{display:block;color:#afafb0;font-size:12px;font-weight:lighter;font-style:italic}.bx-settings-dialog-note:not(:has(a)){margin-top:4px}.bx-settings-dialog-note a{display:inline-block;padding:4px}.bx-settings-custom-user-agent{display:block;width:100%;padding:6px}.bx-donation-link{display:block;text-align:center;text-decoration:none;height:20px;line-height:20px;font-size:14px;margin-top:10px;margin-bottom:10px}.bx-debug-info button{margin-top:10px}.bx-debug-info pre{margin-top:10px;cursor:copy;color:#fff;padding:8px;border:1px solid #2d2d2d;background:#212121;white-space:break-spaces;text-align:left}.bx-debug-info pre:hover{background:#272727}.bx-settings-app-version{margin-top:10px;text-align:center;color:#747474;font-size:12px}.bx-note-unsupported{display:block;font-size:12px;font-style:italic;font-weight:normal;color:#828282}.bx-settings-tab-contents > div *:not(.bx-settings-row):has(+ .bx-settings-row) + .bx-settings-row:has(+ .bx-settings-row){border-top-left-radius:6px;border-top-right-radius:6px}.bx-settings-tab-contents > div .bx-settings-row:not(:has(+ .bx-settings-row)){border:none;border-bottom-left-radius:6px;border-bottom-right-radius:6px}.bx-settings-tab-contents > div *:not(.bx-settings-row):has(+ .bx-settings-row) + .bx-settings-row:not(:has(+ .bx-settings-row)){border:none;border-radius:6px}.bx-suggest-toggler{text-align:left;display:flex;border-radius:4px;overflow:hidden;background:#003861;height:45px;align-items:center}.bx-suggest-toggler label{flex:1;align-content:center;padding:0 10px;background:#004f87;height:100%}.bx-suggest-toggler span{display:inline-block;align-self:center;padding:10px;width:45px;text-align:center}.bx-suggest-toggler:hover,.bx-suggest-toggler:focus{cursor:pointer;background:#005da1}.bx-suggest-toggler:hover label,.bx-suggest-toggler:focus label{background:#006fbe}.bx-suggest-toggler[bx-open] span{transform:rotate(90deg)}.bx-suggest-toggler[bx-open]+ .bx-suggest-box{display:block}.bx-suggest-box{display:none}.bx-suggest-wrapper{display:flex;flex-direction:column;gap:10px;margin:10px}.bx-suggest-note{font-size:11px;color:#8c8c8c;font-style:italic;font-weight:100}.bx-suggest-link{font-size:14px;display:inline-block;margin-top:4px;padding:4px}.bx-suggest-row{display:flex;flex-direction:row;gap:10px}.bx-suggest-row label{flex:1;overflow:overlay;border-radius:4px}.bx-suggest-row label .bx-suggest-label{background:#323232;padding:4px 10px;font-size:12px;text-align:left}.bx-suggest-row label .bx-suggest-value{padding:6px;font-size:14px}.bx-suggest-row label .bx-suggest-value.bx-suggest-change{background-color:var(--bx-warning-color)}.bx-suggest-row.bx-suggest-ok input{visibility:hidden}.bx-suggest-row.bx-suggest-ok .bx-suggest-label{background-color:#008114}.bx-suggest-row.bx-suggest-ok .bx-suggest-value{background-color:#13a72a}.bx-suggest-row.bx-suggest-change .bx-suggest-label{background-color:#a65e08}.bx-suggest-row.bx-suggest-change .bx-suggest-value{background-color:#d57f18}.bx-suggest-row.bx-suggest-change:hover label{cursor:pointer}.bx-suggest-row.bx-suggest-change:hover .bx-suggest-label{background-color:#995707}.bx-suggest-row.bx-suggest-change:hover .bx-suggest-value{background-color:#bd7115}.bx-suggest-row.bx-suggest-change input:not(:checked) + label{opacity:.5}.bx-suggest-row.bx-suggest-change input:not(:checked) + label .bx-suggest-label{background-color:#2a2a2a}.bx-suggest-row.bx-suggest-change input:not(:checked) + label .bx-suggest-value{background-color:#393939}.bx-suggest-row.bx-suggest-change:hover input:not(:checked) + label{opacity:1}.bx-suggest-row.bx-suggest-change:hover input:not(:checked) + label .bx-suggest-label{background-color:#202020}.bx-suggest-row.bx-suggest-change:hover input:not(:checked) + label .bx-suggest-value{background-color:#303030}.bx-sub-content-box{background:#161616;padding:10px;box-shadow:0 0 12px #0f0f0f inset;border-radius:10px}.bx-settings-row .bx-sub-content-box{background:#202020;padding:12px;box-shadow:0 0 4px #000 inset;border-radius:6px}.bx-controller-extra-settings[data-has-gamepad=true] > :first-child{display:none}.bx-controller-extra-settings[data-has-gamepad=true] > :last-child{display:block}.bx-controller-extra-settings[data-has-gamepad=false] > :first-child{display:block}.bx-controller-extra-settings[data-has-gamepad=false] > :last-child{display:none}.bx-controller-extra-settings .bx-controller-extra-wrapper{flex:1;min-width:1px}.bx-controller-extra-settings .bx-sub-content-box{flex:1;text-align:left;display:flex;flex-direction:column;margin-top:10px}.bx-controller-extra-settings .bx-sub-content-box > label{font-size:14px}.bx-preset-row{display:flex;gap:8px}.bx-preset-row .bx-select{flex:1}.bx-toast{user-select:none;-webkit-user-select:none;position:fixed;left:50%;top:24px;transform:translate(-50%,0);background:#000;border-radius:16px;color:#fff;z-index:var(--bx-toast-z-index);font-family:var(--bx-normal-font);border:2px solid #fff;display:flex;align-items:center;opacity:0;overflow:clip;transition:opacity .2s ease-in}.bx-toast.bx-show{opacity:.85}.bx-toast.bx-hide{opacity:0;pointer-events:none}.bx-toast-msg{font-size:14px;display:inline-block;padding:12px 16px;white-space:pre}.bx-toast-status{font-weight:bold;font-size:14px;text-transform:uppercase;display:inline-block;background:#515863;padding:12px 16px;color:#fff;white-space:pre}.bx-wait-time-box{position:fixed;top:0;right:0;background-color:rgba(0,0,0,0.8);color:#fff;z-index:var(--bx-wait-time-box-z-index);padding:12px;border-radius:0 0 0 8px}.bx-wait-time-box label{display:block;text-transform:uppercase;text-align:right;font-size:12px;font-weight:bold;margin:0}.bx-wait-time-box span{display:block;font-family:var(--bx-monospaced-font);text-align:right;font-size:16px;margin-bottom:10px}.bx-wait-time-box span:last-of-type{margin-bottom:0}.bx-remote-play-container{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);color:#fff;background:#1a1b1e;border-radius:10px;width:420px;max-width:calc(100vw - 20px);margin:0 0 0 auto;padding:16px}.bx-remote-play-container > .bx-button{display:table;margin:0 0 0 auto}.bx-remote-play-settings{margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid #2d2d2d}.bx-remote-play-settings > div{display:flex}.bx-remote-play-settings label{flex:1}.bx-remote-play-settings label p{margin:4px 0 0;padding:0;color:#888;font-size:12px}.bx-remote-play-resolution{display:block}.bx-remote-play-resolution input[type="radio"]{accent-color:var(--bx-primary-button-color);margin-right:6px}.bx-remote-play-resolution input[type="radio"]:focus{accent-color:var(--bx-primary-button-hover-color)}.bx-remote-play-device-wrapper{display:flex;margin-bottom:12px}.bx-remote-play-device-wrapper:last-child{margin-bottom:2px}.bx-remote-play-device-info{flex:1;padding:4px 0}.bx-remote-play-device-name{font-size:20px;font-weight:bold;display:inline-block;vertical-align:middle}.bx-remote-play-console-type{font-size:12px;background:#004c87;color:#fff;display:inline-block;border-radius:14px;padding:2px 10px;margin-left:8px;vertical-align:middle}.bx-remote-play-power-state{color:#888;font-size:12px}.bx-remote-play-connect-button{min-height:100%;margin:4px 0}.bx-remote-play-buttons{display:flex;justify-content:space-between}select.bx-select{min-height:30px}div.bx-select{display:flex;align-items:stretch;flex:0 1 auto;gap:8px}div.bx-select select:disabled ~ button{display:none}div.bx-select select:disabled ~ div{background:#131416;color:#fff;pointer-events:none}div.bx-select select:disabled ~ div .bx-select-indicators{visibility:hidden}div.bx-select > div,div.bx-select button.bx-select-value{min-width:120px;text-align:left;line-height:24px;vertical-align:middle;background:#fff;color:#000;border-radius:4px;padding:2px 8px;display:flex;flex:1;flex-direction:column}div.bx-select > div{min-height:24px}div.bx-select > div input{display:inline-block;margin-right:8px}div.bx-select > div label{margin-bottom:0;font-size:14px;width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;min-height:15px}div.bx-select > div label span{display:block;font-size:10px;font-weight:bold;text-align:left;line-height:20px;white-space:pre;min-height:15px;align-content:center}div.bx-select button.bx-select-value{border:none;cursor:pointer;min-height:30px;font-size:.9rem;align-items:center}div.bx-select button.bx-select-value > div{display:flex;width:100%}div.bx-select button.bx-select-value span{flex:1;text-align:left;display:inline-block}div.bx-select button.bx-select-value input{margin:0 4px;accent-color:var(--bx-primary-button-color);pointer-events:none}div.bx-select button.bx-select-value:hover input,div.bx-select button.bx-select-value:focus input{accent-color:var(--bx-danger-button-color)}div.bx-select button.bx-select-value:hover::after,div.bx-select button.bx-select-value:focus::after{border-color:#4d4d4d !important}div.bx-select button.bx-button{border:none;width:24px;height:auto;padding:0;color:#fff;border-radius:4px;font-weight:bold;font-size:12px;font-family:var(--bx-monospaced-font);flex-shrink:0}div.bx-select button.bx-button span{line-height:unset}div.bx-select[data-controller-friendly=true] > div{box-sizing:content-box}div.bx-select[data-controller-friendly=true] select{position:absolute !important;top:-9999px !important;left:-9999px !important;visibility:hidden !important}div.bx-select[data-controller-friendly=false]{position:relative}div.bx-select[data-controller-friendly=false] > div{box-sizing:border-box}div.bx-select[data-controller-friendly=false] > div label{margin-right:24px}div.bx-select[data-controller-friendly=false] select:disabled{display:none}div.bx-select[data-controller-friendly=false] select:not(:disabled){cursor:pointer;position:absolute;top:0;right:0;bottom:0;display:block;opacity:0;z-index:calc(var(--bx-settings-z-index) + 1)}div.bx-select[data-controller-friendly=false] select:not(:disabled):hover + div{background:#f0f0f0}div.bx-select[data-controller-friendly=false] select:not(:disabled) + div label::after{content:\'▾\';font-size:14px;position:absolute;right:8px;pointer-events:none}.bx-select-indicators{display:flex;height:4px;gap:2px;margin-bottom:2px}.bx-select-indicators span{content:\' \';display:inline-block;flex:1;background:#cfcfcf;border-radius:4px;min-width:1px}.bx-select-indicators span[data-highlighted]{background:#9c9c9c;min-width:6px}.bx-select-indicators span[data-selected]{background:#aacfe7}.bx-select-indicators span[data-highlighted][data-selected]{background:#5fa3d0}.bx-guide-home-achievements-progress{display:flex;gap:10px;flex-direction:row}.bx-guide-home-achievements-progress .bx-button{margin-bottom:0 !important}body[data-bx-media-type=tv] .bx-guide-home-achievements-progress{flex-direction:column}body:not([data-bx-media-type=tv]) .bx-guide-home-achievements-progress{flex-direction:row}body:not([data-bx-media-type=tv]) .bx-guide-home-achievements-progress > button:first-of-type{flex:1}body:not([data-bx-media-type=tv]) .bx-guide-home-achievements-progress > button:last-of-type{width:40px}body:not([data-bx-media-type=tv]) .bx-guide-home-achievements-progress > button:last-of-type span{display:none}.bx-guide-home-buttons > div{display:flex;flex-direction:row;gap:12px}body[data-bx-media-type=tv] .bx-guide-home-buttons > div{flex-direction:column}body[data-bx-media-type=tv] .bx-guide-home-buttons > div button{margin-bottom:0 !important}body:not([data-bx-media-type=tv]) .bx-guide-home-buttons > div button span{display:none}.bx-guide-home-buttons[data-is-playing="true"] button[data-state=\'normal\']{display:none}.bx-guide-home-buttons[data-is-playing="false"] button[data-state=\'playing\']{display:none}div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module]{overflow:visible}.bx-stream-menu-button-on{fill:#000 !important;background-color:#2d2d2d !important;color:#000 !important}.bx-stream-refresh-button{top:calc(env(safe-area-inset-top, 0px) + 10px + 50px) !important}body[data-media-type=default] .bx-stream-refresh-button{left:calc(env(safe-area-inset-left, 0px) + 11px) !important}body[data-media-type=tv] .bx-stream-refresh-button{top:calc(var(--gds-focus-borderSize) + 80px) !important}.bx-stream-home-button{top:calc(env(safe-area-inset-top, 0px) + 10px + 50px * 2) !important}body[data-media-type=default] .bx-stream-home-button{left:calc(env(safe-area-inset-left, 0px) + 12px) !important}body[data-media-type=tv] .bx-stream-home-button{top:calc(var(--gds-focus-borderSize) + 80px * 2) !important}div[data-testid=media-container][data-position=center]{display:flex}div[data-testid=media-container][data-position=top] video,div[data-testid=media-container][data-position=top] canvas{top:0}div[data-testid=media-container][data-position=bottom] video,div[data-testid=media-container][data-position=bottom] canvas{bottom:0}#game-stream video{margin:auto;align-self:center;background:#000;position:absolute;left:0;right:0}#game-stream canvas{align-self:center;margin:auto;position:absolute;left:0;right:0}#game-stream.bx-taking-screenshot:before{animation:bx-anim-taking-screenshot .5s ease;content:\' \';position:absolute;width:100%;height:100%;z-index:var(--bx-screenshot-animation-z-index)}#gamepass-dialog-root div[class^=Guide-module__guide] .bx-button{overflow:visible;margin-bottom:12px}@-moz-keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}@-webkit-keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}@-o-keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}@keyframes bx-anim-taking-screenshot{0%{border:0 solid rgba(255,255,255,0.502)}50%{border:8px solid rgba(255,255,255,0.502)}100%{border:0 solid rgba(255,255,255,0.502)}}.bx-number-stepper{text-align:center}.bx-number-stepper > div{display:flex;align-items:center}.bx-number-stepper > div span{flex:1;display:inline-block;min-width:40px;font-family:var(--bx-monospaced-font);white-space:pre;font-size:13px;margin:0 4px}.bx-number-stepper > div button{flex-shrink:0;border:none;width:24px;height:24px;margin:0;line-height:24px;background-color:var(--bx-default-button-color);color:#fff;border-radius:4px;font-weight:bold;font-size:14px;font-family:var(--bx-monospaced-font)}@media (hover:hover){.bx-number-stepper > div button:hover{background-color:var(--bx-default-button-hover-color)}}.bx-number-stepper > div button:active{background-color:var(--bx-default-button-hover-color)}.bx-number-stepper > div button:disabled + span{font-family:var(--bx-title-font)}.bx-number-stepper input[type=range]{display:block;margin:8px 0 2px auto;min-width:180px;width:100%;color:#959595 !important}.bx-number-stepper input[type=range]:disabled,.bx-number-stepper button:disabled{display:none}.bx-number-stepper[data-disabled=true] input[type=range],.bx-number-stepper[disabled=true] input[type=range],.bx-number-stepper[data-disabled=true] button,.bx-number-stepper[disabled=true] button{display:none}.bx-dual-number-stepper > span{display:block;font-family:var(--bx-monospaced-font);font-size:13px;white-space:pre;margin:0 4px;text-align:center}.bx-dual-number-stepper > div input[type=range]{display:block;width:100%;min-width:180px;background:transparent;color:#959595 !important;appearance:none;padding:8px 0}.bx-dual-number-stepper > div input[type=range]::-webkit-slider-runnable-track{background:linear-gradient(90deg,#fff var(--from),var(--bx-primary-button-color) var(--from) var(--to),#fff var(--to) 100%);height:8px;border-radius:2px}.bx-dual-number-stepper > div input[type=range]::-moz-range-track{background:linear-gradient(90deg,#fff var(--from),var(--bx-primary-button-color) var(--from) var(--to),#fff var(--to) 100%);height:8px;border-radius:2px}.bx-dual-number-stepper > div input[type=range]::-webkit-slider-thumb{margin-top:-4px;appearance:none;width:4px;height:16px;background:#00b85f;border:none;border-radius:2px}.bx-dual-number-stepper > div input[type=range]::-moz-range-thumb{margin-top:-4px;appearance:none;width:4px;height:16px;background:#00b85f;border:none;border-radius:2px}.bx-dual-number-stepper > div input[type=range]:hover::-webkit-slider-runnable-track,.bx-dual-number-stepper > div input[type=range].bx-dual-number-stepper > div input[type=range]:active::-webkit-slider-runnable-track,.bx-dual-number-stepper > div input[type=range]:focus::-webkit-slider-runnable-track{background:linear-gradient(90deg,#fff var(--from),#006635 var(--from) var(--to),#fff var(--to) 100%)}.bx-dual-number-stepper > div input[type=range]:hover::-moz-range-track,.bx-dual-number-stepper > div input[type=range].bx-dual-number-stepper > div input[type=range]:active::-moz-range-track,.bx-dual-number-stepper > div input[type=range]:focus::-moz-range-track{background:linear-gradient(90deg,#fff var(--from),#006635 var(--from) var(--to),#fff var(--to) 100%)}.bx-dual-number-stepper > div input[type=range]:hover::-webkit-slider-thumb,.bx-dual-number-stepper > div input[type=range].bx-dual-number-stepper > div input[type=range]:active::-webkit-slider-thumb,.bx-dual-number-stepper > div input[type=range]:focus::-webkit-slider-thumb{background:#fb3232}.bx-dual-number-stepper > div input[type=range]:hover::-moz-range-thumb,.bx-dual-number-stepper > div input[type=range].bx-dual-number-stepper > div input[type=range]:active::-moz-range-thumb,.bx-dual-number-stepper > div input[type=range]:focus::-moz-range-thumb{background:#fb3232}.bx-dual-number-stepper[data-disabled=true] input[type=range],.bx-dual-number-stepper[disabled=true] input[type=range]{display:none}#bx-game-bar{z-index:var(--bx-game-bar-z-index);position:fixed;bottom:0;width:40px;height:90px;overflow:visible;cursor:pointer}#bx-game-bar > svg{display:none;pointer-events:none;position:absolute;height:28px;margin-top:16px}@media (hover:hover){#bx-game-bar:hover > svg{display:block}}#bx-game-bar .bx-game-bar-container{opacity:0;position:absolute;display:flex;overflow:hidden;background:rgba(26,27,30,0.91);box-shadow:0 0 6px #1c1c1c;transition:opacity .1s ease-in}#bx-game-bar .bx-game-bar-container.bx-show{opacity:.9}#bx-game-bar .bx-game-bar-container.bx-show + svg{display:none !important}#bx-game-bar .bx-game-bar-container.bx-hide{opacity:0;pointer-events:none}#bx-game-bar .bx-game-bar-container button{width:60px;height:60px;border-radius:0}#bx-game-bar .bx-game-bar-container button svg{width:28px;height:28px;transition:transform .08s ease 0s}#bx-game-bar .bx-game-bar-container button:hover{border-radius:0}#bx-game-bar .bx-game-bar-container button:active svg{transform:scale(.75)}#bx-game-bar .bx-game-bar-container button.bx-activated{background-color:#fff}#bx-game-bar .bx-game-bar-container button.bx-activated svg{filter:invert(1)}#bx-game-bar .bx-game-bar-container div[data-activated] button{display:none}#bx-game-bar .bx-game-bar-container div[data-activated=\'false\'] button:first-of-type{display:block}#bx-game-bar .bx-game-bar-container div[data-activated=\'true\'] button:last-of-type{display:block}#bx-game-bar[data-position="bottom-left"]{left:0;direction:ltr}#bx-game-bar[data-position="bottom-left"] .bx-game-bar-container{border-radius:0 10px 10px 0}#bx-game-bar[data-position="bottom-right"]{right:0;direction:rtl}#bx-game-bar[data-position="bottom-right"] .bx-game-bar-container{direction:ltr;border-radius:10px 0 0 10px}.bx-badges{margin-left:0;user-select:none;-webkit-user-select:none}.bx-badge{border:none;display:inline-block;line-height:24px;color:#fff;font-family:var(--bx-title-font-semibold);font-size:14px;font-weight:400;margin:0 8px 8px 0;box-shadow:0 0 6px #000;border-radius:4px}.bx-badge-name{background-color:#2d3036;border-radius:4px 0 0 4px}.bx-badge-name svg{width:16px;height:16px}.bx-badge-value{background-color:#808080;border-radius:0 4px 4px 0}.bx-badge-name,.bx-badge-value{display:inline-block;padding:0 8px;line-height:30px;vertical-align:bottom}.bx-badge-battery[data-charging=true] span:first-of-type::after{content:\' ⚡️\'}div[class^=StreamMenu-module__container] .bx-badges{position:absolute;max-width:500px}#gamepass-dialog-root .bx-badges{position:fixed;top:60px;left:460px;max-width:500px}@media (min-width:568px) and (max-height:480px){#gamepass-dialog-root .bx-badges{position:unset;top:unset;left:unset;margin:8px 0}}.bx-stats-bar{display:flex;flex-direction:row;gap:8px;user-select:none;-webkit-user-select:none;position:fixed;top:0;background-color:#000;color:#fff;font-family:var(--bx-monospaced-font);font-size:.9rem;padding-left:8px;z-index:var(--bx-stats-bar-z-index);text-wrap:nowrap}.bx-stats-bar[data-stats*="[time]"] > .bx-stat-time,.bx-stats-bar[data-stats*="[play]"] > .bx-stat-play,.bx-stats-bar[data-stats*="[batt]"] > .bx-stat-batt,.bx-stats-bar[data-stats*="[fps]"] > .bx-stat-fps,.bx-stats-bar[data-stats*="[ping]"] > .bx-stat-ping,.bx-stats-bar[data-stats*="[jit]"] > .bx-stat-jit,.bx-stats-bar[data-stats*="[btr]"] > .bx-stat-btr,.bx-stats-bar[data-stats*="[dt]"] > .bx-stat-dt,.bx-stats-bar[data-stats*="[pl]"] > .bx-stat-pl,.bx-stats-bar[data-stats*="[fl]"] > .bx-stat-fl,.bx-stats-bar[data-stats*="[dl]"] > .bx-stat-dl,.bx-stats-bar[data-stats*="[ul]"] > .bx-stat-ul{display:inline-flex;align-items:baseline}.bx-stats-bar[data-stats$="[time]"] > .bx-stat-time,.bx-stats-bar[data-stats$="[play]"] > .bx-stat-play,.bx-stats-bar[data-stats$="[batt]"] > .bx-stat-batt,.bx-stats-bar[data-stats$="[fps]"] > .bx-stat-fps,.bx-stats-bar[data-stats$="[ping]"] > .bx-stat-ping,.bx-stats-bar[data-stats$="[jit]"] > .bx-stat-jit,.bx-stats-bar[data-stats$="[btr]"] > .bx-stat-btr,.bx-stats-bar[data-stats$="[dt]"] > .bx-stat-dt,.bx-stats-bar[data-stats$="[pl]"] > .bx-stat-pl,.bx-stats-bar[data-stats$="[fl]"] > .bx-stat-fl,.bx-stats-bar[data-stats$="[dl]"] > .bx-stat-dl,.bx-stats-bar[data-stats$="[ul]"] > .bx-stat-ul{border-right:none}.bx-stats-bar::before{display:none;content:\'👀\';vertical-align:middle;margin-right:8px}.bx-stats-bar[data-display=glancing]::before{display:inline-block}.bx-stats-bar[data-position=top-left]{left:0;border-radius:0 0 4px 0}.bx-stats-bar[data-position=top-right]{right:0;border-radius:0 0 0 4px}.bx-stats-bar[data-position=top-center]{transform:translate(-50%,0);left:50%;border-radius:0 0 4px 4px}.bx-stats-bar[data-shadow=true]{background:none;filter:drop-shadow(1px 0 0 rgba(0,0,0,0.941)) drop-shadow(-1px 0 0 rgba(0,0,0,0.941)) drop-shadow(0 1px 0 rgba(0,0,0,0.941)) drop-shadow(0 -1px 0 rgba(0,0,0,0.941))}.bx-stats-bar > div{display:none;border-right:1px solid #fff;padding-right:8px}.bx-stats-bar label{margin:0 8px 0 0;font-family:var(--bx-title-font);font-size:70%;font-weight:bold;vertical-align:middle;cursor:help}.bx-stats-bar span{display:inline-block;text-align:right;vertical-align:middle;white-space:pre}.bx-stats-bar span[data-grade=good]{color:#6bffff}.bx-stats-bar span[data-grade=ok]{color:#fff16b}.bx-stats-bar span[data-grade=bad]{color:#ff5f5f}.bx-mkb-settings{display:flex;flex-direction:column;flex:1;padding-bottom:10px;overflow:hidden}.bx-mkb-pointer-lock-msg{user-select:none;-webkit-user-select:none;position:fixed;left:50%;bottom:40px;transform:translateX(-50%);margin:auto;background:#151515;z-index:var(--bx-mkb-pointer-lock-msg-z-index);color:#fff;font-weight:400;font-family:"Segoe UI",Arial,Helvetica,sans-serif;font-size:1.3rem;padding:12px;border-radius:8px;align-items:center;box-shadow:0 0 6px #000;min-width:300px;opacity:.9;display:flex;flex-direction:column;gap:10px}.bx-mkb-pointer-lock-msg:hover{opacity:1}.bx-mkb-pointer-lock-msg > p{margin:0;width:100%;font-size:22px;margin-bottom:4px;font-weight:bold;text-align:left}.bx-mkb-pointer-lock-msg > div{width:100%;display:flex;flex-direction:row;gap:10px}.bx-mkb-pointer-lock-msg > div button:first-of-type{flex-shrink:1}.bx-mkb-pointer-lock-msg > div button:last-of-type{flex-grow:1}.bx-mkb-key-row{display:flex;margin-bottom:10px;align-items:center;gap:20px}.bx-mkb-key-row label{margin-bottom:0;font-family:var(--bx-promptfont-font);font-size:32px;text-align:center}.bx-mkb-settings.bx-editing .bx-mkb-key-row button{background:#393939;border-radius:4px;border:none}.bx-mkb-settings.bx-editing .bx-mkb-key-row button:hover{background:#333;cursor:pointer}.bx-mkb-action-buttons > div{text-align:right;display:none}.bx-mkb-action-buttons button{margin-left:8px}.bx-mkb-settings:not(.bx-editing) .bx-mkb-action-buttons > div:first-child{display:block}.bx-mkb-settings.bx-editing .bx-mkb-action-buttons > div:last-child{display:block}.bx-mkb-note{display:block;margin:0 0 10px;font-size:12px;text-align:center}button.bx-binding-button{flex:1;min-height:38px;border:none;border-radius:4px;font-size:14px;color:#fff;display:flex;align-items:center;align-self:center;padding:0 6px}button.bx-binding-button:disabled{background:#131416;padding:0 8px}button.bx-binding-button:not(:disabled){border:2px solid transparent;border-top:none;border-bottom:4px solid #252525;background:#3b3b3b;cursor:pointer}button.bx-binding-button:not(:disabled):hover,button.bx-binding-button:not(:disabled).bx-focusable:focus{background:#20b217;border-bottom-color:#186c13}button.bx-binding-button:not(:disabled):active{background:#16900f;border-bottom:3px solid #0c4e08;border-left-width:2px;border-right-width:2px}button.bx-binding-button:not(:disabled).bx-focusable:focus::after{top:-6px;left:-8px;right:-8px;bottom:-10px}.bx-settings-row .bx-binding-button-wrapper button.bx-binding-button{min-width:60px}.bx-controller-customizations-container .bx-btn-detect{display:block;margin-bottom:20px}.bx-controller-customizations-container .bx-btn-detect.bx-monospaced{background:none;font-weight:bold;font-size:12px}.bx-controller-customizations-container .bx-buttons-grid{display:grid;grid-template-columns:auto auto;column-gap:20px;row-gap:10px;margin-bottom:20px}.bx-controller-key-row{display:flex;align-items:stretch}.bx-controller-key-row > label{margin-bottom:0;font-family:var(--bx-promptfont-font);font-size:32px;text-align:center;min-width:50px;flex-shrink:0;display:flex;align-self:center}.bx-controller-key-row > label::after{content:\'❯\';margin:0 12px;font-size:16px;align-self:center}.bx-controller-key-row .bx-select{width:100% !important}.bx-controller-key-row .bx-select > div{min-width:50px}.bx-controller-key-row .bx-select label{font-family:var(--bx-promptfont-font),var(--bx-normal-font);font-size:32px;text-align:center;margin-bottom:6px;height:40px;line-height:40px}.bx-controller-key-row:hover > label{color:#ffe64b}.bx-controller-key-row:hover > label::after{color:#fff}.bx-product-details-buttons{display:flex;gap:10px;flex-direction:row}.bx-product-details-buttons button{max-width:max-content;margin:10px 0 0 0;display:flex}@media (min-width:568px) and (max-height:480px){.bx-product-details-buttons{flex-direction:column}.bx-product-details-buttons button{margin:8px 0 0 10px}}', PREF_HIDE_SECTIONS = getPref("ui.hideSections"), selectorToHide = [];
if (PREF_HIDE_SECTIONS.includes("news")) selectorToHide.push("#BodyContent > div[class*=CarouselRow-module]");
if (getPref("block.features").includes("byog")) selectorToHide.push("#BodyContent > div[class*=ByogRow-module__container___]");
if (PREF_HIDE_SECTIONS.includes("all-games")) selectorToHide.push("#BodyContent div[class*=AllGamesRow-module__gridContainer]"), selectorToHide.push("#BodyContent div[class*=AllGamesRow-module__rowHeader]");
@@ -9171,7 +9423,7 @@ class RootDialogObserver {
static $btnShortcut = AppInterface && createButton({
icon: BxIcon.CREATE_SHORTCUT,
label: t("create-shortcut"),
- style: 64 | 8 | 128 | 2048 | 4096,
+ style: 64 | 8 | 128 | 4096 | 8192,
onClick: (e) => {
window.BX_EXPOSED.dialogRoutes?.closeAll();
let $btn = e.target.closest("button");
@@ -9181,7 +9433,7 @@ class RootDialogObserver {
static $btnWallpaper = AppInterface && createButton({
icon: BxIcon.DOWNLOAD,
label: t("wallpaper"),
- style: 64 | 8 | 128 | 2048 | 4096,
+ style: 64 | 8 | 128 | 4096 | 8192,
onClick: (e) => {
window.BX_EXPOSED.dialogRoutes?.closeAll();
let $btn = e.target.closest("button"), details = parseDetailsPath($btn.dataset.path);
diff --git a/package.json b/package.json
index f082668..14c4fcc 100755
--- a/package.json
+++ b/package.json
@@ -11,10 +11,10 @@
},
"devDependencies": {
"@types/bun": "^1.1.14",
- "@types/node": "^22.10.1",
+ "@types/node": "^22.10.2",
"@types/stylus": "^0.48.43",
- "eslint": "^9.16.0",
- "eslint-plugin-compat": "^6.0.1",
+ "eslint": "^9.17.0",
+ "eslint-plugin-compat": "^6.0.2",
"stylus": "^0.64.0"
},
"peerDependencies": {
diff --git a/src/assets/css/controller.styl b/src/assets/css/controller.styl
new file mode 100644
index 0000000..e12089d
--- /dev/null
+++ b/src/assets/css/controller.styl
@@ -0,0 +1,71 @@
+.bx-controller-customizations-container {
+ .bx-btn-detect {
+ display: block;
+ margin-bottom: 20px;
+
+ &.bx-monospaced {
+ background: none;
+ font-weight: bold;
+ font-size: 12px;
+ }
+ }
+
+ .bx-buttons-grid {
+ display: grid;
+ grid-template-columns: auto auto;
+ column-gap: 20px;
+ row-gap: 10px;
+ margin-bottom: 20px;
+ }
+
+}
+
+.bx-controller-key-row {
+ display: flex;
+ align-items: stretch;
+
+ > label {
+ margin-bottom: 0;
+ font-family: var(--bx-promptfont-font);
+ font-size: 32px;
+ text-align: center;
+ min-width: 50px;
+ flex-shrink: 0;
+ display: flex;
+ align-self: center;
+
+ &::after {
+ content: '❯';
+ margin: 0 12px;
+ font-size: 16px;
+ align-self: center;
+ }
+ }
+
+ .bx-select {
+ width: 100% !important;
+
+ > div {
+ min-width: 50px;
+ }
+
+ label {
+ font-family: var(--bx-promptfont-font), var(--bx-normal-font);
+ font-size: 32px;
+ text-align: center;
+ margin-bottom: 6px;
+ height: 40px;
+ line-height: 40px;
+ }
+ }
+
+ &:hover {
+ > label {
+ color: #ffe64b;
+
+ &::after {
+ color: #fff;
+ }
+ }
+ }
+}
diff --git a/src/assets/css/navigation-dialog.styl b/src/assets/css/navigation-dialog.styl
index fbcb88c..70af076 100755
--- a/src/assets/css/navigation-dialog.styl
+++ b/src/assets/css/navigation-dialog.styl
@@ -17,6 +17,21 @@
border-radius: 4px;
padding: 0 5px;
}
+
+ .bx-focusable {
+ &::after {
+ border-radius: 4px;
+ }
+
+ &:focus::after {
+ offset = 0;
+
+ top: offset;
+ left: offset;
+ right: offset;
+ bottom: offset;
+ }
+ }
}
.bx-navigation-dialog-overlay {
@@ -42,7 +57,7 @@
color: white;
background: #1a1b1e;
border-radius: 10px;
- width: 450px;
+ min-width: @css{ min(calc(100vw - 20px), 500px) };
max-width: calc(100vw - 20px);
margin: 0 0 0 auto;
padding: 16px;
@@ -74,11 +89,9 @@
.bx-dialog-content {
flex: 1;
+ padding: 6px;
overflow: auto;
overflow-x: hidden;
-
- > div {
- }
}
.bx-dialog-preset-tools {
@@ -86,8 +99,9 @@
margin-bottom: 12px;
gap: 6px;
- select {
- flex: 1;
+ button {
+ align-self: center;
+ min-height: 50px;
}
}
@@ -164,29 +178,6 @@
letter-spacing: 6px;
}
}
-
- .bx-shortcut-actions {
- flex: 1;
- position: relative;
-
- select {
- width: 100%;
- height: 100%;
- min-height: 38px;
- display: block;
-
- &:first-of-type {
- position: absolute;
- top: 0;
- left: 0;
- }
-
- &:last-of-type {
- opacity: 0;
- z-index: calc(var(--bx-settings-z-index) + 1);
- }
- }
- }
}
select:disabled {
diff --git a/src/assets/css/number-stepper.styl b/src/assets/css/number-stepper.styl
index b017633..3e21957 100755
--- a/src/assets/css/number-stepper.styl
+++ b/src/assets/css/number-stepper.styl
@@ -10,6 +10,7 @@
display: inline-block;
min-width: 40px;
font-family: var(--bx-monospaced-font);
+ white-space: pre;
font-size: 13px;
margin: 0 4px;
}
@@ -44,7 +45,7 @@
}
}
- input[type="range"] {
+ input[type=range] {
display: block;
margin: 8px 0 2px auto;
min-width: 180px;
@@ -62,3 +63,91 @@
}
}
}
+
+
+.bx-dual-number-stepper {
+ > span {
+ display: block;
+ font-family: var(--bx-monospaced-font);
+ font-size: 13px;
+ white-space: pre;
+ margin: 0 4px;
+ text-align: center;
+ }
+
+ > div {
+ input[type=range] {
+ display: block;
+ width: 100%;
+ min-width: 180px;
+ background: transparent;
+ color: #959595 !important;
+ appearance: none;
+ padding: 8px 0;
+
+ range-track() {
+ background: linear-gradient(90deg, #fff var(--from), var(--bx-primary-button-color) var(--from) var(--to), #fff var(--to) 100%);
+ height: 8px;
+ border-radius: 2px;
+ }
+
+ range-track-hover() {
+ background: linear-gradient(90deg, #fff var(--from), #006635 var(--from) var(--to), #fff var(--to) 100%);
+ }
+
+ thumb() {
+ margin-top: -4px;
+ appearance: none;
+ width: 4px;
+ height: 16px;
+ background: #00b85f;
+ border: none;
+ border-radius: 2px;
+ }
+
+ thumb-hover() {
+ background: #fb3232;
+ }
+
+ &::-webkit-slider-runnable-track {
+ range-track()
+ }
+
+ &::-moz-range-track {
+ range-track()
+ }
+
+ &::-webkit-slider-thumb {
+ thumb();
+ }
+
+ &::-moz-range-thumb {
+ thumb();
+ }
+
+ &:hover, &&:active, &:focus {
+ &::-webkit-slider-runnable-track {
+ range-track-hover();
+ }
+
+ &::-moz-range-track {
+ range-track-hover();
+ }
+
+ &::-webkit-slider-thumb {
+ thumb-hover();
+ }
+
+ &::-moz-range-thumb {
+ thumb-hover();
+ }
+ }
+ }
+ }
+
+ &[data-disabled=true], &[disabled=true] {
+ input[type=range] {
+ display: none;
+ }
+ }
+}
diff --git a/src/assets/css/root.styl b/src/assets/css/root.styl
index 957e48d..da56668 100755
--- a/src/assets/css/root.styl
+++ b/src/assets/css/root.styl
@@ -47,6 +47,7 @@ button_color(name, normal, hover, active, disabled)
@font-face {
font-family: 'promptfont';
src: url('https://redphx.github.io/better-xcloud/fonts/promptfont.otf');
+ unicode-range: U+2196-E011;
}
/* Fix Stream menu buttons not hiding */
@@ -73,6 +74,10 @@ div[class^=HUDButton-module__hiddenContainer] ~ div:not([class^=HUDButton-module
height: 100% !important;
}
+.bx-auto-height {
+ height: auto !important;
+}
+
.bx-no-scroll {
overflow: hidden !important;
}
@@ -125,6 +130,10 @@ div[class^=HUDButton-module__hiddenContainer] ~ div:not([class^=HUDButton-module
font-family: var(--bx-promptfont-font) !important;
}
+.bx-monospaced {
+ font-family: var(--bx-monospaced-font) !important;
+}
+
.bx-line-through {
text-decoration: line-through !important;
}
@@ -270,3 +279,15 @@ div[class*=SupportedInputsBadge] {
opacity: 0;
}
}
+
+.bx-horizontal-shaking {
+ animation: bx-horizontal-shaking .4s ease-in-out 2;
+}
+
+@keyframes bx-horizontal-shaking {
+ 0% { transform: translateX(0) }
+ 25% { transform: translateX(5px) }
+ 50% { transform: translateX(-5px) }
+ 75% { transform: translateX(5px) }
+ 100% { transform: translateX(0) }
+}
diff --git a/src/assets/css/settings-dialog.styl b/src/assets/css/settings-dialog.styl
index 925ebd5..092676b 100755
--- a/src/assets/css/settings-dialog.styl
+++ b/src/assets/css/settings-dialog.styl
@@ -8,21 +8,6 @@
user-select: none;
-webkit-user-select: none;
- .bx-focusable {
- &::after {
- border-radius: 4px;
- }
-
- &:focus::after {
- offset = 0;
-
- top: offset;
- left: offset;
- right: offset;
- bottom: offset;
- }
- }
-
.bx-settings-reload-note {
font-size: 0.8rem;
display: block;
diff --git a/src/assets/css/styles.styl b/src/assets/css/styles.styl
index b8c1a15..62c9b80 100755
--- a/src/assets/css/styles.styl
+++ b/src/assets/css/styles.styl
@@ -16,4 +16,5 @@
@import 'game-bar.styl';
@import 'stream-stats.styl';
@import 'mkb.styl';
+@import 'controller.styl';
@import 'misc.styl';
diff --git a/src/assets/css/web-components.styl b/src/assets/css/web-components.styl
index 7eb2a8f..6989178 100755
--- a/src/assets/css/web-components.styl
+++ b/src/assets/css/web-components.styl
@@ -4,17 +4,11 @@ select.bx-select {
div.bx-select {
display: flex;
- align-items: center;
+ align-items: stretch;
flex: 0 1 auto;
gap: 8px;
select {
- // Render offscreen instead of "display: none" so we could get its size
- position: absolute !important;
- top: -9999px !important;
- left: -9999px !important;
- visibility: hidden !important;
-
&:disabled {
& ~ button {
display: none;
@@ -48,7 +42,6 @@ div.bx-select {
> div {
min-height: 24px;
- box-sizing: content-box;
input {
display: inline-block;
@@ -69,7 +62,7 @@ div.bx-select {
font-size: 10px;
font-weight: bold;
text-align: left;
- line-height: initial;
+ line-height: 20px;
white-space: pre;
min-height: 15px;
align-content: center;
@@ -115,10 +108,9 @@ div.bx-select {
button.bx-button {
border: none;
- height: 24px;
width: 24px;
+ height: auto;
padding: 0;
- line-height: 24px;
color: #fff;
border-radius: 4px;
font-weight: bold;
@@ -130,6 +122,68 @@ div.bx-select {
line-height: unset;
}
}
+
+ &[data-controller-friendly=true] {
+ > div {
+ box-sizing: content-box;
+ }
+
+ select {
+ // Render offscreen instead of "display: none" so we could get its size
+ position: absolute !important;
+ top: -9999px !important;
+ left: -9999px !important;
+ visibility: hidden !important;
+ }
+ }
+
+ &[data-controller-friendly=false] {
+ position: relative;
+
+ > div {
+ box-sizing: border-box;
+
+ label {
+ margin-right: 24px;
+ }
+ }
+
+ select {
+ &:disabled {
+ display: none;
+ }
+
+ &:not(:disabled) {
+ cursor: pointer;
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ display: block;
+
+ opacity: 0;
+ z-index: calc(var(--bx-settings-z-index) + 1);
+
+ &:hover {
+ + div {
+ background: #f0f0f0;
+ }
+ }
+
+ + div {
+ label {
+ &::after {
+ content: '▾';
+ font-size: 14px;
+ position: absolute;
+ right: 8px;
+ pointer-events: none;
+ }
+ }
+ }
+ }
+ }
+ }
}
.bx-select-indicators {
@@ -144,9 +198,11 @@ div.bx-select {
flex: 1;
background: #cfcfcf;
border-radius: 4px;
+ min-width: 1px;
&[data-highlighted] {
background: #9c9c9c;
+ min-width: 6px;
}
&[data-selected] {
diff --git a/src/assets/svg/pencil-simple-line.svg b/src/assets/svg/pencil-simple-line.svg
new file mode 100644
index 0000000..c1bd3b2
--- /dev/null
+++ b/src/assets/svg/pencil-simple-line.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/enums/gamepad.ts b/src/enums/gamepad.ts
index 1f19674..70994e4 100755
--- a/src/enums/gamepad.ts
+++ b/src/enums/gamepad.ts
@@ -24,11 +24,13 @@ export enum GamepadKey {
LS_DOWN = 101,
LS_LEFT = 102,
LS_RIGHT = 103,
+ LS = 104,
RS_UP = 200,
RS_DOWN = 201,
RS_LEFT = 202,
RS_RIGHT = 203,
+ RS = 204,
};
export const GamepadKeyName: Record = {
@@ -56,12 +58,16 @@ export const GamepadKeyName: Record = {
[GamepadKey.LS_DOWN]: ['Left Stick Down', PrompFont.LS_DOWN],
[GamepadKey.LS_LEFT]: ['Left Stick Left', PrompFont.LS_LEFT],
[GamepadKey.LS_RIGHT]: ['Left Stick Right', PrompFont.LS_RIGHT],
+ [GamepadKey.LS]: ['Left Stick', PrompFont.LS],
[GamepadKey.R3]: ['R3', PrompFont.R3],
[GamepadKey.RS_UP]: ['Right Stick Up', PrompFont.RS_UP],
[GamepadKey.RS_DOWN]: ['Right Stick Down', PrompFont.RS_DOWN],
[GamepadKey.RS_LEFT]: ['Right Stick Left', PrompFont.RS_LEFT],
[GamepadKey.RS_RIGHT]: ['Right Stick Right', PrompFont.RS_RIGHT],
+ [GamepadKey.RS]: ['Right Stick', PrompFont.RS],
+
+ [GamepadKey.SHARE]: ['Screenshot', PrompFont.SHARE],
};
diff --git a/src/enums/pref-keys.ts b/src/enums/pref-keys.ts
index 7f8d29e..bc84df1 100755
--- a/src/enums/pref-keys.ts
+++ b/src/enums/pref-keys.ts
@@ -1,3 +1,5 @@
+import type { BlockFeature, CodecProfile, DeviceVibrationMode, GameBarPosition, LoadingScreenRocket, NativeMkbMode, StreamPlayerType, StreamResolution, StreamStat, StreamStatPosition, StreamVideoProcessing, TouchControllerMode, TouchControllerStyleCustom, TouchControllerStyleStandard, UiLayout, UiSection, VideoPosition, VideoPowerPreference, VideoRatio } from "./pref-values"
+
export const enum StorageKey {
GLOBAL = 'BetterXcloud',
@@ -12,6 +14,7 @@ export const enum StorageKey {
LIST_FORCE_NATIVE_MKB = 'BetterXcloud.GhPages.ForceNativeMkb',
}
+
export const enum PrefKey {
VERSION_LAST_CHECK = 'version.lastCheck',
VERSION_LATEST = 'version.latest',
@@ -112,3 +115,78 @@ export const enum PrefKey {
GAME_FORTNITE_FORCE_CONSOLE = 'game.fortnite.forceConsole',
}
+
+
+export type PrefTypeMap = {
+ [PrefKey.AUDIO_MIC_ON_PLAYING]: boolean,
+ [PrefKey.AUDIO_VOLUME_CONTROL_ENABLED]: boolean,
+ [PrefKey.AUDIO_VOLUME]: number,
+ [PrefKey.BLOCK_FEATURES]: BlockFeature[],
+ [PrefKey.BLOCK_TRACKING]: boolean,
+ [PrefKey.CONTROLLER_POLLING_RATE]: number,
+ [PrefKey.DEVICE_VIBRATION_INTENSITY]: number,
+ [PrefKey.DEVICE_VIBRATION_MODE]: DeviceVibrationMode,
+ [PrefKey.GAME_BAR_POSITION]: GameBarPosition,
+ [PrefKey.GAME_FORTNITE_FORCE_CONSOLE]: boolean,
+ [PrefKey.KEYBOARD_SHORTCUTS_IN_GAME_PRESET_ID]: number,
+ [PrefKey.LOADING_SCREEN_GAME_ART]: boolean,
+ [PrefKey.LOADING_SCREEN_ROCKET]: LoadingScreenRocket,
+ [PrefKey.LOADING_SCREEN_SHOW_WAIT_TIME]: boolean,
+ [PrefKey.LOCAL_CO_OP_ENABLED]: boolean,
+ [PrefKey.MKB_ENABLED]: boolean,
+ [PrefKey.MKB_HIDE_IDLE_CURSOR]: boolean,
+ [PrefKey.MKB_P1_MAPPING_PRESET_ID]: number,
+ [PrefKey.MKB_P1_SLOT]: number,
+ [PrefKey.NATIVE_MKB_FORCED_GAMES]: string[],
+ [PrefKey.NATIVE_MKB_MODE]: NativeMkbMode,
+ [PrefKey.NATIVE_MKB_SCROLL_HORIZONTAL_SENSITIVITY]: number,
+ [PrefKey.NATIVE_MKB_SCROLL_VERTICAL_SENSITIVITY]: number,
+ [PrefKey.REMOTE_PLAY_ENABLED]: boolean,
+ [PrefKey.REMOTE_PLAY_STREAM_RESOLUTION]: StreamResolution,
+ [PrefKey.SCREENSHOT_APPLY_FILTERS]: boolean,
+ [PrefKey.SERVER_BYPASS_RESTRICTION]: string,
+ [PrefKey.SERVER_PREFER_IPV6]: boolean,
+ [PrefKey.SERVER_REGION]: string,
+ [PrefKey.STATS_CONDITIONAL_FORMATTING]: boolean,
+ [PrefKey.STATS_ITEMS]: StreamStat[],
+ [PrefKey.STATS_OPACITY_ALL]: number,
+ [PrefKey.STATS_OPACITY_BACKGROUND]: number,
+ [PrefKey.STATS_POSITION]: StreamStatPosition,
+ [PrefKey.STATS_QUICK_GLANCE_ENABLED]: boolean,
+ [PrefKey.STATS_SHOW_WHEN_PLAYING]: boolean,
+ [PrefKey.STATS_TEXT_SIZE]: string,
+ [PrefKey.STREAM_CODEC_PROFILE]: CodecProfile,
+ [PrefKey.STREAM_COMBINE_SOURCES]: boolean,
+ [PrefKey.STREAM_MAX_VIDEO_BITRATE]: number,
+ [PrefKey.STREAM_PREFERRED_LOCALE]: StreamPreferredLocale,
+ [PrefKey.STREAM_RESOLUTION]: StreamResolution,
+ [PrefKey.TOUCH_CONTROLLER_AUTO_OFF]: boolean,
+ [PrefKey.TOUCH_CONTROLLER_DEFAULT_OPACITY]: number,
+ [PrefKey.TOUCH_CONTROLLER_MODE]: TouchControllerMode,
+ [PrefKey.TOUCH_CONTROLLER_STYLE_CUSTOM]: TouchControllerStyleCustom,
+ [PrefKey.TOUCH_CONTROLLER_STYLE_STANDARD]: TouchControllerStyleStandard,
+ [PrefKey.UI_CONTROLLER_FRIENDLY]: boolean,
+ [PrefKey.UI_CONTROLLER_SHOW_STATUS]: boolean,
+ [PrefKey.UI_DISABLE_FEEDBACK_DIALOG]: boolean,
+ [PrefKey.UI_GAME_CARD_SHOW_WAIT_TIME]: boolean,
+ [PrefKey.UI_HIDE_SECTIONS]: UiSection[],
+ [PrefKey.UI_HIDE_SYSTEM_MENU_ICON]: boolean,
+ [PrefKey.UI_LAYOUT]: UiLayout,
+ [PrefKey.UI_REDUCE_ANIMATIONS]: boolean,
+ [PrefKey.UI_SCROLLBAR_HIDE]: boolean,
+ [PrefKey.UI_SIMPLIFY_STREAM_MENU]: boolean,
+ [PrefKey.UI_SKIP_SPLASH_VIDEO]: boolean,
+ [PrefKey.VERSION_CURRENT]: string,
+ [PrefKey.VERSION_LAST_CHECK]: number,
+ [PrefKey.VERSION_LATEST]: string,
+ [PrefKey.VIDEO_BRIGHTNESS]: number,
+ [PrefKey.VIDEO_CONTRAST]: number,
+ [PrefKey.VIDEO_MAX_FPS]: number,
+ [PrefKey.VIDEO_PLAYER_TYPE]: StreamPlayerType,
+ [PrefKey.VIDEO_POSITION]: VideoPosition,
+ [PrefKey.VIDEO_POWER_PREFERENCE]: VideoPowerPreference,
+ [PrefKey.VIDEO_PROCESSING]: StreamVideoProcessing,
+ [PrefKey.VIDEO_RATIO]: VideoRatio,
+ [PrefKey.VIDEO_SATURATION]: number,
+ [PrefKey.VIDEO_SHARPNESS]: number,
+}
diff --git a/src/enums/pref-values.ts b/src/enums/pref-values.ts
index 812b78f..bcb3a2d 100755
--- a/src/enums/pref-values.ts
+++ b/src/enums/pref-values.ts
@@ -84,6 +84,12 @@ export const enum StreamStat {
CLOCK = 'time',
};
+export const enum StreamStatPosition {
+ TOP_LEFT = 'top-left',
+ TOP_CENTER = 'top-center',
+ TOP_RIGHT = 'top-right',
+}
+
export const enum VideoRatio {
'16:9' = '16:9',
'18:9' = '18:9',
@@ -101,6 +107,12 @@ export const enum VideoPosition {
BOTTOM_HALF = 'bottom-half',
}
+export const enum VideoPowerPreference {
+ DEFAULT = 'default',
+ LOW_POWER = 'low-power',
+ HIGH_PERFORMANCE = 'high-performance',
+}
+
export const enum StreamPlayerType {
VIDEO = 'default',
WEBGL2 = 'webgl2',
diff --git a/src/enums/prompt-font.ts b/src/enums/prompt-font.ts
index a64f45b..fc3a367 100755
--- a/src/enums/prompt-font.ts
+++ b/src/enums/prompt-font.ts
@@ -18,15 +18,19 @@ export enum PrompFont {
LEFT = '≺',
RIGHT = '≼',
+ LS = '⇱',
L3 = '↺',
LS_UP = '↾',
LS_DOWN = '⇂',
LS_LEFT = '↼',
LS_RIGHT = '⇀',
+ RS = '⇲',
R3 = '↻',
RS_UP = '↿',
RS_DOWN = '⇃',
RS_LEFT = '↽',
RS_RIGHT = '⇁',
+
+ SHARE = '📸',
}
diff --git a/src/index.ts b/src/index.ts
index 8a4bf70..757b696 100755
--- a/src/index.ts
+++ b/src/index.ts
@@ -171,7 +171,7 @@ document.addEventListener('readystatechange', e => {
}
// Hide "Play with Friends" skeleton section
- if (getPref(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.FRIENDS) || getPref(PrefKey.BLOCK_FEATURES).includes(BlockFeature.FRIENDS)) {
+ if (getPref(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.FRIENDS) || getPref(PrefKey.BLOCK_FEATURES).includes(BlockFeature.FRIENDS)) {
const $parent = document.querySelector('div[class*=PlayWithFriendsSkeleton]')?.closest('div[class*=HomePage-module]');
$parent && ($parent.style.display = 'none');
}
@@ -357,8 +357,8 @@ isFullVersion() && window.addEventListener(BxEvent.CAPTURE_SCREENSHOT, e => {
function main() {
GhPagesUtils.fetchLatestCommit();
- if (getPref(PrefKey.NATIVE_MKB_MODE) !== NativeMkbMode.OFF) {
- const customList = getPref(PrefKey.NATIVE_MKB_FORCED_GAMES);
+ if (getPref(PrefKey.NATIVE_MKB_MODE) !== NativeMkbMode.OFF) {
+ const customList = getPref(PrefKey.NATIVE_MKB_FORCED_GAMES);
BX_FLAGS.ForceNativeMkbTitles.push(...customList);
}
@@ -405,12 +405,12 @@ function main() {
RemotePlayManager.detect();
}
- if (getPref(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.ALL) {
+ if (getPref(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.ALL) {
TouchController.setup();
}
// Start PointerProviderServer
- if (AppInterface && (getPref(PrefKey.MKB_ENABLED) || getPref(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON)) {
+ if (AppInterface && (getPref(PrefKey.MKB_ENABLED) || getPref(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON)) {
STATES.pointerServerPort = AppInterface.startPointerServer() || 9269;
BxLogger.info('startPointerServer', 'Port', STATES.pointerServerPort.toString());
}
diff --git a/src/modules/game-bar/game-bar.ts b/src/modules/game-bar/game-bar.ts
index 870dd6e..e8c339f 100755
--- a/src/modules/game-bar/game-bar.ts
+++ b/src/modules/game-bar/game-bar.ts
@@ -20,7 +20,7 @@ export class GameBar {
private static instance: GameBar | null | undefined;
public static getInstance(): typeof GameBar['instance'] {
if (typeof GameBar.instance === 'undefined') {
- if (getPref(PrefKey.GAME_BAR_POSITION) !== GameBarPosition.OFF) {
+ if (getPref(PrefKey.GAME_BAR_POSITION) !== GameBarPosition.OFF) {
GameBar.instance = new GameBar();
} else {
GameBar.instance = null;
@@ -46,7 +46,7 @@ export class GameBar {
let $container;
- const position = getPref(PrefKey.GAME_BAR_POSITION);
+ const position = getPref(PrefKey.GAME_BAR_POSITION);
const $gameBar = CE('div', { id: 'bx-game-bar', class: 'bx-gone', 'data-position': position },
$container = CE('div', { class: 'bx-game-bar-container bx-offscreen' }),
@@ -55,7 +55,7 @@ export class GameBar {
this.actions = [
new ScreenshotAction(),
- ...(STATES.userAgent.capabilities.touch && (getPref(PrefKey.TOUCH_CONTROLLER_MODE) !== TouchControllerMode.OFF) ? [new TouchControlAction()] : []),
+ ...(STATES.userAgent.capabilities.touch && (getPref(PrefKey.TOUCH_CONTROLLER_MODE) !== TouchControllerMode.OFF) ? [new TouchControlAction()] : []),
new SpeakerAction(),
new RendererAction(),
new MicrophoneAction(),
diff --git a/src/modules/loading-screen.ts b/src/modules/loading-screen.ts
index 4ba5548..ed836bb 100755
--- a/src/modules/loading-screen.ts
+++ b/src/modules/loading-screen.ts
@@ -37,7 +37,7 @@ export class LoadingScreen {
LoadingScreen.setBackground(titleInfo.product.heroImageUrl || titleInfo.product.titledHeroImageUrl || titleInfo.product.tileImageUrl);
- if (getPref(PrefKey.LOADING_SCREEN_ROCKET) === LoadingScreenRocket.HIDE) {
+ if (getPref(PrefKey.LOADING_SCREEN_ROCKET) === LoadingScreenRocket.HIDE) {
LoadingScreen.hideRocket();
}
}
@@ -89,7 +89,7 @@ export class LoadingScreen {
static setupWaitTime(waitTime: number) {
// Hide rocket when queing
- if (getPref(PrefKey.LOADING_SCREEN_ROCKET) === LoadingScreenRocket.HIDE_QUEUE) {
+ if (getPref(PrefKey.LOADING_SCREEN_ROCKET) === LoadingScreenRocket.HIDE_QUEUE) {
LoadingScreen.hideRocket();
}
diff --git a/src/modules/mkb/mkb-handler.ts b/src/modules/mkb/mkb-handler.ts
index 0a75528..05ae42f 100755
--- a/src/modules/mkb/mkb-handler.ts
+++ b/src/modules/mkb/mkb-handler.ts
@@ -580,7 +580,7 @@ export class EmulatedMkbHandler extends MkbHandler {
updateGamepadSlots() {
// Set gamepad slot
- this.VIRTUAL_GAMEPAD.index = getPref(PrefKey.MKB_P1_SLOT) - 1;
+ this.VIRTUAL_GAMEPAD.index = getPref(PrefKey.MKB_P1_SLOT) - 1;
}
start() {
diff --git a/src/modules/mkb/mkb-popup.ts b/src/modules/mkb/mkb-popup.ts
index f0bb0f6..a845473 100755
--- a/src/modules/mkb/mkb-popup.ts
+++ b/src/modules/mkb/mkb-popup.ts
@@ -7,6 +7,7 @@ import { NativeMkbHandler } from "./native-mkb-handler";
import { StreamSettings } from "@/utils/stream-settings";
import { KeyHelper } from "./key-helper";
import { BxEventBus } from "@/utils/bx-event-bus";
+import { BxIcon } from "@/utils/bx-icon";
type MkbPopupType = 'virtual' | 'native';
@@ -90,6 +91,7 @@ export class MkbPopup {
createButton({
label: t('manage'),
+ icon: BxIcon.MANAGE,
style: ButtonStyle.FOCUSABLE,
onClick: () => {
const dialog = SettingsDialog.getInstance();
diff --git a/src/modules/mkb/native-mkb-handler.ts b/src/modules/mkb/native-mkb-handler.ts
index 4c2e920..eaec396 100755
--- a/src/modules/mkb/native-mkb-handler.ts
+++ b/src/modules/mkb/native-mkb-handler.ts
@@ -43,7 +43,7 @@ export class NativeMkbHandler extends MkbHandler {
private readonly LOG_TAG = 'NativeMkbHandler';
static isAllowed = () => {
- return STATES.browser.capabilities.emulatedNativeMkb && getPref(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON;
+ return STATES.browser.capabilities.emulatedNativeMkb && getPref(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON;
}
private pointerClient: PointerClient | undefined;
diff --git a/src/modules/patcher/patcher-utils.ts b/src/modules/patcher/patcher-utils.ts
index 362f7e3..ab94621 100644
--- a/src/modules/patcher/patcher-utils.ts
+++ b/src/modules/patcher/patcher-utils.ts
@@ -1,22 +1,30 @@
import type { PatchArray, PatchName, PatchPage } from "./patcher";
export class PatcherUtils {
- static indexOf(txt: string, searchString: string, startIndex: number, maxRange: number): number {
+ static indexOf(txt: string, searchString: string, startIndex: number, maxRange=0, after=false): number {
+ if (startIndex < 0) {
+ return -1;
+ }
+
const index = txt.indexOf(searchString, startIndex);
if (index < 0 || (maxRange && index - startIndex > maxRange)) {
return -1;
}
- return index;
+ return after ? index + searchString.length : index;
}
- static lastIndexOf(txt: string, searchString: string, startIndex: number, maxRange: number): number {
+ static lastIndexOf(txt: string, searchString: string, startIndex: number, maxRange=0, after=false): number {
+ if (startIndex < 0) {
+ return -1;
+ }
+
const index = txt.lastIndexOf(searchString, startIndex);
if (index < 0 || (maxRange && startIndex - index > maxRange)) {
return -1;
}
- return index;
+ return after ? index + searchString.length : index;
}
static insertAt(txt: string, index: number, insertString: string): string {
diff --git a/src/modules/patcher/patcher.ts b/src/modules/patcher/patcher.ts
index 09d25f9..8e4ac04 100755
--- a/src/modules/patcher/patcher.ts
+++ b/src/modules/patcher/patcher.ts
@@ -4,10 +4,10 @@ import { BxLogger } from "@utils/bx-logger";
import { blockSomeNotifications, hashCode, renderString } from "@utils/utils";
import { BxEvent } from "@/utils/bx-event";
-import codeControllerShortcuts from "./patches/controller-shortcuts.js" with { type: "text" };
+import codeControllerCustomization from "./patches/controller-customization.js" with { type: "text" };
+import codePollGamepad from "./patches/poll-gamepad.js" with { type: "text" };
import codeExposeStreamSession from "./patches/expose-stream-session.js" with { type: "text" };
import codeLocalCoOpEnable from "./patches/local-co-op-enable.js" with { type: "text" };
-import codeRemotePlayEnable from "./patches/remote-play-enable.js" with { type: "text" };
import codeRemotePlayKeepAlive from "./patches/remote-play-keep-alive.js" with { type: "text" };
import codeVibrationAdjust from "./patches/vibration-adjust.js" with { type: "text" };
import { PrefKey, StorageKey } from "@/enums/pref-keys.js";
@@ -88,7 +88,7 @@ const PATCHES = {
return false;
}
- const layout = getPref(PrefKey.UI_LAYOUT) === UiLayout.TV ? UiLayout.TV : UiLayout.DEFAULT;
+ const layout = getPref(PrefKey.UI_LAYOUT) === UiLayout.TV ? UiLayout.TV : UiLayout.DEFAULT;
return str.replace(text, `?"${layout}":"${layout}"`);
},
@@ -120,7 +120,9 @@ const PATCHES = {
return false;
}
- return str.replace(text, codeRemotePlayEnable);
+ const newCode = `connectMode: window.BX_REMOTE_PLAY_CONFIG ? "xhome-connect" : "cloud-connect",
+remotePlayServerId: (window.BX_REMOTE_PLAY_CONFIG && window.BX_REMOTE_PLAY_CONFIG.serverId) || '',`;
+ return str.replace(text, newCode);
},
// Remote Play: Disable achievement toast
@@ -191,17 +193,34 @@ const PATCHES = {
codeBlock = codeBlock.replace('this.inputPollingDurationStats.addValue', '');
}
- // Map the Share button on Xbox Series controller with the capturing screenshot feature
- const match = codeBlock.match(/this\.gamepadTimestamps\.set\((\w+)\.index/);
- if (match) {
- const gamepadVar = match[1];
- const newCode = renderString(codeControllerShortcuts, {
- gamepadVar,
- });
-
- codeBlock = codeBlock.replace('this.gamepadTimestamps.set', newCode + 'this.gamepadTimestamps.set');
+ // Controller shortcuts
+ let match = codeBlock.match(/this\.gamepadTimestamps\.set\(([A-Za-z0-9_$]+)\.index/);
+ if (!match) {
+ return false;
}
+ let newCode = renderString(codePollGamepad, {
+ gamepadVar: match[1],
+ });
+ codeBlock = codeBlock.replace('this.gamepadTimestamps.set', newCode + 'this.gamepadTimestamps.set');
+
+ // Controller customization
+ match = codeBlock.match(/let ([A-Za-z0-9_$]+)=this\.gamepadMappings\.find/);
+ if (!match) {
+ return false;
+ }
+
+ const xCloudGamepadVar = match[1];
+ const inputFeedbackManager = PatcherUtils.indexOf(codeBlock, 'this.inputFeedbackManager.onGamepadConnected(', 0, 10000);
+ const backetIndex = PatcherUtils.indexOf(codeBlock, '}', inputFeedbackManager, 100);
+ if (backetIndex < 0) {
+ return false;
+ }
+
+ let customizationCode = ';'; // End previous code line
+ customizationCode += renderString(codeControllerCustomization, { xCloudGamepadVar });
+ codeBlock = PatcherUtils.insertAt(codeBlock, backetIndex, customizationCode);
+
str = str.substring(0, index) + codeBlock + str.substring(setTimeoutIndex);
return str;
},
@@ -357,7 +376,7 @@ if (window.BX_EXPOSED.stopTakRendering) {
}
let autoOffCode = '';
- if (getPref(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.OFF) {
+ if (getPref(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.OFF) {
autoOffCode = 'return;';
} else if (getPref(PrefKey.TOUCH_CONTROLLER_AUTO_OFF)) {
autoOffCode = `
@@ -414,7 +433,7 @@ e.guideUI = null;
`;
// Remove the TAK Edit button when the touch controller is disabled
- if (getPref(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.OFF) {
+ if (getPref(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.OFF) {
newCode += 'e.canShowTakHUD = false;';
}
@@ -534,7 +553,7 @@ BxLogger.info('patchRemotePlayMkb', ${configsVar});
return false;
}
- const opacity = (getPref(PrefKey.TOUCH_CONTROLLER_DEFAULT_OPACITY) / 100).toFixed(1);
+ const opacity = (getPref(PrefKey.TOUCH_CONTROLLER_DEFAULT_OPACITY) / 100).toFixed(1);
const newCode = `opacityMultiplier: ${opacity}`;
str = str.replace(text, newCode);
return str;
@@ -771,7 +790,7 @@ true` + text;
return false;
}
- const PREF_HIDE_SECTIONS = getPref(PrefKey.UI_HIDE_SECTIONS);
+ const PREF_HIDE_SECTIONS = getPref(PrefKey.UI_HIDE_SECTIONS);
const siglIds: GamePassCloudGallery[] = [];
const sections: PartialRecord = {
@@ -962,7 +981,7 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) {
// Find index after {
index = str.indexOf('{', index) + 1;
- const blockFeatures = getPref(PrefKey.BLOCK_FEATURES);
+ const blockFeatures = getPref(PrefKey.BLOCK_FEATURES);
const filters = [];
if (blockFeatures.includes(BlockFeature.NOTIFICATIONS_INVITES)) {
filters.push('GameInvite', 'PartyInvite');
@@ -987,7 +1006,7 @@ ${subsVar} = subs;
};
let PATCH_ORDERS = PatcherUtils.filterPatches([
- ...(AppInterface && getPref(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON ? [
+ ...(AppInterface && getPref(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON ? [
'enableNativeMkb',
'exposeInputSink',
'disableAbsoluteMouse',
@@ -1019,7 +1038,7 @@ let PATCH_ORDERS = PatcherUtils.filterPatches([
'overrideStorageGetSettings',
getPref(PrefKey.UI_GAME_CARD_SHOW_WAIT_TIME) && 'patchSetCurrentFocus',
- getPref(PrefKey.UI_LAYOUT) !== UiLayout.DEFAULT && 'websiteLayout',
+ getPref(PrefKey.UI_LAYOUT) !== UiLayout.DEFAULT && 'websiteLayout',
getPref(PrefKey.GAME_FORTNITE_FORCE_CONSOLE) && 'forceFortniteConsole',
...(STATES.userAgent.capabilities.touch ? [
@@ -1055,7 +1074,7 @@ let PATCH_ORDERS = PatcherUtils.filterPatches([
] : []),
]);
-const hideSections = getPref(PrefKey.UI_HIDE_SECTIONS);
+const hideSections = getPref(PrefKey.UI_HIDE_SECTIONS);
let HOME_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
hideSections.includes(UiSection.NEWS) && 'ignoreNewsSection',
hideSections.includes(UiSection.FRIENDS) && 'ignorePlayWithFriendsSection',
@@ -1086,11 +1105,11 @@ let STREAM_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
getPref(PrefKey.UI_DISABLE_FEEDBACK_DIALOG) && 'skipFeedbackDialog',
...(STATES.userAgent.capabilities.touch ? [
- getPref(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.ALL && 'patchShowSensorControls',
- getPref(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.ALL && 'exposeTouchLayoutManager',
- (getPref(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.OFF || getPref(PrefKey.TOUCH_CONTROLLER_AUTO_OFF)) && 'disableTakRenderer',
- getPref(PrefKey.TOUCH_CONTROLLER_DEFAULT_OPACITY) !== 100 && 'patchTouchControlDefaultOpacity',
- (getPref(PrefKey.TOUCH_CONTROLLER_MODE) !== TouchControllerMode.OFF && (getPref(PrefKey.MKB_ENABLED) || getPref(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON)) && 'patchBabylonRendererClass',
+ getPref(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.ALL && 'patchShowSensorControls',
+ getPref(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.ALL && 'exposeTouchLayoutManager',
+ (getPref(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.OFF || getPref(PrefKey.TOUCH_CONTROLLER_AUTO_OFF)) && 'disableTakRenderer',
+ getPref(PrefKey.TOUCH_CONTROLLER_DEFAULT_OPACITY) !== 100 && 'patchTouchControlDefaultOpacity',
+ (getPref(PrefKey.TOUCH_CONTROLLER_MODE) !== TouchControllerMode.OFF && (getPref(PrefKey.MKB_ENABLED) || getPref(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON)) && 'patchBabylonRendererClass',
] : []),
BX_FLAGS.EnableXcloudLogging && 'enableConsoleLogging',
@@ -1105,7 +1124,7 @@ let STREAM_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
] : []),
// Native MKB
- ...(AppInterface && getPref(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON ? [
+ ...(AppInterface && getPref(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON ? [
'patchMouseAndKeyboardEnabled',
'disableNativeRequestPointerLock',
] : []),
diff --git a/src/modules/patcher/patches/controller-shortcuts.js b/src/modules/patcher/patches/controller-shortcuts.js
deleted file mode 100755
index d3a1376..0000000
--- a/src/modules/patcher/patches/controller-shortcuts.js
+++ /dev/null
@@ -1,99 +0,0 @@
-if (window.BX_EXPOSED.disableGamepadPolling) {
- this.inputConfiguration.useIntervalWorkerThreadForInput && this.intervalWorker ? this.intervalWorker.scheduleTimer(50) : this.pollGamepadssetTimeoutTimerID = setTimeout(this.pollGamepads, 50);
- return;
-}
-
-const currentGamepad = ${gamepadVar};
-
-// Share button on XS controller
-if (currentGamepad.buttons[17] && currentGamepad.buttons[17].pressed) {
- window.dispatchEvent(new Event(BxEvent.CAPTURE_SCREENSHOT));
-}
-
-const btnHome = currentGamepad.buttons[16];
-if (btnHome) {
- if (!this.bxHomeStates) {
- this.bxHomeStates = {};
- }
-
- let intervalMs = 0;
- let hijack = false;
-
- if (btnHome.pressed) {
- hijack = true;
- intervalMs = 16;
- this.gamepadIsIdle.set(currentGamepad.index, false);
-
- if (this.bxHomeStates[currentGamepad.index]) {
- const lastTimestamp = this.bxHomeStates[currentGamepad.index].timestamp;
-
- if (currentGamepad.timestamp !== lastTimestamp) {
- this.bxHomeStates[currentGamepad.index].timestamp = currentGamepad.timestamp;
-
- const handled = window.BX_EXPOSED.handleControllerShortcut(currentGamepad);
- if (handled) {
- this.bxHomeStates[currentGamepad.index].shortcutPressed += 1;
- }
- }
- } else {
- // First time pressing > save current timestamp
- window.BX_EXPOSED.resetControllerShortcut(currentGamepad.index);
- this.bxHomeStates[currentGamepad.index] = {
- shortcutPressed: 0,
- timestamp: currentGamepad.timestamp,
- };
- }
- } else if (this.bxHomeStates[currentGamepad.index]) {
- hijack = true;
- const info = structuredClone(this.bxHomeStates[currentGamepad.index]);
-
- // Home button released
- this.bxHomeStates[currentGamepad.index] = null;
-
- if (info.shortcutPressed === 0) {
- const fakeGamepadMappings = [{
- GamepadIndex: currentGamepad.index,
- A: 0,
- B: 0,
- X: 0,
- Y: 0,
- LeftShoulder: 0,
- RightShoulder: 0,
- LeftTrigger: 0,
- RightTrigger: 0,
- View: 0,
- Menu: 0,
- LeftThumb: 0,
- RightThumb: 0,
- DPadUp: 0,
- DPadDown: 0,
- DPadLeft: 0,
- DPadRight: 0,
- Nexus: 1,
- LeftThumbXAxis: 0,
- LeftThumbYAxis: 0,
- RightThumbXAxis: 0,
- RightThumbYAxis: 0,
- PhysicalPhysicality: 0,
- VirtualPhysicality: 0,
- Dirty: true,
- Virtual: false,
- }];
-
- const isLongPress = (currentGamepad.timestamp - info.timestamp) >= 500;
- intervalMs = isLongPress ? 500 : 100;
-
- this.inputSink.onGamepadInput(performance.now() - intervalMs, fakeGamepadMappings);
- } else {
- intervalMs = window.BX_STREAM_SETTINGS.controllerPollingRate;
- }
- }
-
- if (hijack && intervalMs) {
- // Listen to next button press
- this.inputConfiguration.useIntervalWorkerThreadForInput && this.intervalWorker ? this.intervalWorker.scheduleTimer(intervalMs) : this.pollGamepadssetTimeoutTimerID = setTimeout(this.pollGamepads, intervalMs);
-
- // Hijack this button
- return;
- }
-}
diff --git a/src/modules/patcher/patches/remote-play-enable.js b/src/modules/patcher/patches/remote-play-enable.js
deleted file mode 100755
index 56f88dc..0000000
--- a/src/modules/patcher/patches/remote-play-enable.js
+++ /dev/null
@@ -1,2 +0,0 @@
-connectMode: window.BX_REMOTE_PLAY_CONFIG ? "xhome-connect" : "cloud-connect",
-remotePlayServerId: (window.BX_REMOTE_PLAY_CONFIG && window.BX_REMOTE_PLAY_CONFIG.serverId) || '',
diff --git a/src/modules/patcher/patches/remote-play-keep-alive.js b/src/modules/patcher/patches/remote-play-keep-alive.js
deleted file mode 100755
index 5ccafac..0000000
--- a/src/modules/patcher/patches/remote-play-keep-alive.js
+++ /dev/null
@@ -1,7 +0,0 @@
-const msg = JSON.parse(e);
-if (msg.reason === 'WarningForBeingIdle' && !window.location.pathname.includes('/launch/')) {
- try {
- this.sendKeepAlive();
- return;
- } catch (ex) { console.log(ex); }
-}
diff --git a/src/modules/patcher/patches/src/controller-customization.ts b/src/modules/patcher/patches/src/controller-customization.ts
new file mode 100644
index 0000000..a536507
--- /dev/null
+++ b/src/modules/patcher/patches/src/controller-customization.ts
@@ -0,0 +1,167 @@
+import { BxEvent as BxEventNamespace } from "@/utils/bx-event";
+
+// "currentGamepad" variable in poll-gamepad.js
+declare const currentGamepad: Gamepad;
+declare const $xCloudGamepadVar$: XcloudGamepad;
+declare const BxEvent: typeof BxEventNamespace;
+
+// Share button on XS controller
+const shareButtonPressed = currentGamepad.buttons[17]?.pressed;
+let shareButtonHandled = false;
+
+const xCloudGamepad: XcloudGamepad = $xCloudGamepadVar$;
+if (currentGamepad.id in window.BX_STREAM_SETTINGS.controllers) {
+ const controller = window.BX_STREAM_SETTINGS.controllers[currentGamepad.id];
+ if (controller?.customization) {
+ const MIN_RANGE = 0.1;
+
+ const { mapping, ranges } = controller.customization;
+ const pressedButtons: Partial> = {};
+ const releasedButtons: Partial> = {};
+ let isModified = false;
+
+ // Limit left trigger range
+ if (ranges.LeftTrigger) {
+ const [from, to] = ranges.LeftTrigger;
+ xCloudGamepad.LeftTrigger = xCloudGamepad.LeftTrigger > to ? 1 : xCloudGamepad.LeftTrigger;
+ xCloudGamepad.LeftTrigger = xCloudGamepad.LeftTrigger < from ? 0 : xCloudGamepad.LeftTrigger;
+ }
+
+ // Limit right trigger range
+ if (ranges.RightTrigger) {
+ const [from, to] = ranges.RightTrigger;
+ xCloudGamepad.RightTrigger = xCloudGamepad.RightTrigger > to ? 1 : xCloudGamepad.RightTrigger;
+ xCloudGamepad.RightTrigger = xCloudGamepad.RightTrigger < from ? 0 : xCloudGamepad.RightTrigger;
+ }
+
+ // Limit left stick deadzone
+ if (ranges.LeftThumb) {
+ const [from, to] = ranges.LeftThumb;
+
+ const xAxis = xCloudGamepad.LeftThumbXAxis;
+ const yAxis = xCloudGamepad.LeftThumbYAxis;
+
+ const range = Math.abs(Math.sqrt(xAxis * xAxis + yAxis * yAxis));
+ let newRange = range > to ? 1 : range;
+ newRange = newRange < from ? 0 : newRange;
+
+ if (newRange !== range) {
+ xCloudGamepad.LeftThumbXAxis = xAxis * (newRange / range);
+ xCloudGamepad.LeftThumbYAxis = yAxis * (newRange / range);
+ }
+ }
+
+ // Limit right stick deadzone
+ if (ranges.RightThumb) {
+ const [from, to] = ranges.RightThumb;
+
+ const xAxis = xCloudGamepad.RightThumbXAxis;
+ const yAxis = xCloudGamepad.RightThumbYAxis;
+
+ const range = Math.abs(Math.sqrt(xAxis * xAxis + yAxis * yAxis));
+ let newRange = range > to ? 1 : range;
+ newRange = newRange < from ? 0 : newRange;
+
+ if (newRange !== range) {
+ xCloudGamepad.RightThumbXAxis = xAxis * (newRange / range);
+ xCloudGamepad.RightThumbYAxis = yAxis * (newRange / range);
+ }
+ }
+
+ // Handle the Share button
+ if (shareButtonPressed && 'Share' in mapping) {
+ const targetButton = mapping['Share'];
+ if (typeof targetButton === 'string') {
+ pressedButtons[targetButton] = 1;
+ }
+
+ // Don't send capturing request
+ shareButtonHandled = true;
+ delete mapping['Share'];
+ }
+
+ // Handle other buttons
+ let key: keyof typeof mapping;
+ for (key in mapping) {
+ const mappedKey = mapping[key];
+
+ if (key === 'LeftStickAxes' || key === 'RightStickAxes') {
+ let sourceX: keyof XcloudGamepad;
+ let sourceY: keyof XcloudGamepad;
+ let targetX: keyof XcloudGamepad;
+ let targetY: keyof XcloudGamepad;
+
+ if (key === 'LeftStickAxes') {
+ sourceX = 'LeftThumbXAxis';
+ sourceY = 'LeftThumbYAxis';
+ targetX = 'RightThumbXAxis';
+ targetY = 'RightThumbYAxis';
+ } else {
+ sourceX = 'RightThumbXAxis';
+ sourceY = 'RightThumbYAxis';
+ targetX = 'LeftThumbXAxis';
+ targetY = 'LeftThumbYAxis';
+ }
+
+ if (typeof mappedKey === 'string') {
+ // Calculate moved range
+ const rangeX = xCloudGamepad[sourceX];
+ const rangeY = xCloudGamepad[sourceY];
+ const movedRange = Math.abs(Math.sqrt(rangeX * rangeX + rangeY * rangeY));
+ const moved = movedRange >= MIN_RANGE;
+
+ // Swap sticks
+ if (moved) {
+ pressedButtons[targetX] = rangeX;
+ pressedButtons[targetY] = rangeY;
+ }
+ }
+
+ // Unbind original stick
+ releasedButtons[sourceX] = 0;
+ releasedButtons[sourceY] = 0;
+
+ isModified = true;
+ } else if (typeof mappedKey === 'string') {
+ let pressed = false;
+ let value = 0;
+
+ if (key === 'LeftTrigger' || key === 'RightTrigger') {
+ // Only set pressed state when pressing pass max range
+ const currentRange = xCloudGamepad[key];
+ if (mappedKey === 'LeftTrigger' || mappedKey === 'RightTrigger') {
+ pressed = currentRange >= MIN_RANGE;
+ value = currentRange;
+ } else {
+ pressed = true;
+ value = currentRange >= 0.9 ? 1 : 0;
+ }
+ } else if (xCloudGamepad[key]) {
+ pressed = true;
+ value = xCloudGamepad[key] as number;
+ }
+
+ if (pressed) {
+ // Only copy button value when it's being pressed
+ pressedButtons[mappedKey] = value;
+ // Unbind original button
+ releasedButtons[key] = 0;
+
+ isModified = true;
+ }
+ } else if (mappedKey === false) {
+ // Disable key
+ pressedButtons[key] = 0;
+
+ isModified = true;
+ }
+ }
+
+ isModified && Object.assign(xCloudGamepad, releasedButtons, pressedButtons);
+ }
+}
+
+// Capture screenshot when the Share button is pressed
+if (shareButtonPressed && !shareButtonHandled) {
+ window.dispatchEvent(new Event(BxEvent.CAPTURE_SCREENSHOT));
+}
diff --git a/src/modules/patcher/patches/expose-stream-session.js b/src/modules/patcher/patches/src/expose-stream-session.ts
old mode 100755
new mode 100644
similarity index 66%
rename from src/modules/patcher/patches/expose-stream-session.js
rename to src/modules/patcher/patches/src/expose-stream-session.ts
index ea595eb..2c4bd86
--- a/src/modules/patcher/patches/expose-stream-session.js
+++ b/src/modules/patcher/patches/src/expose-stream-session.ts
@@ -1,7 +1,15 @@
-window.BX_EXPOSED.streamSession = this;
+import type { MicrophoneState } from "@/modules/shortcuts/microphone-shortcut";
+import { BxEvent as BxEventNamespace } from "@/utils/bx-event";
-const orgSetMicrophoneState = this.setMicrophoneState.bind(this);
-this.setMicrophoneState = state => {
+declare const $this$: any;
+declare const BxEvent: typeof BxEventNamespace;
+
+const self = $this$;
+window.BX_EXPOSED.streamSession = self;
+
+// Patch setMicrophoneState()
+const orgSetMicrophoneState = self.setMicrophoneState.bind(self);
+self.setMicrophoneState = (state: MicrophoneState) => {
orgSetMicrophoneState(state);
window.BxEventBus.Stream.emit('microphone.state.changed', { state });
};
@@ -9,7 +17,7 @@ this.setMicrophoneState = state => {
window.dispatchEvent(new Event(BxEvent.STREAM_SESSION_READY));
// Patch updateDimensions() to make native touch work correctly with WebGL2
-let updateDimensionsStr = this.updateDimensions.toString();
+let updateDimensionsStr = self.updateDimensions.toString();
if (updateDimensionsStr.startsWith('function ')) {
updateDimensionsStr = updateDimensionsStr.substring(9);
@@ -19,7 +27,6 @@ if (updateDimensionsStr.startsWith('function ')) {
const renderTargetVar = updateDimensionsStr.match(/if\((\w+)\){/)[1];
updateDimensionsStr = updateDimensionsStr.replaceAll(renderTargetVar + '.scroll', 'scroll');
-
updateDimensionsStr = updateDimensionsStr.replace(`if(${renderTargetVar}){`, `
if (${renderTargetVar}) {
const scrollWidth = ${renderTargetVar}.dataset.width ? parseInt(${renderTargetVar}.dataset.width) : ${renderTargetVar}.scrollWidth;
diff --git a/src/modules/patcher/patches/local-co-op-enable.js b/src/modules/patcher/patches/src/local-co-op-enable.ts
old mode 100755
new mode 100644
similarity index 58%
rename from src/modules/patcher/patches/local-co-op-enable.js
rename to src/modules/patcher/patches/src/local-co-op-enable.ts
index f3ed93c..106c072
--- a/src/modules/patcher/patches/local-co-op-enable.js
+++ b/src/modules/patcher/patches/src/local-co-op-enable.ts
@@ -1,9 +1,14 @@
+import { BxLogger as OrgBxLogger } from "@/utils/bx-logger";
+
+declare const BxLogger: typeof OrgBxLogger;
+declare const $this$: any;
+
// Save the original onGamepadChanged() and onGamepadInput()
-this.orgOnGamepadChanged = this.onGamepadChanged;
-this.orgOnGamepadInput = this.onGamepadInput;
+$this$.orgOnGamepadChanged = $this$.onGamepadChanged;
+$this$.orgOnGamepadInput = $this$.onGamepadInput;
let match;
-let onGamepadChangedStr = this.onGamepadChanged.toString();
+let onGamepadChangedStr = $this$.onGamepadChanged.toString();
// Fix problem with Safari
if (onGamepadChangedStr.startsWith('function ')) {
@@ -11,9 +16,9 @@ if (onGamepadChangedStr.startsWith('function ')) {
}
onGamepadChangedStr = onGamepadChangedStr.replaceAll('0', 'arguments[1]');
-eval(`this.patchedOnGamepadChanged = function ${onGamepadChangedStr}`);
+eval(`$this$.patchedOnGamepadChanged = function ${onGamepadChangedStr}`);
-let onGamepadInputStr = this.onGamepadInput.toString();
+let onGamepadInputStr = $this$.onGamepadInput.toString();
// Fix problem with Safari
if (onGamepadInputStr.startsWith('function ')) {
onGamepadInputStr = onGamepadInputStr.substring(9);
@@ -22,19 +27,19 @@ if (onGamepadInputStr.startsWith('function ')) {
match = onGamepadInputStr.match(/(\w+\.GamepadIndex)/);
if (match) {
const gamepadIndexVar = match[0];
- onGamepadInputStr = onGamepadInputStr.replace('this.gamepadStates.get(', `this.gamepadStates.get(${gamepadIndexVar},`);
- eval(`this.patchedOnGamepadInput = function ${onGamepadInputStr}`);
+ onGamepadInputStr = onGamepadInputStr.replace('$this$.gamepadStates.get(', `$this$.gamepadStates.get(${gamepadIndexVar},`);
+ eval(`$this$.patchedOnGamepadInput = function ${onGamepadInputStr}`);
BxLogger.info('supportLocalCoOp', '✅ Successfully patched local co-op support');
} else {
BxLogger.error('supportLocalCoOp', '❌ Unable to patch local co-op support');
}
// Add method to switch between patched and original methods
-this.toggleLocalCoOp = enable => {
+$this$.toggleLocalCoOp = (enable: boolean) => {
BxLogger.info('toggleLocalCoOp', enable ? 'Enabled' : 'Disabled');
- this.onGamepadChanged = enable ? this.patchedOnGamepadChanged : this.orgOnGamepadChanged;
- this.onGamepadInput = enable ? this.patchedOnGamepadInput : this.orgOnGamepadInput;
+ $this$.onGamepadChanged = enable ? $this$.patchedOnGamepadChanged : $this$.orgOnGamepadChanged;
+ $this$.onGamepadInput = enable ? $this$.patchedOnGamepadInput : $this$.orgOnGamepadInput;
// Reconnect all gamepads
const gamepads = window.navigator.getGamepads();
@@ -54,4 +59,4 @@ this.toggleLocalCoOp = enable => {
};
// Expose this method
-window.BX_EXPOSED.toggleLocalCoOp = this.toggleLocalCoOp.bind(this);
+window.BX_EXPOSED.toggleLocalCoOp = $this$.toggleLocalCoOp.bind(this);
diff --git a/src/modules/patcher/patches/src/poll-gamepad.ts b/src/modules/patcher/patches/src/poll-gamepad.ts
new file mode 100644
index 0000000..53cca1c
--- /dev/null
+++ b/src/modules/patcher/patches/src/poll-gamepad.ts
@@ -0,0 +1,98 @@
+type GamepadManager = {
+ pollGamepadssetTimeoutTimerID: number;
+ intervalWorker: any;
+ pollGamepads(pollGamepads: any, arg1: number): any;
+ gamepadIsIdle: any;
+ inputSink: any;
+ inputConfiguration: any;
+
+ bxHomeStates: any;
+};
+
+declare const $gamepadVar$: Gamepad;
+declare const $this$: GamepadManager;
+
+const self = $this$;
+if (window.BX_EXPOSED.disableGamepadPolling) {
+ self.inputConfiguration.useIntervalWorkerThreadForInput && self.intervalWorker ? self.intervalWorker.scheduleTimer(50) : self.pollGamepadssetTimeoutTimerID = window.setTimeout(self.pollGamepads, 50);
+ // @ts-ignore
+ return;
+}
+
+const currentGamepad = $gamepadVar$;
+
+const btnHome = currentGamepad.buttons[16];
+// Controller shortcuts
+if (btnHome) {
+ if (!self.bxHomeStates) {
+ self.bxHomeStates = {};
+ }
+
+ let intervalMs = 0;
+ let hijack = false;
+
+ if (btnHome.pressed) {
+ hijack = true;
+ intervalMs = 16;
+ self.gamepadIsIdle.set(currentGamepad.index, false);
+
+ if (self.bxHomeStates[currentGamepad.index]) {
+ const lastTimestamp = self.bxHomeStates[currentGamepad.index].timestamp;
+
+ if (currentGamepad.timestamp !== lastTimestamp) {
+ self.bxHomeStates[currentGamepad.index].timestamp = currentGamepad.timestamp;
+
+ const handled = window.BX_EXPOSED.handleControllerShortcut(currentGamepad);
+ if (handled) {
+ self.bxHomeStates[currentGamepad.index].shortcutPressed += 1;
+ }
+ }
+ } else {
+ // First time pressing > save current timestamp
+ window.BX_EXPOSED.resetControllerShortcut(currentGamepad.index);
+ self.bxHomeStates[currentGamepad.index] = {
+ shortcutPressed: 0,
+ timestamp: currentGamepad.timestamp,
+ };
+ }
+ } else if (self.bxHomeStates[currentGamepad.index]) {
+ hijack = true;
+ const info = structuredClone(self.bxHomeStates[currentGamepad.index]);
+
+ // Home button released
+ self.bxHomeStates[currentGamepad.index] = null;
+
+ if (info.shortcutPressed === 0) {
+ const fakeGamepadMappings: XcloudGamepad[] = [{
+ GamepadIndex: currentGamepad.index,
+ A: 0, B: 0, X: 0, Y: 0,
+ LeftShoulder: 0, RightShoulder: 0,
+ LeftTrigger: 0, RightTrigger: 0,
+ View: 0, Menu: 0,
+ LeftThumb: 0, RightThumb: 0,
+ DPadUp: 0, DPadDown: 0, DPadLeft: 0, DPadRight: 0,
+ Nexus: 1,
+ LeftThumbXAxis: 0, LeftThumbYAxis: 0,
+ RightThumbXAxis: 0, RightThumbYAxis: 0,
+ PhysicalPhysicality: 0, VirtualPhysicality: 0,
+ Dirty: true, Virtual: false,
+ }];
+
+ const isLongPress = (currentGamepad.timestamp - info.timestamp) >= 500;
+ intervalMs = isLongPress ? 500 : 100;
+
+ self.inputSink.onGamepadInput(performance.now() - intervalMs, fakeGamepadMappings);
+ } else {
+ intervalMs = window.BX_STREAM_SETTINGS.controllerPollingRate;
+ }
+ }
+
+ if (hijack && intervalMs) {
+ // Listen to next button press
+ self.inputConfiguration.useIntervalWorkerThreadForInput && self.intervalWorker ? self.intervalWorker.scheduleTimer(intervalMs) : self.pollGamepadssetTimeoutTimerID = setTimeout(self.pollGamepads, intervalMs);
+
+ // Hijack this button
+ // @ts-ignore
+ return;
+ }
+}
diff --git a/src/modules/patcher/patches/src/remote-play-keep-alive.ts b/src/modules/patcher/patches/src/remote-play-keep-alive.ts
new file mode 100644
index 0000000..54f797d
--- /dev/null
+++ b/src/modules/patcher/patches/src/remote-play-keep-alive.ts
@@ -0,0 +1,11 @@
+declare const $this$: any;
+declare const e: string;
+
+try {
+ const msg = JSON.parse(e);
+ if (msg.reason === 'WarningForBeingIdle' && !window.location.pathname.includes('/launch/')) {
+ $this$.sendKeepAlive();
+ // @ts-ignore
+ return;
+ }
+} catch (ex) { console.log(ex); }
diff --git a/src/modules/patcher/patches/src/vibration-adjust.ts b/src/modules/patcher/patches/src/vibration-adjust.ts
new file mode 100644
index 0000000..dabe03b
--- /dev/null
+++ b/src/modules/patcher/patches/src/vibration-adjust.ts
@@ -0,0 +1,26 @@
+declare const e: {
+ gamepad: Gamepad;
+ repeat: number;
+ leftMotorPercent: number;
+ rightMotorPercent: number;
+ leftTriggerMotorPercent: number;
+ rightTriggerMotorPercent: number;
+};
+
+if (e?.gamepad?.connected) {
+ const gamepadSettings = window.BX_STREAM_SETTINGS.controllers[e.gamepad.id];
+ if (gamepadSettings?.customization) {
+ const intensity = gamepadSettings.customization.vibrationIntensity;
+
+ if (intensity <= 0) {
+ e.repeat = 0;
+ // @ts-ignore
+ return;
+ } else if (intensity < 1) {
+ e.leftMotorPercent *= intensity;
+ e.rightMotorPercent *= intensity;
+ e.leftTriggerMotorPercent *= intensity;
+ e.rightTriggerMotorPercent *= intensity;
+ }
+ }
+}
diff --git a/src/modules/patcher/patches/vibration-adjust.js b/src/modules/patcher/patches/vibration-adjust.js
deleted file mode 100755
index f75356d..0000000
--- a/src/modules/patcher/patches/vibration-adjust.js
+++ /dev/null
@@ -1,16 +0,0 @@
-const gamepad = e.gamepad;
-if (gamepad?.connected) {
- const gamepadSettings = window.BX_STREAM_SETTINGS.controllers[gamepad.id];
- if (gamepadSettings) {
- const intensity = gamepadSettings.vibrationIntensity;
-
- if (intensity === 0) {
- return void(e.repeat = 0);
- } else if (intensity < 1) {
- e.leftMotorPercent *= intensity;
- e.rightMotorPercent *= intensity;
- e.leftTriggerMotorPercent *= intensity;
- e.rightTriggerMotorPercent *= intensity;
- }
- }
-}
diff --git a/src/modules/shortcuts/sound-shortcut.ts b/src/modules/shortcuts/sound-shortcut.ts
index dc0e3ef..6010d7e 100755
--- a/src/modules/shortcuts/sound-shortcut.ts
+++ b/src/modules/shortcuts/sound-shortcut.ts
@@ -17,7 +17,7 @@ export class SoundShortcut {
return 0;
}
- const currentValue = getPref(PrefKey.AUDIO_VOLUME);
+ const currentValue = getPref(PrefKey.AUDIO_VOLUME);
let nearestValue: number;
if (amount > 0) { // Increase
@@ -49,7 +49,7 @@ export class SoundShortcut {
static muteUnmute() {
if (getPref(PrefKey.AUDIO_VOLUME_CONTROL_ENABLED) && STATES.currentStream.audioGainNode) {
const gainValue = STATES.currentStream.audioGainNode.gain.value;
- const settingValue = getPref(PrefKey.AUDIO_VOLUME);
+ const settingValue = getPref(PrefKey.AUDIO_VOLUME);
let targetValue: number;
if (settingValue === 0) { // settingValue is 0 => set to 100
diff --git a/src/modules/stream-player.ts b/src/modules/stream-player.ts
index d323a24..5bb50ba 100755
--- a/src/modules/stream-player.ts
+++ b/src/modules/stream-player.ts
@@ -7,7 +7,7 @@ import { STATES } from "@/utils/global";
import { PrefKey } from "@/enums/pref-keys";
import { getPref } from "@/utils/settings-storages/global-settings-storage";
import { BX_FLAGS } from "@/utils/bx-flags";
-import { StreamPlayerType, StreamVideoProcessing, VideoPosition, VideoRatio } from "@/enums/pref-values";
+import { StreamPlayerType, StreamVideoProcessing, VideoPosition } from "@/enums/pref-values";
export type StreamPlayerOptions = Partial<{
processing: string,
@@ -44,7 +44,7 @@ export class StreamPlayer {
const $fragment = document.createDocumentFragment();
- this.$videoCss = CE('style', { id: 'bx-video-css' });
+ this.$videoCss = CE('style', { id: 'bx-video-css' });
$fragment.appendChild(this.$videoCss);
// Setup SVG filters
@@ -60,7 +60,7 @@ export class StreamPlayer {
id: 'bx-filter-usm-matrix',
order: '3',
xmlns: 'http://www.w3.org/2000/svg',
- })),
+ }) as unknown as SVGFEConvolveMatrixElement),
),
);
$fragment.appendChild($svg);
@@ -98,7 +98,7 @@ export class StreamPlayer {
}
private resizePlayer() {
- const PREF_RATIO = getPref(PrefKey.VIDEO_RATIO);
+ const PREF_RATIO = getPref(PrefKey.VIDEO_RATIO);
const $video = this.$video;
const isNativeTouchGame = STATES.currentStream.titleInfo?.details.hasNativeTouchSupport;
@@ -142,7 +142,7 @@ export class StreamPlayer {
// Set position
const $parent = $video.parentElement!;
- const position = getPref(PrefKey.VIDEO_POSITION);
+ const position = getPref(PrefKey.VIDEO_POSITION);
$parent.style.removeProperty('padding-top');
$parent.dataset.position = position;
diff --git a/src/modules/stream/stream-settings-utils.ts b/src/modules/stream/stream-settings-utils.ts
index 7c4bea3..51fa98a 100755
--- a/src/modules/stream/stream-settings-utils.ts
+++ b/src/modules/stream/stream-settings-utils.ts
@@ -7,7 +7,7 @@ import { StreamVideoProcessing, StreamPlayerType } from "@/enums/pref-values";
import { escapeCssSelector } from "@/utils/html";
export function onChangeVideoPlayerType() {
- const playerType = getPref(PrefKey.VIDEO_PLAYER_TYPE);
+ const playerType = getPref(PrefKey.VIDEO_PLAYER_TYPE);
const $videoProcessing = document.getElementById(`bx_setting_${escapeCssSelector(PrefKey.VIDEO_PROCESSING)}`) as HTMLSelectElement;
const $videoSharpness = document.getElementById(`bx_setting_${escapeCssSelector(PrefKey.VIDEO_SHARPNESS)}`) as HTMLElement;
const $videoPowerPreference = document.getElementById(`bx_setting_${escapeCssSelector(PrefKey.VIDEO_POWER_PREFERENCE)}`) as HTMLElement;
diff --git a/src/modules/stream/stream-stats.ts b/src/modules/stream/stream-stats.ts
index 4417dc6..d1e7fc0 100755
--- a/src/modules/stream/stream-stats.ts
+++ b/src/modules/stream/stream-stats.ts
@@ -192,8 +192,8 @@ export class StreamStats {
}
refreshStyles() {
- const PREF_ITEMS = getPref(PrefKey.STATS_ITEMS);
- const PREF_OPACITY_BG = getPref(PrefKey.STATS_OPACITY_BACKGROUND);
+ const PREF_ITEMS = getPref(PrefKey.STATS_ITEMS);
+ const PREF_OPACITY_BG = getPref(PrefKey.STATS_OPACITY_BACKGROUND);
const $container = this.$container;
$container.dataset.stats = '[' + PREF_ITEMS.join('][') + ']';
diff --git a/src/modules/touch-controller.ts b/src/modules/touch-controller.ts
index a2bd247..43a6519 100755
--- a/src/modules/touch-controller.ts
+++ b/src/modules/touch-controller.ts
@@ -289,8 +289,8 @@ export class TouchController {
TouchController.#$style = $style;
- const PREF_STYLE_STANDARD = getPref(PrefKey.TOUCH_CONTROLLER_STYLE_STANDARD);
- const PREF_STYLE_CUSTOM = getPref(PrefKey.TOUCH_CONTROLLER_STYLE_CUSTOM);
+ const PREF_STYLE_STANDARD = getPref(PrefKey.TOUCH_CONTROLLER_STYLE_STANDARD);
+ const PREF_STYLE_CUSTOM = getPref(PrefKey.TOUCH_CONTROLLER_STYLE_CUSTOM);
BxEventBus.Stream.on('dataChannelCreated', payload => {
const { dataChannel } = payload;
diff --git a/src/modules/ui/dialog/navigation-dialog.ts b/src/modules/ui/dialog/navigation-dialog.ts
index dbb6918..d39b376 100755
--- a/src/modules/ui/dialog/navigation-dialog.ts
+++ b/src/modules/ui/dialog/navigation-dialog.ts
@@ -1,12 +1,10 @@
import { GamepadKey } from "@/enums/gamepad";
-import { PrefKey } from "@/enums/pref-keys";
import { VIRTUAL_GAMEPAD_ID } from "@/modules/mkb/mkb-handler";
import { BxEvent } from "@/utils/bx-event";
import { BxEventBus } from "@/utils/bx-event-bus";
import { BxLogger } from "@/utils/bx-logger";
-import { CE, isElementVisible } from "@/utils/html";
+import { calculateSelectBoxes, CE, isElementVisible } from "@/utils/html";
import { setNearby } from "@/utils/navigation-utils";
-import { getPref } from "@/utils/settings-storages/global-settings-storage";
export enum NavigationDirection {
UP = 1,
@@ -21,10 +19,10 @@ export type NavigationNearbyElements = Partial<{
focus: NavigationElement | (() => boolean),
loop: ((direction: NavigationDirection) => boolean),
- [NavigationDirection.UP]: NavigationElement | (() => void) | 'previous' | 'next',
- [NavigationDirection.DOWN]: NavigationElement | (() => void) | 'previous' | 'next',
- [NavigationDirection.LEFT]: NavigationElement | (() => void) | 'previous' | 'next',
- [NavigationDirection.RIGHT]: NavigationElement | (() => void) | 'previous' | 'next',
+ [NavigationDirection.UP]: NavigationElement,
+ [NavigationDirection.DOWN]: NavigationElement,
+ [NavigationDirection.LEFT]: NavigationElement,
+ [NavigationDirection.RIGHT]: NavigationElement,
}>;
export interface NavigationElement extends HTMLElement {
@@ -107,16 +105,18 @@ export class NavigationDialogManager {
private static readonly GAMEPAD_POLLING_INTERVAL = 50;
private static readonly GAMEPAD_KEYS = [
- GamepadKey.UP,
- GamepadKey.DOWN,
- GamepadKey.LEFT,
- GamepadKey.RIGHT,
- GamepadKey.A,
- GamepadKey.B,
- GamepadKey.LB,
- GamepadKey.RB,
- GamepadKey.LT,
- GamepadKey.RT,
+ GamepadKey.A, GamepadKey.B,
+ GamepadKey.X, GamepadKey.Y,
+
+ GamepadKey.UP, GamepadKey.RIGHT,
+ GamepadKey.DOWN, GamepadKey.LEFT,
+
+ GamepadKey.LB, GamepadKey.RB,
+ GamepadKey.LT, GamepadKey.RT,
+
+ GamepadKey.L3, GamepadKey.R3,
+
+ GamepadKey.SELECT, GamepadKey.START,
];
private static readonly GAMEPAD_DIRECTION_MAP = {
@@ -172,61 +172,21 @@ export class NavigationDialogManager {
window.addEventListener(BxEvent.XCLOUD_GUIDE_MENU_SHOWN, e => this.hide());
// Calculate minimum width of controller-friendly