Compare commits

..

No commits in common. "ed94c651729b3b525a3390bfcfa4e1bf63c230a0" and "92980649ebd337c1279e5d8ba79ca96736e0f559" have entirely different histories.

12 changed files with 32 additions and 228 deletions

View File

@ -5,6 +5,7 @@ import UglifyJS from 'uglify-js';
import fs from 'fs'; import fs from 'fs';
import webpack from 'webpack'; import webpack from 'webpack';
import path from 'path'; import path from 'path';
import applyPatches from './patches/patches.js';
import ModUtils, { minifyCode } from './modUtils.js'; import ModUtils, { minifyCode } from './modUtils.js';
if (!fs.existsSync("./build")) fs.mkdirSync("./build"); if (!fs.existsSync("./build")) fs.mkdirSync("./build");
@ -55,9 +56,8 @@ script = script.replace(/\bS\[(\d+)\]/g, (_match, index) => `"${stringArray[inde
const modUtils = new ModUtils(minifyCode(script)); const modUtils = new ModUtils(minifyCode(script));
import applyPatches from './patches/main.js'; import customLobbyPatches from './patches/customLobby.js';
console.log("Applying patches..."); customLobbyPatches(modUtils);
applyPatches(modUtils);
// for versions ^1.99.5.2 // for versions ^1.99.5.2
const minificationResult = UglifyJS.minify(modUtils.script, { const minificationResult = UglifyJS.minify(modUtils.script, {
@ -101,10 +101,9 @@ rawCodeSegments.forEach(code => {
}); });
modUtils.executePostMinifyHandlers(); modUtils.executePostMinifyHandlers();
applyPatches(modUtils);
script = modUtils.script; script = modUtils.script;
console.log("Building client code...")
await buildClientCode(); await buildClientCode();
// the dictionary should maybe get embedded into one of the files in the bundle // the dictionary should maybe get embedded into one of the files in the bundle
fs.writeFileSync( fs.writeFileSync(

View File

@ -5,8 +5,6 @@ import UglifyJS from 'uglify-js';
const escapeRegExp = (/** @type {string} */ string) => string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); const escapeRegExp = (/** @type {string} */ string) => string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&');
export function minifyCode(/** @type {string} */ script) { export function minifyCode(/** @type {string} */ script) {
// "return" statements outside of a function throw a parse error
script = "()=>{" + script + "}";
const output = UglifyJS.minify(script, { const output = UglifyJS.minify(script, {
compress: false, compress: false,
mangle: false mangle: false
@ -14,10 +12,7 @@ export function minifyCode(/** @type {string} */ script) {
if (output.error) throw output.error; if (output.error) throw output.error;
if (output.warnings) throw (output.warnings); if (output.warnings) throw (output.warnings);
if (output.warnings) console.warn(output.warnings); if (output.warnings) console.warn(output.warnings);
let code = output.code; return output.code;
if (code.endsWith(";")) code = code.slice(0, -1);
code = code.slice(5, -1); // unwrap from function
return code;
} }
class ModUtils { class ModUtils {
@ -39,8 +34,6 @@ class ModUtils {
this.matchRawCode = this.matchRawCode.bind(this); this.matchRawCode = this.matchRawCode.bind(this);
this.replaceCode = this.replaceCode.bind(this); this.replaceCode = this.replaceCode.bind(this);
this.waitForMinification = this.waitForMinification.bind(this); this.waitForMinification = this.waitForMinification.bind(this);
this.matchCode = this.matchCode.bind(this);
this.insertCode = this.insertCode.bind(this);
} }
/** @param {RegExp} expression */ /** @param {RegExp} expression */
@ -78,13 +71,11 @@ class ModUtils {
// Return value example: // Return value example:
// When replaceRawCode or matchRawCode are called with "var1=var2+1;" as the code // When replaceRawCode or matchRawCode are called with "var1=var2+1;" as the code
// and this matches "a=b+1;", the returned value will be the object: { var1: "a", var2: "b" } // and this matches "a=b+1;", the returned value will be the object: { var1: "a", var2: "b" }
/** @param {{ [x: string]: string; }} [nameMappings] */
replaceRawCode(/** @type {string} */ raw, /** @type {string} */ result, nameMappings) { replaceRawCode(/** @type {string} */ raw, /** @type {string} */ result, nameMappings) {
const { expression, groups } = this.generateRegularExpression(raw, false, nameMappings); const { expression, groups } = this.generateRegularExpression(raw, false, nameMappings);
let localizerCount = 0; let localizerCount = 0;
let replacementString = result.replaceAll("$", "$$").replaceAll("__L()", "__L)").replaceAll("__L(", "__L,") let replacementString = result.replaceAll("$", "$$").replaceAll("__L()", "__L)").replaceAll("__L(", "__L,")
.replace(/\w+/g, match => { .replace(/\w+/g, match => {
if (nameMappings !== undefined && nameMappings.hasOwnProperty(match)) return nameMappings[match];
// these would get stored as "___localizer1", "___localizer2", ... // these would get stored as "___localizer1", "___localizer2", ...
if (match === "__L") match = "___localizer" + (++localizerCount); if (match === "__L") match = "___localizer" + (++localizerCount);
return groups.hasOwnProperty(match) ? "$" + groups[match] : match; return groups.hasOwnProperty(match) ? "$" + groups[match] : match;
@ -99,14 +90,8 @@ class ModUtils {
} }
matchRawCode(/** @type {string} */ raw, nameMappings) { matchRawCode(/** @type {string} */ raw, nameMappings) {
const { expression, groups } = this.generateRegularExpression(raw, false, nameMappings); const { expression, groups } = this.generateRegularExpression(raw, false, nameMappings);
try {
const expressionMatchResult = this.matchOne(expression); const expressionMatchResult = this.matchOne(expression);
return Object.fromEntries(Object.entries(groups).map( return Object.fromEntries(Object.entries(groups).map(([identifier, groupNumber]) => [identifier, expressionMatchResult[groupNumber]]));
([identifier, groupNumber]) => [identifier, expressionMatchResult[groupNumber]]
));
} catch (e) {
throw new Error("matchRawCode match error:\n\n" + e + "\n\nRaw code: " + raw + "\n");
}
} }
generateRegularExpression(/** @type {string} */ code, /** @type {boolean} */ isForDictionary, nameMappings) { generateRegularExpression(/** @type {string} */ code, /** @type {boolean} */ isForDictionary, nameMappings) {
const groups = {}; const groups = {};
@ -134,38 +119,9 @@ class ModUtils {
let expression = new RegExp(isForDictionary ? raw.replaceAll("@@", "@") : raw, "g"); let expression = new RegExp(isForDictionary ? raw.replaceAll("@@", "@") : raw, "g");
return { expression, groups }; return { expression, groups };
} }
replaceCode(code, replacement, options) {
/** return this.replaceRawCode(minifyCode(code), replacement);
* @typedef {{ dictionary?: { [x: string]: string } }} BaseOptions
* @typedef {BaseOptions & { addToDictionary?: string[] }} MatchCodeOptions
*/
matchCode(code, /** @type {MatchCodeOptions=} */ options) {
const result = this.matchRawCode(minifyCode(code));
if (options?.addToDictionary !== undefined) {
options.addToDictionary.forEach(varName => {
if (result[varName] === undefined)
throw new Error(`matchCode addToDictionary error: ${varName} was undefined in the match results`)
this.addToDictionary(varName, result[varName]);
});
} }
return result;
}
replaceCode(/** @type {string} */ code, /** @type {string} */ replacement, /** @type {BaseOptions=} */ options) {
return this.replaceRawCode(minifyCode(code), replacement, options?.dictionary);
}
/**
* @param {string} code
* @param {string} codeToInsert
* @param {BaseOptions} [options]
*/
insertCode(code, codeToInsert, options) {
const insertionPoint = "/* here */";
if (!code.includes(insertionPoint)) throw new Error("insertCode: No insertion point found");
return this.replaceCode(code.replace(insertionPoint, ""), code.replace(insertionPoint, codeToInsert), options);
}
waitForMinification(/** @type {Function} */ handler) { waitForMinification(/** @type {Function} */ handler) {
this.postMinifyHandlers.push(handler); this.postMinifyHandlers.push(handler);
} }

View File

@ -1,17 +1,23 @@
import ModUtils from '../modUtils.js'; import ModUtils from '../modUtils.js';
// Custom lobby patches // Custom lobby patches
export default (/** @type {ModUtils} */ { insertCode, replaceRawCode, dictionary: dict, waitForMinification }) => { export default (/** @type {ModUtils} */ { replaceCode, replaceRawCode, dictionary: dict, waitForMinification }) => {
// set player id correctly // set player id correctly
insertCode(`function aBG(aBE) { replaceCode(`function aBG(aBE) {
if (!Lobby.aAl) { return -1; } if (!Lobby.aAl) { return -1; }
/* here */
var s = aBE.length; var s = aBE.length;
var qu = Lobby.aAl.qu; var qu = Lobby.aAl.qu;
for (var i = 0; i < s; i++) { if (aBE[i].qu === qu) { return i; } } for (var i = 0; i < s; i++) { if (aBE[i].qu === qu) { return i; } }
return -1; return -1;
}`, `if (__fx.customLobby.isActive()) return __fx.customLobby.getPlayerId();`); }`, `function aBG(aBE) {
if (!Lobby.aAl) { return -1; }
if (__fx.customLobby.isActive()) return __fx.customLobby.getPlayerId();
var s = aBE.length;
var qu = Lobby.aAl.qu;
for (var i = 0; i < s; i++) { if (aBE[i].qu === qu) { return i; } }
return -1;
}`);
waitForMinification(() => { waitForMinification(() => {
replaceRawCode("this.aHm=function(){i___.rX(),aM.a7U(0),aM.init()}", replaceRawCode("this.aHm=function(){i___.rX(),aM.a7U(0),aM.init()}",
@ -36,10 +42,10 @@ export default (/** @type {ModUtils} */ { insertCode, replaceRawCode, dictionary
wR===1 && __fx.customLobby.isActive() && ${dict.MenuManager}.${dict.getState}() !== 6 && __fx.customLobby.setActive(false); wR===1 && __fx.customLobby.isActive() && ${dict.MenuManager}.${dict.getState}() !== 6 && __fx.customLobby.setActive(false);
if(8===i.pz&&0===wR)if(4211===d)wS(d);`) if(8===i.pz&&0===wR)if(4211===d)wS(d);`)
// when leaving a game // when leaving a game
replaceRawCode("this.wl=function(zs){a1.gZ||az.oO.a11.length||(az.oO.a11=az.a12.vd()),ap.ky.zt(),this.vH=0,bU.zu(),m.n.setState(0),aN.setState(0),zs||bJ.df.show(),2===this.a3D?i.ky.a3U():1===this.a3D?i.j(19):i.j(5,5)}", replaceRawCode("this.wl=function(zs){a1.gZ||az.oO.a11.length||(az.oO.a11=az.a12.vd()),ap.ky.zt(),this.vH=0,bU.zu(),m.n.setState(0),zs||bJ.df.show(),aN.setState(0),2===this.a3D?i.ky.a3U():1===this.a3D?i.j(19):i.j(5,5)}",
`this.wl=function(zs){a1.gZ||az.oO.a11.length||(az.oO.a11=az.a12.vd()), `this.wl=function(zs){a1.gZ||az.oO.a11.length||(az.oO.a11=az.a12.vd()),
__fx.customLobby.isActive() === false && ap.ky.zt(), __fx.customLobby.isActive() === false && ap.ky.zt(),
this.vH=0,bU.zu(),m.n.setState(0),aN.setState(0),zs||bJ.df.show(); this.vH=0,bU.zu(),m.n.setState(0),zs||bJ.df.show(),aN.setState(0);
if (__fx.customLobby.isActive()) __fx.customLobby.rejoinLobby(); else 2===this.a3D?i.ky.a3U():1===this.a3D?i.j(19):i.j(5,5)}`) if (__fx.customLobby.isActive()) __fx.customLobby.rejoinLobby(); else 2===this.a3D?i.ky.a3U():1===this.a3D?i.j(19):i.j(5,5)}`)
// do not display lobby UI // do not display lobby UI
replaceRawCode(`(sV.style.backdropFilter="blur(4px)",sV.style.webkitBackdropFilter="blur(4px)"),`, replaceRawCode(`(sV.style.backdropFilter="blur(4px)",sV.style.webkitBackdropFilter="blur(4px)"),`,

View File

@ -1,22 +0,0 @@
import fs from 'fs'
import ModUtils from '../modUtils.js';
const modules = await Promise.all(fs.readdirSync("./patches").flatMap(fileName => {
if (fileName === "main.js") return [];
else return import("./" + fileName);
}));
const requiredVariables = new Set(modules.map(module => module.requiredVariables ?? []).flat());
export default function applyPatches(/** @type {ModUtils} */ modUtils) {
const dictionary = modUtils.dictionary;
requiredVariables.forEach(varName => {
if (!dictionary.hasOwnProperty(varName)) {
throw new Error(`"${varName}" is required by a module but not defined the dictionary`);
}
});
// apply patches (default exported function)
modules.forEach(module => module.default(modUtils))
}

View File

@ -1,56 +0,0 @@
export default (/** @type {import('../modUtils.js').default} */ { insertCode, replaceCode, matchCode }) => {
const { mainCanvas, x, y } = insertCode(`this.te = function() {
if (!this.b()) { return; }
mainCanvas.drawImage(canvas, x, this.y);
/* here */
}`, `if (__fx.settings.keybindButtons) __fx.mobileKeybinds.draw(mainCanvas, x, this.y);`)
const { h, redraw } = insertCode(`a6k = Math.floor(3 * this.h / 2);
a4M = c.pZ.rN(1, Math.floor(0.5 * this.h));
canvas = document.createElement("canvas");
canvas.width = w;
/* here */
canvas.height = this.h;
ctx = canvas.getContext("2d", { alpha: true });
ctx.font = a4M;
c.pZ.textBaseline(ctx, 1);
c.pZ.textAlign(ctx, 1);
this.a6m();
redraw();
`, `__fx.mobileKeybinds.setSize(w, this.h, mainCanvas)`, { dictionary: { mainCanvas } })
const { ba, gap } = matchCode(`this.h = Math.floor(0.066 * h___.pb); w = h___.w - 4 * ba.gap - this.h;`);
const { bd, requestRepaint } = insertCode(`this.gm = function(kt, ku) {
if (!this.b()) { return false; }
/* here */
if (!a.a0n(kt, ku)) { return false; }
aR.mC = false;
if (a6w(this, kt, ku)) { bd.requestRepaint = true; }
return true;
};`,
`if (__fx.settings.keybindButtons && ku > this.y - Math.floor(ba.gap / 4) - this.h && ku < this.y - Math.floor(ba.gap / 4) && __fx.mobileKeybinds.click(kt - x)) return true;`,
{ dictionary: { x, y, h, ba, gap } }
)
insertCode(
`var a6l = 11 / 12; /* here */`,
`__fx.keybindFunctions.repaintAttackPercentageBar = function() { redraw(); bd.requestRepaint = true; };`,
{ dictionary: { redraw, bd, requestRepaint } }
)
// fix to correctly display peace vote menu and game messages (prevent overlap with keybind buttons)
replaceCode(`if (a.a4y(aM.a4u())) {
if (au.b) { return a.y - a.h - 2 * a4a; }
else { return a.y - a4a; }
}`, `if (a.a4y(aM.a4u())) {
if (au.b) { return __fx.settings.keybindButtons ? a.y - 2 * a.h - 3 * a4a : a.y - a.h - 2 * a4a; }
else { return __fx.settings.keybindButtons ? a.y - a.h - 2 * a4a : a.y - a4a; }
}`)
insertCode(
`if (a.a4y(aM.a4u())) { return /* here */ a.y - h - ba.gap; }`,
`__fx.settings.keybindButtons ? a.y - 2 * (h + ba.gap) : `
)
}

View File

@ -1,12 +1,6 @@
import assets from '../assets.js'; import assets from '../assets.js';
import ModUtils from '../modUtils.js'; import ModUtils from '../modUtils.js';
export default (/** @type {ModUtils} */ { replace, replaceOne, replaceRawCode, dictionary, matchOne, matchRawCode, escapeRegExp }) => {
export default (/** @type {ModUtils} */ modUtils) => {
modUtils.waitForMinification(() => applyPatches(modUtils))
}
//export const requiredVariables = ["game", "playerId", "playerData", "rawPlayerNames", "gIsSingleplayer", "playerTerritories"];
function applyPatches(/** @type {ModUtils} */ { replace, replaceOne, replaceRawCode, dictionary, matchOne, matchRawCode, escapeRegExp }) {
// Constants for easy usage of otherwise long variable access expressions // Constants for easy usage of otherwise long variable access expressions
const dict = dictionary; const dict = dictionary;

View File

@ -1,10 +1,7 @@
const playerDataProperties = ["playerTerritories", "playerBalances", "rawPlayerNames"]; const playerDataProperties = ["playerTerritories", "playerBalances", "rawPlayerNames"];
const gameObjectProperties = ["playerId", "gIsTeamGame", "gHumans", "gLobbyMaxJoin", "gameState", "gIsSingleplayer"]; const gameObjectProperties = ["playerId", "gIsTeamGame", "gHumans", "gLobbyMaxJoin", "gameState", "gIsSingleplayer"];
export const getVar = varName => { export const getVar = varName => {
if (playerDataProperties.includes(varName)) return window[dictionary.playerData]?.[dictionary[varName]]; if (playerDataProperties.includes(varName)) return window[dictionary.playerData]?.[dictionary[varName]];
if (gameObjectProperties.includes(varName)) return window[dictionary.game]?.[dictionary[varName]]; if (gameObjectProperties.includes(varName)) return window[dictionary.game]?.[dictionary[varName]];
return window[dictionary[varName]] return window[dictionary[varName]]
}; };
export const getUIGap = () => Math.floor(window[dictionary.uiSizes]?.[dictionary.gap] ?? 10);

View File

@ -1,66 +1,10 @@
import { getUIGap } from "./gameInterface.js";
import { getSettings } from "./settings.js"; import { getSettings } from "./settings.js";
export const keybindFunctions = { export const keybindFunctions = { setAbsolute: () => {}, setRelative: () => {} };
setAbsolute: () => {},
setRelative: () => {},
repaintAttackPercentageBar: () => {}
};
export const keybindHandler = key => { export const keybindHandler = key => {
const keybindData = getSettings().attackPercentageKeybinds.find(keybind => keybind.key === key); const keybindData = getSettings().attackPercentageKeybinds.find(keybind => keybind.key === key);
if (keybindData === undefined) return false; if (keybindData === undefined) return false;
executeKeybind(keybindData); if (keybindData.type === "absolute") keybindFunctions.setAbsolute(keybindData.value);
else keybindFunctions.setRelative(keybindData.value);
return true; return true;
}; };
function executeKeybind(keybind) {
if (keybind.type === "absolute") keybindFunctions.setAbsolute(keybind.value);
else keybindFunctions.setRelative(keybind.value);
keybindFunctions.repaintAttackPercentageBar();
}
// mobile keybinds (keybind buttons)
let canvas;
let width = 0;
let height = 0;
const maxCount = 6;
export const mobileKeybinds = {
setSize: (w, h, mainCanvas) => {
if (getSettings().keybindButtons !== true) return;
width = w;
height = h;
// redraw
canvas = document.createElement("canvas");
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext("2d");
const fontName = mainCanvas.font.split("px ", 2)[1];
ctx.font = "bold " + h / 2 + "px " + fontName;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
const keybinds = getSettings().attackPercentageKeybinds.slice(0, maxCount);
const gap = getUIGap() / 4;
const buttonWidth = (w - gap * (maxCount - 1)) / maxCount;
keybinds.forEach((keybind, i) => {
ctx.fillStyle = "rgba(0, 0, 0, 0.8)";
ctx.fillRect(i * (buttonWidth + gap), 0, buttonWidth, h);
ctx.fillStyle = "white";
const label = keybind.type === "absolute" ? (keybind.value * 100).toFixed() + "%" : "x " + Math.round(keybind.value * 100) / 100;
ctx.fillText(label, (i + 0.5) * (buttonWidth + gap), h / 2);
});
},
click: (xRelative) => {
if (xRelative < 0 || xRelative > width) return false;
const keybinds = getSettings().attackPercentageKeybinds;
const index = Math.floor(xRelative / width * maxCount);
if (index >= keybinds.length) return false;
executeKeybind(keybinds[index]);
return true;
},
draw: (mainCanvas, x, y) => {
mainCanvas.drawImage(canvas, x, y - (height + getUIGap() / 4));
}
}

View File

@ -1,4 +1,4 @@
export function KeybindsInput(/** @type {HTMLElement} */ containerElement) { export function KeybindsInput(containerElement) {
const header = document.createElement("p"); const header = document.createElement("p");
header.innerText = "Attack Percentage Keybinds"; header.innerText = "Attack Percentage Keybinds";
const keybindContainer = document.createElement("div"); const keybindContainer = document.createElement("div");
@ -6,7 +6,6 @@ export function KeybindsInput(/** @type {HTMLElement} */ containerElement) {
const keybindAddButton = document.createElement("button"); const keybindAddButton = document.createElement("button");
keybindAddButton.innerText = "Add"; keybindAddButton.innerText = "Add";
containerElement.append(header, keybindContainer, keybindAddButton); containerElement.append(header, keybindContainer, keybindAddButton);
containerElement.className = "keybinds-input";
this.container = keybindContainer; this.container = keybindContainer;
this.keys = [ "key", "type", "value" ]; this.keys = [ "key", "type", "value" ];
this.objectArray = []; this.objectArray = [];

View File

@ -1,5 +1,5 @@
const fx_version = '0.6.7.2'; // FX Client Version const fx_version = '0.6.7.1'; // FX Client Version
const fx_update = 'Mar 2'; // FX Client Last Updated const fx_update = 'Feb 15'; // FX Client Last Updated
if ("serviceWorker" in navigator) { if ("serviceWorker" in navigator) {
navigator.serviceWorker.addEventListener("message", (e) => { navigator.serviceWorker.addEventListener("message", (e) => {
@ -20,7 +20,7 @@ import winCounter from "./winCounter.js";
import playerList from "./playerList.js"; import playerList from "./playerList.js";
import gameScriptUtils from "./gameScriptUtils.js"; import gameScriptUtils from "./gameScriptUtils.js";
import hoveringTooltip from "./hoveringTooltip.js"; import hoveringTooltip from "./hoveringTooltip.js";
import { keybindFunctions, keybindHandler, mobileKeybinds } from "./keybinds.js"; import { keybindFunctions, keybindHandler } from "./keybinds.js";
import customLobby from './customLobby.js'; import customLobby from './customLobby.js';
window.__fx = window.__fx || {}; window.__fx = window.__fx || {};
@ -33,7 +33,6 @@ __fx.utils = gameScriptUtils;
__fx.WindowManager = WindowManager; __fx.WindowManager = WindowManager;
__fx.keybindFunctions = keybindFunctions; __fx.keybindFunctions = keybindFunctions;
__fx.keybindHandler = keybindHandler; __fx.keybindHandler = keybindHandler;
__fx.mobileKeybinds = mobileKeybinds;
__fx.donationsTracker = donationsTracker; __fx.donationsTracker = donationsTracker;
__fx.playerList = playerList; __fx.playerList = playerList;
__fx.hoveringTooltip = hoveringTooltip; __fx.hoveringTooltip = hoveringTooltip;

View File

@ -21,7 +21,6 @@ var settings = {
detailedTeamPercentage: false, detailedTeamPercentage: false,
//"customMapFileBtn": true //"customMapFileBtn": true
customBackgroundUrl: "", customBackgroundUrl: "",
keybindButtons: false,
attackPercentageKeybinds: [], attackPercentageKeybinds: [],
}; };
__fx.settings = settings; __fx.settings = settings;
@ -103,10 +102,6 @@ const settingsManager = new (function () {
"A custom image to be shown as the main menu background instead of the currently selected map.", "A custom image to be shown as the main menu background instead of the currently selected map.",
}, },
KeybindsInput, KeybindsInput,
{
for: "keybindButtons", type: "checkbox",
label: "Keybind buttons", note: "Show keybind buttons above the troop selector (max 6)"
}
]; ];
const settingsContainer = document.querySelector(".settings .scrollable"); const settingsContainer = document.querySelector(".settings .scrollable");
var inputFields = {}; // (includes select menus) var inputFields = {}; // (includes select menus)

View File

@ -28,7 +28,7 @@
border-width : 2px; border-width : 2px;
border-width : calc(0.15 * (1vw + 1vh)); border-width : calc(0.15 * (1vw + 1vh));
font-size : 20px; font-size : 20px;
font-size : calc(13px + ((0.5 * (1.1vw - 0.1vh)) + 0.14rem)); font-size : calc(14px + ((0.5 * (1.1vw - 0.1vh)) + 0.14rem));
max-height : 90%; max-height : 90%;
transition : 0.2s; transition : 0.2s;
z-index : 10; z-index : 10;
@ -59,13 +59,6 @@
margin: 0px; margin: 0px;
} }
.keybinds-input {
margin-bottom: 1em;
}
.keybinds-input input {
width: 10em;
}
.flex { .flex {
display: flex; display: flex;
} }