Fullscreen hitbox.io, reserve spots, away-tab alerts, audio controls, mobile Grab, readable chat, lobby commands, and first-start setup for hitbox.io.
// ==UserScript==
// @name QOLBox
// @namespace Violentmonkey Scripts
// @author gpt-5.4 and gpt-5.5
// @version 2.0.0
// @description Fullscreen hitbox.io, reserve spots, away-tab alerts, audio controls, mobile Grab, readable chat, lobby commands, and first-start setup for hitbox.io.
// @license ISC
// @match https://hitbox.io/
// @match https://www.hitbox.io/
// @match https://hitbox.io/game2.html*
// @match https://www.hitbox.io/game2.html*
// @run-at document-start
// @inject-into page
// @grant none
// ==/UserScript==
"use strict";
(() => {
// src/config/qolbox-constants.ts
var DESKTOP_LOBBY_CHAT_PROMPT = "Press enter to send a message";
var TOUCH_LOBBY_CHAT_PROMPT = "Tap here to send a message";
var MENU_KEY_LABEL = "F8";
var MENU_KEY = "F8";
var QOLBOX_MENU_ID = "qolboxMenu";
var QOLBOX_MENU_ROOT_CLASS = "qolbox-menu-open";
var FALLBACK_BASE_WIDTH = 800;
var FALLBACK_BASE_HEIGHT = 500;
var SCORE_ROW_FALLBACK_RGB = { red: 225, green: 21, blue: 0, alpha: 1 };
var TEAM_SCORE_COLORS = /* @__PURE__ */ new Map([
[2, { red: 225, green: 21, blue: 0, alpha: 1 }],
[3, { red: 0, green: 117, blue: 225, alpha: 1 }]
]);
var FULLSCREEN_GAMEPLAY_LAYER_SELECTOR = "#pixiContainer, #singlePlayer, .singlePlayer";
var FULLSCREEN_EDITOR_LAYER_SELECTOR = "#editorContainer";
var FULLSCREEN_MENU_LAYER_SELECTOR = ".replayViewer";
var CHAT_INPUT_SELECTOR = ".inGameChat .input, .lobbyContainer .chatBox .input";
var FULLSCREEN_PLAY_LAYER_SELECTOR = [
FULLSCREEN_GAMEPLAY_LAYER_SELECTOR,
FULLSCREEN_EDITOR_LAYER_SELECTOR
].join(", ");
var FULLSCREEN_RENDER_LAYER_SELECTOR = [
FULLSCREEN_PLAY_LAYER_SELECTOR,
FULLSCREEN_MENU_LAYER_SELECTOR
].join(", ");
var FULLSCREEN_RENDER_CANVAS_SELECTORS = [
"#pixiContainer canvas",
"#singlePlayer canvas",
".singlePlayer canvas",
"#editorContainer > canvas",
".replayViewer canvas"
];
var FULLSCREEN_RENDER_CANVAS_SELECTOR = FULLSCREEN_RENDER_CANVAS_SELECTORS.join(", ");
var FULLSCREEN_RENDER_CANVAS_FOCUS_SELECTOR = FULLSCREEN_RENDER_CANVAS_SELECTORS.flatMap((selector) => [`${selector}:focus`, `${selector}:focus-visible`]).join(", ");
var FULLSCREEN_LAYOUT_TARGET_SELECTOR = [
"#appContainer",
"#relativeContainer",
"#backgroundImage",
FULLSCREEN_RENDER_LAYER_SELECTOR,
FULLSCREEN_RENDER_CANVAS_SELECTOR,
".inGameCSS",
".scores",
".spectateControls"
].join(", ");
var GAMEPLAY_FOCUS_EXCLUSION_SELECTOR = [
CHAT_INPUT_SELECTOR,
".inGameChat",
".lobbyContainer",
".cornerButton",
".cornerButton .items",
".jukebox",
".scores",
".spectateControls",
".qolboxMenuOverlay",
".qolboxReserveWindowContainer",
".connectingWindowContainer",
".passwordWindowContainer",
".buttonArea",
"button",
"input",
"select",
"textarea",
"a",
".button",
".bottomButton",
".item"
].join(", ");
var FEATURE_PATCH_TARGET_SELECTOR = [
CHAT_INPUT_SELECTOR,
".items.left",
".items.left .item",
".jukebox",
".jukebox .knob.volumeContainer",
".buttonArea",
".cornerButton .items",
".cornerButton .items .item",
"#ytContainer",
"#ytContainer iframe",
".roomListContainer",
".roomListContainer .scrollBox tr",
".roomListContainer .bottomButton.right",
".passwordWindowContainer",
".connectingWindowContainer",
".lobbyContainer",
".lobbyContainer .teamsButtonContainer"
].join(", ");
var FULLSCREEN_SETTLE_PASSES = 4;
var FULLSCREEN_NATIVE_LAYOUT_WAIT_MS = 2500;
var RESIZE_SETTLE_PASSES = 2;
var JUKEBOX_WHEEL_STEP = 5;
var JUKEBOX_DRAG_SENSITIVITY = 1;
var YOUTUBE_HOOK_RETRY_DELAY_MS = 250;
var YOUTUBE_HOOK_MAX_RETRIES = 120;
var RESERVE_BUTTON_TEXT = "RESERVE";
var JOIN_BUTTON_TEXT = "JOIN";
var RESERVE_WAIT_TITLE_TEXT = "Waiting for a Spot";
var RESERVE_WAIT_TEXT = "Waiting for someone to leave...";
var RESERVE_STATUS_FALLBACK_TEXT = "Connecting...";
var RESERVE_UNAVAILABLE_TITLE_TEXT = "Lobby Not Available";
var RESERVE_ONE_PERSON_TEXT = "This lobby only allows one person, so there is no spot to reserve.";
var RESERVE_RETRY_DELAY_MS = 2500;
var RESERVE_COUNTDOWN_UPDATE_MS = 100;
var RESERVE_RETRY_AUDIO_SUPPRESS_MS = 900;
var RESERVE_JOINED_ROOM_FULL_SUPPRESS_MS = 12e3;
var RESERVE_ROOM_FULL_PATTERN = /room[_ ]?full|room is full/i;
var RESERVE_ROOM_CLOSED_PATTERN = /room[_ ]?not[_ ]?found|room has just closed/i;
var RESERVE_WRONG_PASSWORD_PATTERN = /wrong[_ ]?password|password incorrect|incorrect password/i;
var GAME_START_INDICATOR_DELAY_MS = 1200;
var GAME_START_WATCH_INTERVAL_MS = 750;
var GAME_START_FLASH_INTERVAL_MS = 700;
var GAME_START_END_WATCH_INTERVAL_MS = 1e3;
var GAME_START_LOCAL_TRANSITION_TIMEOUT_MS = 5e3;
var TYPING_INDICATOR_TIMEOUT_MS = 1600;
var IS_QOLBOX_GAME_PAGE = /\/game2\.html$/i.test(window.location.pathname);
// src/utils/object-properties.ts
function isReflectableObject(value) {
return typeof value === "object" && value !== null || typeof value === "function";
}
function readObjectProperty(source, property) {
return isReflectableObject(source) ? Reflect.get(source, property) : void 0;
}
function setObjectProperty(source, property, value) {
if (!isReflectableObject(source)) {
return false;
}
try {
return Reflect.set(source, property, value);
} catch {
return false;
}
}
// src/features/game-start-shared.ts
var GAME_START_TITLE_PREFIX = "[GAME STARTED] ";
var GAME_PULLED_TITLE_PREFIX = "[PULLED INTO GAME] ";
var GAME_START_TITLE_PREFIXES = [GAME_START_TITLE_PREFIX, GAME_PULLED_TITLE_PREFIX];
var GAME_START_FAVICON_HREF = "data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 64 64%22%3E%3Crect width=%2264%22 height=%2264%22 rx=%2212%22 fill=%22%23f5c542%22/%3E%3Cpath d=%22M32 10 56 54H8Z%22 fill=%22%23111111%22/%3E%3Crect x=%2229%22 y=%2223%22 width=%226%22 height=%2217%22 rx=%223%22 fill=%22%23f5c542%22/%3E%3Ccircle cx=%2232%22 cy=%2247%22 r=%223%22 fill=%22%23f5c542%22/%3E%3C/svg%3E";
function stripGameStartTitlePrefix(title) {
const value = String(title);
for (const prefix of GAME_START_TITLE_PREFIXES) {
if (value.startsWith(prefix)) {
return value.slice(prefix.length);
}
}
return value;
}
// src/features/top-level-page.ts
var TOP_LEVEL_INPUT_STYLE_ID = "qolbox-top-level-input-style";
var HITBOX_ORIGIN_PATTERN = /^https:\/\/(www\.)?hitbox\.io$/i;
function installTopLevelGameInputPassthrough() {
const applyPassthroughStyle = () => {
if (document.getElementById(TOP_LEVEL_INPUT_STYLE_ID)) {
return true;
}
const root = document.head || document.documentElement;
if (!root) {
return false;
}
const style = document.createElement("style");
style.id = TOP_LEVEL_INPUT_STYLE_ID;
style.textContent = `
#adboxverticalleft,
#adboxverticalright {
pointer-events: none !important;
}
#adboxverticalleft iframe,
#adboxverticalleft a,
#adboxverticalright iframe,
#adboxverticalright a {
pointer-events: auto !important;
}
`;
root.appendChild(style);
return true;
};
if (!applyPassthroughStyle()) {
document.addEventListener("DOMContentLoaded", applyPassthroughStyle, { once: true });
}
}
function installTopLevelGameStartRelay() {
if (window.top !== window || window.__qolboxGameStartRelayInstalled) {
return;
}
window.__qolboxGameStartRelayInstalled = true;
let relayActive = false;
let relayOriginalTitle = "";
let relayOriginalFavicon = null;
let relayFaviconLink = null;
function saveRelayState() {
if (relayActive) {
return;
}
const link = document.querySelector('link[rel~="icon"]');
relayOriginalTitle = stripGameStartTitlePrefix(document.title || "");
relayOriginalFavicon = link ? {
href: link.getAttribute("href"),
link,
type: link.getAttribute("type")
} : { href: null, link: null, type: null };
relayFaviconLink = link || document.createElement("link");
if (!link) {
relayFaviconLink.rel = "icon";
(document.head || document.documentElement).appendChild(relayFaviconLink);
}
relayActive = true;
}
function setRelayFavicon(active) {
saveRelayState();
if (!relayFaviconLink) {
return;
}
if (active) {
relayFaviconLink.setAttribute("href", GAME_START_FAVICON_HREF);
relayFaviconLink.setAttribute("type", "image/svg+xml");
return;
}
if (relayOriginalFavicon && relayOriginalFavicon.href) {
relayFaviconLink.setAttribute("href", relayOriginalFavicon.href);
} else {
relayFaviconLink.removeAttribute("href");
}
if (relayOriginalFavicon && relayOriginalFavicon.type) {
relayFaviconLink.setAttribute("type", relayOriginalFavicon.type);
} else {
relayFaviconLink.removeAttribute("type");
}
}
function clearRelayState() {
if (!relayActive) {
return;
}
document.title = relayOriginalTitle;
if (relayOriginalFavicon && relayFaviconLink) {
if (!relayOriginalFavicon.link) {
relayFaviconLink.remove();
} else {
setRelayFavicon(false);
}
}
relayActive = false;
relayOriginalTitle = "";
relayOriginalFavicon = null;
relayFaviconLink = null;
}
window.addEventListener(
"message",
(event) => {
if (!HITBOX_ORIGIN_PATTERN.test(event.origin)) {
return;
}
const data = event.data;
if (readObjectProperty(data, "source") !== "QOLBox" || readObjectProperty(data, "feature") !== "gameStartIndicator") {
return;
}
const action = readObjectProperty(data, "action");
if (action === "title") {
saveRelayState();
document.title = String(readObjectProperty(data, "title") || relayOriginalTitle);
} else if (action === "favicon") {
setRelayFavicon(Boolean(readObjectProperty(data, "active")));
} else if (action === "clear") {
clearRelayState();
}
},
true
);
}
// src/boot/page-entry.ts
function shouldRunGamePageBootstrap() {
if (IS_QOLBOX_GAME_PAGE) {
return true;
}
installTopLevelGameInputPassthrough();
installTopLevelGameStartRelay();
return false;
}
// src/boot/startup-sequence.ts
function runQolboxStartupSequence(options) {
const scheduleInitialSettle = () => {
options.scheduleUiWork({
force: true,
features: true,
passes: FULLSCREEN_SETTLE_PASSES
});
};
options.applyFeatureRootClasses();
options.ensureGlobalStyle();
options.installQolboxMenuHooks();
if (options.isReserveEnabled()) {
options.installReserveSocketCaptureHook();
}
if (options.isAudioEnabled()) {
options.installYouTubeReadyCallbackHook();
}
options.installFullscreenHooks();
options.scheduleFirstBootOnboarding();
scheduleInitialSettle();
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", scheduleInitialSettle, { once: true });
} else {
scheduleInitialSettle();
}
}
// src/settings/advanced-settings.ts
var ADVANCED_RESERVE_RETRY_INTERVAL_MS = "reserveRetryIntervalMs";
var ADVANCED_COMMAND_ALIASES = "commandAliases";
var ADVANCED_ALERT_DELAY_MS = "gameStartAlertDelayMs";
var ADVANCED_ALERT_FLASH_INTERVAL_MS = "gameStartAlertFlashIntervalMs";
var ADVANCED_TYPING_DURATION_MS = "typingIndicatorDurationMs";
var ADVANCED_SETTINGS_KEY = "vm.hitbox.qolboxAdvancedSettings";
var ADVANCED_SETTING_DEFINITIONS = [
{
key: ADVANCED_RESERVE_RETRY_INTERVAL_MS,
kind: "number",
title: "Reserve retry interval",
description: "Milliseconds between reserve join attempts.",
defaultValue: RESERVE_RETRY_DELAY_MS,
min: 500,
max: 1e4,
step: 100,
unit: "ms"
},
{
key: ADVANCED_COMMAND_ALIASES,
kind: "boolean",
title: "Command aliases",
description: "Enable shorthand commands such as /rec and /r.",
defaultValue: true
},
{
key: ADVANCED_ALERT_DELAY_MS,
kind: "number",
title: "Tab alert delay",
description: "Delay before the away-tab title changes.",
defaultValue: GAME_START_INDICATOR_DELAY_MS,
min: 200,
max: 5e3,
step: 100,
unit: "ms"
},
{
key: ADVANCED_ALERT_FLASH_INTERVAL_MS,
kind: "number",
title: "Tab flash speed",
description: "Milliseconds between title/favicon flashes.",
defaultValue: GAME_START_FLASH_INTERVAL_MS,
min: 250,
max: 2e3,
step: 50,
unit: "ms"
},
{
key: ADVANCED_TYPING_DURATION_MS,
kind: "number",
title: "Typing indicator duration",
description: "How long remote typing pulses remain visible.",
defaultValue: TYPING_INDICATOR_TIMEOUT_MS,
min: 500,
max: 5e3,
step: 100,
unit: "ms"
}
];
function isRecord(value) {
return typeof value === "object" && value !== null;
}
function clampNumber(value, definition) {
const stepped = Math.round(value / definition.step) * definition.step;
return Math.min(definition.max, Math.max(definition.min, stepped));
}
function getDefaultAdvancedSettings() {
const settings = {};
for (const definition of ADVANCED_SETTING_DEFINITIONS) {
settings[definition.key] = definition.defaultValue;
}
return settings;
}
function sanitizeAdvancedSetting(definition, value) {
switch (definition.kind) {
case "number": {
const numericValue = Number(value);
return Number.isFinite(numericValue) ? clampNumber(numericValue, definition) : definition.defaultValue;
}
case "boolean":
return value === true || value === "true";
}
}
function loadAdvancedSettings() {
const settings = getDefaultAdvancedSettings();
try {
const rawSettings = localStorage.getItem(ADVANCED_SETTINGS_KEY);
if (!rawSettings) {
return settings;
}
const parsedSettings = JSON.parse(rawSettings);
if (!isRecord(parsedSettings)) {
return settings;
}
for (const definition of ADVANCED_SETTING_DEFINITIONS) {
if (Object.prototype.hasOwnProperty.call(parsedSettings, definition.key)) {
settings[definition.key] = sanitizeAdvancedSetting(definition, parsedSettings[definition.key]);
}
}
} catch {
}
return settings;
}
function saveAdvancedSettings(settings) {
try {
localStorage.setItem(ADVANCED_SETTINGS_KEY, JSON.stringify(settings));
} catch {
}
}
function getAdvancedSettingDefinition(key) {
return ADVANCED_SETTING_DEFINITIONS.find((definition) => definition.key === key) || null;
}
function getAdvancedReserveRetryIntervalMs(settings = loadAdvancedSettings()) {
return settings[ADVANCED_RESERVE_RETRY_INTERVAL_MS];
}
function getAdvancedGameStartAlertDelayMs(settings = loadAdvancedSettings()) {
return settings[ADVANCED_ALERT_DELAY_MS];
}
function getAdvancedGameStartFlashIntervalMs(settings = loadAdvancedSettings()) {
return settings[ADVANCED_ALERT_FLASH_INTERVAL_MS];
}
function getAdvancedTypingIndicatorDurationMs(settings = loadAdvancedSettings()) {
return settings[ADVANCED_TYPING_DURATION_MS];
}
function areAdvancedCommandAliasesEnabled(settings = loadAdvancedSettings()) {
return settings[ADVANCED_COMMAND_ALIASES];
}
// src/settings/advanced-settings-controller.ts
function createAdvancedSettingsController(options) {
const settings = loadAdvancedSettings();
function getAdvancedSettings() {
return { ...settings };
}
function getAdvancedSetting(key) {
return settings[key];
}
function setAdvancedSetting(key, value) {
const definition = getAdvancedSettingDefinition(key);
if (!definition) {
return;
}
settings[definition.key] = sanitizeAdvancedSetting(definition, value);
applyAdvancedSettingsChange();
}
function setAdvancedSettings(nextSettings) {
for (const definition of ADVANCED_SETTING_DEFINITIONS) {
settings[definition.key] = sanitizeAdvancedSetting(definition, nextSettings[definition.key]);
}
applyAdvancedSettingsChange();
}
function resetAdvancedSetting(key) {
const definition = getAdvancedSettingDefinition(key);
if (!definition) {
return;
}
settings[definition.key] = definition.defaultValue;
applyAdvancedSettingsChange();
}
function resetAdvancedSettings() {
const defaults = getDefaultAdvancedSettings();
for (const definition of ADVANCED_SETTING_DEFINITIONS) {
settings[definition.key] = defaults[definition.key];
}
applyAdvancedSettingsChange();
}
function applyAdvancedSettingsChange() {
saveAdvancedSettings(settings);
options.onApplyPersistentFeatures();
options.onScheduleLayoutRefresh();
options.onRenderMenu();
}
return {
getAdvancedSetting,
getAdvancedSettings,
resetAdvancedSetting,
resetAdvancedSettings,
setAdvancedSettings,
setAdvancedSetting
};
}
// src/settings/feature-settings.ts
var FEATURE_FULLSCREEN = "fullscreen";
var FEATURE_AUDIO = "audio";
var FEATURE_RESERVE = "reserve";
var FEATURE_CHAT = "chat";
var FEATURE_GAME_START_ALERT = "gameStartAlert";
var FEATURE_MOBILE_GRAB = "mobileGrab";
var FEATURE_LOBBY_COMMANDS = "lobbyCommands";
var FEATURE_SETTINGS_KEY = "vm.hitbox.qolboxFeatures";
var FEATURE_DEFINITIONS = [
{
key: FEATURE_FULLSCREEN,
title: "Fullscreen Layout",
shortTitle: "Fullscreen",
summary: "Center and scale hitbox.io so the play area uses the browser window cleanly."
},
{
key: FEATURE_AUDIO,
title: "Audio Controls",
shortTitle: "Audio",
summary: "Remember volume choices, make the sliders easier to adjust, and keep jukebox mute behavior stable."
},
{
key: FEATURE_RESERVE,
title: "Reserve Spots",
shortTitle: "Reserve",
summary: "Wait for a spot in full custom lobbies instead of immediately giving up on room_full."
},
{
key: FEATURE_CHAT,
title: "Chat Improvements",
shortTitle: "Chat",
summary: "Press Esc to discard drafts, keep readable game chat scrollable, and show remote typing indicators."
},
{
key: FEATURE_GAME_START_ALERT,
title: "Away Game Alert",
shortTitle: "Game Alert",
summary: "Flash the tab title and favicon when you need to play while away from the tab."
},
{
key: FEATURE_MOBILE_GRAB,
title: "Mobile Grab Button",
shortTitle: "Mobile Grab",
summary: "Add the missing Grab control to the game's mobile ability buttons."
},
{
key: FEATURE_LOBBY_COMMANDS,
title: "Lobby Commands",
shortTitle: "Commands",
summary: "Add practical lobby controls, bulk player targets, and the complete host-settings listing.",
onboardingText: "Use /spec, /join, /red, /blue, /switch, /lock, /unlock, /host, /start, /end and /restart. /rec is shorthand for /record, and /r is shorthand for /restart. Use all, playing, or spectators for group targets, /settings all for every host setting, and exact or unique partial names with native /kick and /ban."
}
];
var DEFAULT_FEATURE_SETTINGS = {
[FEATURE_FULLSCREEN]: true,
[FEATURE_AUDIO]: true,
[FEATURE_RESERVE]: true,
[FEATURE_CHAT]: true,
[FEATURE_GAME_START_ALERT]: true,
[FEATURE_MOBILE_GRAB]: true,
[FEATURE_LOBBY_COMMANDS]: true
};
function isRecord2(value) {
return typeof value === "object" && value !== null;
}
function getDefaultFeatureSettings() {
return { ...DEFAULT_FEATURE_SETTINGS };
}
function loadFeatureSettings() {
const defaults = getDefaultFeatureSettings();
try {
const rawSettings = localStorage.getItem(FEATURE_SETTINGS_KEY);
if (!rawSettings) {
return defaults;
}
const parsedSettings = JSON.parse(rawSettings);
if (!isRecord2(parsedSettings)) {
return defaults;
}
for (const feature of FEATURE_DEFINITIONS) {
if (Object.prototype.hasOwnProperty.call(parsedSettings, feature.key)) {
defaults[feature.key] = parsedSettings[feature.key] !== false;
}
}
} catch {
}
return defaults;
}
function saveFeatureSettings(settings) {
try {
localStorage.setItem(FEATURE_SETTINGS_KEY, JSON.stringify(settings));
} catch {
}
}
function isKnownFeature(featureKey) {
return FEATURE_DEFINITIONS.some((feature) => feature.key === featureKey);
}
// src/settings/feature-gates.ts
function createFeatureGateSet(shouldRunFeature) {
return {
isAudioEnabled: () => shouldRunFeature(FEATURE_AUDIO),
isChatEnabled: () => shouldRunFeature(FEATURE_CHAT),
isFullscreenEnabled: () => shouldRunFeature(FEATURE_FULLSCREEN),
isGameStartAlertEnabled: () => shouldRunFeature(FEATURE_GAME_START_ALERT),
isLobbyCommandsEnabled: () => shouldRunFeature(FEATURE_LOBBY_COMMANDS),
isMobileGrabEnabled: () => shouldRunFeature(FEATURE_MOBILE_GRAB),
isReserveEnabled: () => shouldRunFeature(FEATURE_RESERVE),
shouldRunFeature
};
}
// src/settings/feature-settings-controller.ts
function createFeatureSettingsController(options) {
const featureSettings = loadFeatureSettings();
function isFeatureEnabled(featureKey) {
return isKnownFeature(featureKey) && featureSettings[featureKey] !== false;
}
function shouldRunFeature(featureKey) {
return options.isOnboardingComplete() && isFeatureEnabled(featureKey);
}
function setFeatureEnabled(featureKey, enabled) {
if (!isKnownFeature(featureKey)) {
return;
}
featureSettings[featureKey] = Boolean(enabled);
applySettingsChange([featureKey]);
}
function applySettingsChange(featuresToRefresh = []) {
saveFeatureSettings(featureSettings);
options.onApplyFeatureRootClasses();
for (const featureKey of featuresToRefresh) {
if (!shouldRunFeature(featureKey)) {
options.onDisableFeatureSideEffects(featureKey);
}
}
if (options.isOnboardingComplete()) {
options.onApplyPersistentFeatures();
options.onScheduleUiWork({ force: true, features: true, passes: options.resizeSettlePasses });
}
options.onRenderMenu();
}
function setAllFeatureSettings(nextSettings) {
const changedFeatures = [];
for (const { key } of FEATURE_DEFINITIONS) {
if (featureSettings[key] !== nextSettings[key]) {
changedFeatures.push(key);
}
featureSettings[key] = nextSettings[key];
}
applySettingsChange(changedFeatures);
}
function resetFeatureSettingsToDefaults() {
setAllFeatureSettings(getDefaultFeatureSettings());
}
return {
isFeatureEnabled,
resetFeatureSettingsToDefaults,
setAllFeatureSettings,
setFeatureEnabled,
shouldRunFeature
};
}
// src/dom/settings-menu-dom.ts
function findSettingsContainer() {
return document.querySelector(".items.left");
}
function findChangeControlsItem(container) {
if (!container) {
return null;
}
for (const item of container.querySelectorAll(".item")) {
if ((item.textContent || "").trim() === "Change Controls") {
return item;
}
}
return null;
}
// src/settings/audio-storage.ts
var STEP_PERCENT = 5;
var DEFAULT_GAME_PERCENT = 100;
var DEFAULT_JUKEBOX_PERCENT = 50;
var GAME_VOLUME_KEY = "vm.hitbox.volumePercent";
var JUKEBOX_STATE_KEY = "vm.hitbox.jukeboxState";
function isRecord3(value) {
return typeof value === "object" && value !== null;
}
function clampPercent(value, fallback = 0) {
if (value === null || value === void 0 || typeof value === "string" && value.trim() === "") {
return fallback;
}
const numericValue = Number(value);
if (!Number.isFinite(numericValue)) {
return fallback;
}
return Math.max(0, Math.min(100, Math.round(numericValue / STEP_PERCENT) * STEP_PERCENT));
}
function clampJukeboxPercent(value) {
if (value === null || value === void 0 || typeof value === "string" && value.trim() === "") {
return DEFAULT_JUKEBOX_PERCENT;
}
const numericValue = Number(value);
if (!Number.isFinite(numericValue)) {
return DEFAULT_JUKEBOX_PERCENT;
}
return Math.max(0, Math.min(100, Math.round(numericValue)));
}
function loadGamePercent() {
try {
return clampPercent(localStorage.getItem(GAME_VOLUME_KEY), DEFAULT_GAME_PERCENT);
} catch {
return DEFAULT_GAME_PERCENT;
}
}
function saveGamePercent(percent) {
try {
localStorage.setItem(GAME_VOLUME_KEY, String(percent));
} catch {
}
}
function loadJukeboxState() {
const fallback = { percent: null, muted: false };
try {
const rawState = localStorage.getItem(JUKEBOX_STATE_KEY);
if (!rawState) {
return fallback;
}
const parsed = JSON.parse(rawState);
if (!isRecord3(parsed)) {
return fallback;
}
return {
percent: parsed.percent !== null && parsed.percent !== void 0 ? clampJukeboxPercent(parsed.percent) : null,
muted: Boolean(parsed.muted)
};
} catch {
return fallback;
}
}
function saveJukeboxState(state) {
try {
localStorage.setItem(JUKEBOX_STATE_KEY, JSON.stringify(state));
} catch {
}
}
// src/hitbox/native-access.ts
function isNativeObject(value) {
return typeof value === "object" && value !== null;
}
function isNativeReflectTarget(value) {
return isNativeObject(value) || typeof value === "function";
}
function readNativeProperty(source, property) {
return isNativeObject(source) ? Reflect.get(source, property) : void 0;
}
function readNativeReflectProperty(source, property) {
return isNativeReflectTarget(source) ? Reflect.get(source, property) : void 0;
}
function setNativeReflectProperty(source, property, value) {
return isNativeReflectTarget(source) && Reflect.set(source, property, value);
}
function readNativePath(source, path) {
let current = source;
for (const property of path) {
current = readNativeProperty(current, property);
if (current === void 0 || current === null) {
return current;
}
}
return current;
}
function hasNativeMethod(source, methodName) {
return typeof readNativeProperty(source, methodName) === "function";
}
function callNativeMethod(source, methodName, args = []) {
const method = readNativeProperty(source, methodName);
if (!isNativeObject(source) || typeof method !== "function") {
return { called: false, result: void 0 };
}
return { called: true, result: Reflect.apply(method, source, [...args]) };
}
// src/hitbox/howler-audio-adapter.ts
function isNativeCallable(value) {
return typeof value === "function";
}
function createHowlerGameAudioAdapter(options) {
let originalHowlVolume = null;
let settingGameVolumeInternally = false;
function applyGameVolumeToHowls() {
const howler = readNativeReflectProperty(window, "Howler");
const howls = readNativeReflectProperty(howler, "_howls");
if (!Array.isArray(howls) || !originalHowlVolume) {
return;
}
settingGameVolumeInternally = true;
try {
for (const howl of howls) {
if (!isNativeReflectTarget(howl)) {
continue;
}
const storedBaseVolume = readNativeReflectProperty(howl, "__qolboxBaseVolume");
let baseVolume = typeof storedBaseVolume === "number" ? storedBaseVolume : null;
if (baseVolume === null) {
const initialVolume = Number(readNativeReflectProperty(howl, "_volume"));
baseVolume = Number.isFinite(initialVolume) ? initialVolume : 1;
setNativeReflectProperty(howl, "__qolboxBaseVolume", baseVolume);
}
Reflect.apply(originalHowlVolume, howl, [baseVolume * options.getGameVolumeScalar()]);
}
} finally {
settingGameVolumeInternally = false;
}
}
function hookHowlPrototype() {
if (!options.isAudioEnabled() && !originalHowlVolume) {
return false;
}
const howlConstructor = readNativeReflectProperty(window, "Howl");
const howlPrototype = readNativeReflectProperty(howlConstructor, "prototype");
if (!isNativeReflectTarget(howlPrototype)) {
return false;
}
const currentVolumeMethod = readNativeReflectProperty(howlPrototype, "volume");
let volumePatched = Boolean(
isNativeCallable(currentVolumeMethod) && readNativeReflectProperty(currentVolumeMethod, "__qolboxWrapped") === true
);
if (!volumePatched && isNativeCallable(currentVolumeMethod)) {
let wrappedVolume2 = function(value, ...rest) {
if (arguments.length === 0) {
const baseVolume = readNativeReflectProperty(this, "__qolboxBaseVolume");
if (typeof baseVolume === "number") {
return baseVolume;
}
return Reflect.apply(baseVolumeMethod, this, []);
}
if (typeof value === "number" && !settingGameVolumeInternally) {
setNativeReflectProperty(this, "__qolboxBaseVolume", value);
return Reflect.apply(baseVolumeMethod, this, [value * options.getGameVolumeScalar(), ...rest]);
}
return Reflect.apply(baseVolumeMethod, this, [value, ...rest]);
};
var wrappedVolume = wrappedVolume2;
const baseVolumeMethod = currentVolumeMethod;
originalHowlVolume = baseVolumeMethod;
setNativeReflectProperty(wrappedVolume2, "__qolboxWrapped", true);
setNativeReflectProperty(howlPrototype, "volume", wrappedVolume2);
volumePatched = true;
}
const currentPlayMethod = readNativeReflectProperty(howlPrototype, "play");
const playPatched = isNativeCallable(currentPlayMethod) && readNativeReflectProperty(currentPlayMethod, "__qolboxReserveAudioWrapped");
if (isNativeCallable(currentPlayMethod) && !playPatched) {
let wrappedPlay2 = function(...args) {
if (options.shouldSuppressReserveRetryAudio()) {
return void 0;
}
return Reflect.apply(basePlayMethod, this, args);
};
var wrappedPlay = wrappedPlay2;
const basePlayMethod = currentPlayMethod;
setNativeReflectProperty(wrappedPlay2, "__qolboxReserveAudioWrapped", true);
setNativeReflectProperty(howlPrototype, "play", wrappedPlay2);
}
return volumePatched;
}
return {
applyGameVolumeToHowls,
hookHowlPrototype
};
}
// src/features/audio-levels.ts
var KEYBOARD_PAGE_STEP_MULTIPLIER = 4;
var GAME_CURVE_EXPONENT = 2;
var JUKEBOX_CURVE_EXPONENT = 2;
var JUKEBOX_MIN_ANGLE = -40;
var JUKEBOX_MAX_ANGLE = 220;
var JUKEBOX_ARC_CENTER = 14;
var JUKEBOX_ARC_RADIUS = 12;
var JUKEBOX_ANGLE_EPSILON = 1e-6;
function readBooleanProperty(source, property) {
return readObjectProperty(source, property) === true;
}
function readStringProperty(source, property) {
const value = readObjectProperty(source, property);
return typeof value === "string" ? value : "";
}
function percentToGameScalar(percent) {
return Math.pow(clampPercent(percent, DEFAULT_GAME_PERCENT) / 100, GAME_CURVE_EXPONENT);
}
function percentToJukeboxVolume(percent) {
const clampedPercent = clampJukeboxPercent(percent);
if (clampedPercent <= 0) {
return 0;
}
return Math.max(1, Math.round(Math.pow(clampedPercent / 100, JUKEBOX_CURVE_EXPONENT) * 100));
}
function percentToJukeboxAngle(percent) {
const normalized = clampJukeboxPercent(percent) / 100;
return JUKEBOX_MIN_ANGLE + (JUKEBOX_MAX_ANGLE - JUKEBOX_MIN_ANGLE) * normalized;
}
function getKeyboardPercentTarget(event, currentPercent, stepPercent) {
if (!event || readBooleanProperty(event, "altKey") || readBooleanProperty(event, "ctrlKey") || readBooleanProperty(event, "metaKey")) {
return null;
}
const current = Number.isFinite(Number(currentPercent)) ? Number(currentPercent) : 0;
const step = Math.max(1, Number(stepPercent) || 1);
switch (readStringProperty(event, "key")) {
case "ArrowUp":
case "ArrowRight":
return current + step;
case "ArrowDown":
case "ArrowLeft":
return current - step;
case "PageUp":
return current + step * KEYBOARD_PAGE_STEP_MULTIPLIER;
case "PageDown":
return current - step * KEYBOARD_PAGE_STEP_MULTIPLIER;
case "Home":
return 0;
case "End":
return 100;
default:
return null;
}
}
function angleToJukeboxPercent(angle) {
const numericAngle = Number(angle);
if (!Number.isFinite(numericAngle)) {
return DEFAULT_JUKEBOX_PERCENT;
}
const normalizedAngle = normalizeJukeboxAngle(numericAngle);
const normalized = (Math.min(JUKEBOX_MAX_ANGLE, Math.max(JUKEBOX_MIN_ANGLE, normalizedAngle)) - JUKEBOX_MIN_ANGLE) / (JUKEBOX_MAX_ANGLE - JUKEBOX_MIN_ANGLE);
return clampJukeboxPercent(normalized * 100);
}
function normalizeJukeboxAngle(angle) {
const numericAngle = Number(angle);
if (!Number.isFinite(numericAngle)) {
return percentToJukeboxAngle(DEFAULT_JUKEBOX_PERCENT);
}
const candidates = [numericAngle, numericAngle + 360, numericAngle - 360];
for (const candidate of candidates) {
if (candidate >= JUKEBOX_MIN_ANGLE - JUKEBOX_ANGLE_EPSILON && candidate <= JUKEBOX_MAX_ANGLE + JUKEBOX_ANGLE_EPSILON) {
return Math.max(JUKEBOX_MIN_ANGLE, Math.min(JUKEBOX_MAX_ANGLE, candidate));
}
}
return Math.max(JUKEBOX_MIN_ANGLE, Math.min(JUKEBOX_MAX_ANGLE, numericAngle));
}
function parseJukeboxAngleFromTransform(transform) {
if (typeof transform !== "string" || transform === "" || transform === "none") {
return null;
}
const rotateMatch = transform.match(/rotate\(\s*(-?\d+(?:\.\d+)?)deg\s*\)/i);
if (rotateMatch) {
return normalizeJukeboxAngle(Number(rotateMatch[1]));
}
const matrixMatch = transform.match(/^matrix\(([^)]+)\)$/i);
if (matrixMatch) {
const values = matrixMatch[1].split(",").map((value) => Number(value.trim()));
if (values.length >= 4 && values.every(Number.isFinite)) {
return normalizeJukeboxAngle(Math.atan2(values[1], values[0]) * 180 / Math.PI);
}
}
const matrix3dMatch = transform.match(/^matrix3d\(([^)]+)\)$/i);
if (matrix3dMatch) {
const values = matrix3dMatch[1].split(",").map((value) => Number(value.trim()));
if (values.length >= 16 && values.every(Number.isFinite)) {
return normalizeJukeboxAngle(Math.atan2(values[1], values[0]) * 180 / Math.PI);
}
}
return null;
}
function polarToArcPoint(angle) {
const radians = (angle + 180) * Math.PI / 180;
return {
x: JUKEBOX_ARC_CENTER + JUKEBOX_ARC_RADIUS * Math.cos(radians),
y: JUKEBOX_ARC_CENTER + JUKEBOX_ARC_RADIUS * Math.sin(radians)
};
}
// src/dom/element-guards.ts
function isObjectLike(value) {
return typeof value === "object" && value !== null;
}
function hasDataset(value) {
return value instanceof Element && "dataset" in value && isObjectLike(value.dataset);
}
function isFocusableElement(value) {
return value instanceof Element && "focus" in value && typeof value.focus === "function";
}
function isStyleDeclaration(value) {
return isObjectLike(value) && "getPropertyPriority" in value && typeof value.getPropertyPriority === "function" && "getPropertyValue" in value && typeof value.getPropertyValue === "function" && "removeProperty" in value && typeof value.removeProperty === "function" && "setProperty" in value && typeof value.setProperty === "function";
}
function isStyledElement(value) {
return value instanceof Element && "style" in value && isStyleDeclaration(value.style);
}
function isTabbableElement(value) {
return value instanceof Element && "tabIndex" in value && typeof value.tabIndex === "number";
}
function getCanvasBackingSize(value) {
if (typeof value !== "object" || value === null || !("width" in value) || !("height" in value) || typeof value.width !== "number" || typeof value.height !== "number") {
return null;
}
return {
width: value.width,
height: value.height
};
}
// src/dom/dom-helpers.ts
function isFocusableValue(value) {
return isFocusableElement(value);
}
function isElementVisible(element) {
if (!element || !element.isConnected) {
return false;
}
const style = window.getComputedStyle(element);
if (style.display === "none" || style.visibility === "hidden") {
return false;
}
const rect = element.getBoundingClientRect();
return rect.width > 0 && rect.height > 0;
}
function hasVisibleLayer(selector) {
for (const layer of document.querySelectorAll(selector)) {
if (isElementVisible(layer)) {
return true;
}
}
return false;
}
function escapeMenuText(value) {
return String(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
}
function focusElementWithoutScroll(element) {
if (!isFocusableValue(element)) {
return;
}
try {
element.focus({ preventScroll: true });
} catch {
element.focus();
}
}
function keepOutOfBrowserTabOrder(element) {
if (isTabbableElement(element)) {
element.tabIndex = -1;
}
}
function keepInBrowserTabOrder(element) {
if (isTabbableElement(element)) {
element.tabIndex = 0;
}
}
function matchesElementOrDescendant(node, selector) {
if (!(node instanceof Element)) {
return false;
}
return node.matches(selector) || Boolean(node.closest(selector)) || Boolean(node.querySelector(selector));
}
function mutationTouchesSelector(record, selector) {
const targetElement = record.target instanceof Element ? record.target : record.target.parentElement instanceof Element ? record.target.parentElement : null;
if (matchesElementOrDescendant(targetElement, selector)) {
return true;
}
for (const node of record.addedNodes) {
if (matchesElementOrDescendant(node, selector)) {
return true;
}
}
for (const node of record.removedNodes) {
if (matchesElementOrDescendant(node, selector)) {
return true;
}
}
return false;
}
// src/features/game-volume-menu-item.ts
function isGameVolumeMenuItemElement(value) {
return value instanceof Element && hasDataset(value) && isStyledElement(value) && "cursor" in value.style && "userSelect" in value.style;
}
function findGameVolumeItem() {
const candidates = document.querySelectorAll(".items.left .item, .item");
for (const candidate of candidates) {
if (/^Volume:\s*\d+%$/.test(candidate.textContent?.trim() || "") && isGameVolumeMenuItemElement(candidate)) {
return candidate;
}
}
return null;
}
function updateGameVolumeItemView(item, gamePercent) {
item.textContent = `Volume: ${gamePercent}%`;
item.setAttribute("title", "Scroll or use arrow keys to adjust by 5%, left-click up, right-click down");
item.style.cursor = "ns-resize";
item.style.userSelect = "none";
keepOutOfBrowserTabOrder(item);
item.setAttribute("role", "slider");
item.setAttribute("aria-label", "Game volume");
item.setAttribute("aria-valuemin", "0");
item.setAttribute("aria-valuemax", "100");
item.setAttribute("aria-valuenow", String(gamePercent));
item.setAttribute("aria-valuetext", `${gamePercent}%`);
}
// src/features/game-volume-menu-control.ts
function readNumberProperty(source, property) {
const value = readObjectProperty(source, property);
return typeof value === "number" ? value : Number(value);
}
function createGameVolumeMenuController(options) {
let currentGameMenuItem = null;
function updateGameVolumeText() {
if (!options.isAudioEnabled()) {
return;
}
if (!currentGameMenuItem || !currentGameMenuItem.isConnected) {
currentGameMenuItem = findGameVolumeItem();
}
if (!currentGameMenuItem) {
return;
}
const gamePercent = options.getGamePercent();
updateGameVolumeItemView(currentGameMenuItem, gamePercent);
}
function patchGameVolumeMenu() {
if (!options.isAudioEnabled()) {
return false;
}
const item = findGameVolumeItem();
if (!item) {
return false;
}
currentGameMenuItem = item;
if (!item.dataset.qolboxGameVolumePatched) {
item.dataset.qolboxGameVolumePatched = "true";
item.addEventListener(
"click",
(event) => {
if (!options.isAudioEnabled()) {
return;
}
event.preventDefault();
event.stopImmediatePropagation();
focusElementWithoutScroll(item);
options.setGamePercent(options.getGamePercent() + options.stepPercent);
},
true
);
item.addEventListener(
"contextmenu",
(event) => {
if (!options.isAudioEnabled()) {
return;
}
event.preventDefault();
event.stopImmediatePropagation();
focusElementWithoutScroll(item);
options.setGamePercent(options.getGamePercent() - options.stepPercent);
},
true
);
item.addEventListener(
"wheel",
(event) => {
if (!options.isAudioEnabled()) {
return;
}
event.preventDefault();
event.stopImmediatePropagation();
focusElementWithoutScroll(item);
options.setGamePercent(
options.getGamePercent() + (readNumberProperty(event, "deltaY") < 0 ? options.stepPercent : -options.stepPercent)
);
},
{ passive: false, capture: true }
);
item.addEventListener(
"keydown",
(event) => {
if (!options.isAudioEnabled()) {
return;
}
const nextPercent = getKeyboardPercentTarget(
event,
options.getGamePercent(),
options.stepPercent
);
if (nextPercent === null) {
return;
}
event.preventDefault();
event.stopImmediatePropagation();
options.setGamePercent(nextPercent);
},
true
);
}
updateGameVolumeText();
return true;
}
return {
patchGameVolumeMenu,
updateGameVolumeText
};
}
// src/features/game-volume-control.ts
function createGameVolumeController(options) {
let gamePercent = loadGamePercent();
const howlerAudio = createHowlerGameAudioAdapter({
getGameVolumeScalar: () => options.isAudioEnabled() ? percentToGameScalar(gamePercent) : 1,
isAudioEnabled: options.isAudioEnabled,
shouldSuppressReserveRetryAudio: options.isReserveRetryAudioSuppressed
});
const menuController = createGameVolumeMenuController({
stepPercent: STEP_PERCENT,
getGamePercent: () => gamePercent,
isAudioEnabled: options.isAudioEnabled,
setGamePercent
});
function updateGameVolumeText() {
menuController.updateGameVolumeText();
}
function applyGameVolume() {
updateGameVolumeText();
howlerAudio.applyGameVolumeToHowls();
}
function setGamePercent(nextPercent) {
gamePercent = clampPercent(nextPercent, DEFAULT_GAME_PERCENT);
saveGamePercent(gamePercent);
applyGameVolume();
}
function patchGameVolumeMenu() {
return menuController.patchGameVolumeMenu();
}
function shouldSuppressReserveRetryAudio() {
return options.isReserveRetryAudioSuppressed();
}
function hookHowlPrototype() {
const volumePatched = howlerAudio.hookHowlPrototype();
if (volumePatched) {
applyGameVolume();
}
return volumePatched;
}
return {
applyGameVolume,
hookHowlPrototype,
patchGameVolumeMenu,
setGamePercent,
shouldSuppressReserveRetryAudio
};
}
// src/hitbox/youtube-player-native.ts
function isRecord4(value) {
return typeof value === "object" && value !== null;
}
function isNativeCallable2(value) {
return typeof value === "function";
}
function isConstructableCallable(value) {
return typeof value === "function";
}
function readBooleanProperty2(source, property) {
return readObjectProperty(source, property) === true;
}
// src/hitbox/youtube-player-options-wrapper.ts
function wrapYouTubePlayerOptions(args, options) {
const wrappedArgs = Array.from(args);
const optionsArg = wrappedArgs[1];
if (!isRecord4(optionsArg)) {
return wrappedArgs;
}
const events = isRecord4(optionsArg.events) ? optionsArg.events : {};
const originalOnReady = events.onReady;
if (readBooleanProperty2(originalOnReady, "__qolboxWrapped")) {
return wrappedArgs;
}
const wrappedEvents = {
...events,
onReady(event, ...readyArgs) {
const player = readObjectProperty(event, "target") || options.getPlayer();
options.onPlayerReady(player);
options.onPlayerStateNeeded(player || options.getPlayer());
try {
return isNativeCallable2(originalOnReady) ? Reflect.apply(originalOnReady, this, [event, ...readyArgs]) : void 0;
} finally {
window.setTimeout(() => {
options.onPlayerStateNeeded(player || options.getPlayer());
}, 0);
}
}
};
setObjectProperty(wrappedEvents.onReady, "__qolboxWrapped", true);
setObjectProperty(wrappedEvents.onReady, "__qolboxOriginal", originalOnReady);
wrappedArgs[1] = {
...optionsArg,
events: wrappedEvents
};
return wrappedArgs;
}
// src/hitbox/youtube-player-adapter.ts
function createYouTubeJukeboxAdapter(options) {
let trackedPlayers = /* @__PURE__ */ new Set();
let hookInstalled = false;
let retryTimer = 0;
let retryCount = 0;
let readyCallbackHookInstalled = false;
function trackPlayer(player) {
if (!player || !isNativeCallable2(readObjectProperty(player, "setVolume"))) {
return;
}
trackedPlayers.add(player);
}
function discoverPlayers() {
const yt = readObjectProperty(window, "YT");
const getPlayer = readObjectProperty(yt, "get");
if (!isNativeCallable2(getPlayer)) {
return;
}
for (const candidate of document.querySelectorAll("#ytContainer [id], #ytContainer iframe[id]")) {
if (!candidate.id) {
continue;
}
try {
const player = Reflect.apply(getPlayer, yt, [candidate.id]);
trackPlayer(player);
} catch {
}
}
}
function applyPlayerState(player) {
if (!options.isEnabled()) {
return;
}
const setVolume = readObjectProperty(player, "setVolume");
if (!player || !isNativeCallable2(setVolume)) {
trackedPlayers.delete(player);
return;
}
try {
const setPlaybackRate = readObjectProperty(player, "setPlaybackRate");
const getPlaybackRate = readObjectProperty(player, "getPlaybackRate");
const playbackRate = isNativeCallable2(getPlaybackRate) ? Reflect.apply(getPlaybackRate, player, []) : null;
if (isNativeCallable2(setPlaybackRate) && playbackRate !== 1) {
Reflect.apply(setPlaybackRate, player, [1]);
}
const getVolume = readObjectProperty(player, "getVolume");
const getMuted = readObjectProperty(player, "isMuted");
const currentVolume = isNativeCallable2(getVolume) ? Reflect.apply(getVolume, player, []) : null;
const currentlyMuted = isNativeCallable2(getMuted) ? Reflect.apply(getMuted, player, []) : null;
if (options.isMuted()) {
if (currentVolume !== 0) {
Reflect.apply(setVolume, player, [0]);
}
const mute = readObjectProperty(player, "mute");
if (isNativeCallable2(mute) && currentlyMuted !== true) {
Reflect.apply(mute, player, []);
}
} else {
const targetVolume = options.getVolume();
if (currentVolume !== targetVolume) {
Reflect.apply(setVolume, player, [targetVolume]);
}
const unMute = readObjectProperty(player, "unMute");
if (isNativeCallable2(unMute) && currentlyMuted === true) {
Reflect.apply(unMute, player, []);
}
}
} catch {
trackedPlayers.delete(player);
}
}
function applyToTrackedPlayers() {
if (!options.isEnabled()) {
return;
}
discoverPlayers();
for (const player of Array.from(trackedPlayers)) {
applyPlayerState(player);
}
}
function scheduleRetry() {
if (!options.isEnabled() || hookInstalled || retryTimer || retryCount >= options.maxRetries) {
return;
}
retryCount += 1;
retryTimer = window.setTimeout(() => {
retryTimer = 0;
hookPlayerConstructor();
options.onPlayerStateNeeded();
}, options.retryDelayMs);
}
function wrapReadyCallback(callback) {
if (!isNativeCallable2(callback) || readBooleanProperty2(callback, "__qolboxWrapped")) {
return callback;
}
const nativeCallback = callback;
function wrappedYouTubeReadyCallback(...args) {
if (options.isEnabled()) {
hookPlayerConstructor();
options.onPlayerStateNeeded();
}
try {
return Reflect.apply(nativeCallback, this, args);
} finally {
if (options.isEnabled()) {
hookPlayerConstructor();
window.setTimeout(options.onPlayerStateNeeded, 0);
}
}
}
setObjectProperty(wrappedYouTubeReadyCallback, "__qolboxWrapped", true);
setObjectProperty(wrappedYouTubeReadyCallback, "__qolboxOriginal", callback);
return wrappedYouTubeReadyCallback;
}
function installReadyCallbackHook() {
if (!options.isEnabled() || readyCallbackHookInstalled) {
return;
}
const descriptor = Object.getOwnPropertyDescriptor(window, "onYouTubeIframeAPIReady");
if (descriptor && (!descriptor.configurable || descriptor.get || descriptor.set)) {
return;
}
readyCallbackHookInstalled = true;
let readyCallback = wrapReadyCallback(
descriptor ? descriptor.value : readObjectProperty(window, "onYouTubeIframeAPIReady")
);
try {
Object.defineProperty(window, "onYouTubeIframeAPIReady", {
configurable: true,
enumerable: true,
get() {
return readyCallback;
},
set(value) {
readyCallback = wrapReadyCallback(value);
}
});
} catch {
readyCallbackHookInstalled = false;
}
}
function hookPlayerConstructor() {
if (!options.isEnabled()) {
return false;
}
installReadyCallbackHook();
const yt = readObjectProperty(window, "YT");
const playerConstructor = readObjectProperty(yt, "Player");
if (!isConstructableCallable(playerConstructor)) {
scheduleRetry();
return false;
}
if (retryTimer) {
window.clearTimeout(retryTimer);
retryTimer = 0;
}
retryCount = 0;
if (hookInstalled || readBooleanProperty2(playerConstructor, "__qolboxWrapped")) {
hookInstalled = true;
discoverPlayers();
return true;
}
const OriginalPlayer = playerConstructor;
function WrappedPlayer(...args) {
let instance = null;
const wrappedArgs = wrapYouTubePlayerOptions(args, {
getPlayer: () => instance,
onPlayerReady: trackPlayer,
onPlayerStateNeeded: applyPlayerState
});
instance = new OriginalPlayer(...wrappedArgs);
trackPlayer(instance);
window.setTimeout(() => {
applyPlayerState(instance);
}, 0);
return instance;
}
Object.setPrototypeOf(WrappedPlayer, OriginalPlayer);
setObjectProperty(WrappedPlayer, "prototype", readObjectProperty(OriginalPlayer, "prototype"));
setObjectProperty(WrappedPlayer, "__qolboxWrapped", true);
setObjectProperty(yt, "Player", WrappedPlayer);
hookInstalled = true;
discoverPlayers();
return true;
}
return {
applyToTrackedPlayers,
hookPlayerConstructor,
installReadyCallbackHook
};
}
// src/features/jukebox-dom-helpers.ts
function readJukeboxProperty(source, property) {
return readObjectProperty(source, property);
}
function setJukeboxProperty(source, property, value) {
return setObjectProperty(source, property, value);
}
function readJukeboxNumberProperty(source, property) {
const value = readJukeboxProperty(source, property);
return typeof value === "number" ? value : Number(value);
}
function readJukeboxBooleanProperty(source, property) {
return readJukeboxProperty(source, property) === true;
}
function isNativeCallable3(value) {
return typeof value === "function";
}
function isJukeboxStyleDatasetElement(value) {
return value instanceof Element && typeof readJukeboxProperty(value, "dataset") === "object" && typeof readJukeboxProperty(value, "style") === "object";
}
function requestJukeboxPointerCapture(knob, event) {
const setPointerCapture = readJukeboxProperty(knob, "setPointerCapture");
const pointerId = readJukeboxProperty(event, "pointerId");
if (!isNativeCallable3(setPointerCapture) || pointerId === void 0) {
return;
}
try {
Reflect.apply(setPointerCapture, knob, [pointerId]);
} catch {
}
}
// src/features/chat-input-elements.ts
function hasEditableChatValue(value) {
return value instanceof Element && "value" in value && typeof value.value === "string";
}
function canBlur(value) {
return typeof value === "object" && value !== null && "blur" in value && typeof value.blur === "function";
}
function isChatInputElement(element, selector) {
return element instanceof Element && element.matches(selector);
}
function isLobbyChatInputElement(element, selector) {
return element instanceof Element && element.matches(selector);
}
function getActiveChatInputElement(target, selector) {
if (isChatInputElement(target, selector)) {
return target;
}
if (target instanceof Element) {
const closestChatInput = target.closest(selector);
if (isChatInputElement(closestChatInput, selector)) {
return closestChatInput;
}
}
return document.querySelector(".inGameChat .input:focus, .lobbyContainer .chatBox .input:focus");
}
// src/features/chat-keyboard-events.ts
function readTextProperty(source, property) {
const value = readObjectProperty(source, property);
return typeof value === "string" ? value : void 0;
}
function isEscapeKey(event) {
const key = readTextProperty(event, "key");
const code = readTextProperty(event, "code");
return key === "Escape" || key === "Esc" || code === "Escape";
}
function isTabKey(event) {
const key = readTextProperty(event, "key");
const code = readTextProperty(event, "code");
return key === "Tab" || code === "Tab";
}
function isEnterKey(event) {
return readTextProperty(event, "key") === "Enter";
}
// src/features/chat-input-controls.ts
function createChatInputController(options) {
let escapeHooksInstalled = false;
let commandAliasHooksInstalled = false;
let suppressEscapeKeyUntil = 0;
function isChatInput(element) {
return isChatInputElement(element, options.chatInputSelector);
}
function isLobbyChatInput(element) {
return isLobbyChatInputElement(element, options.lobbyChatInputSelector);
}
function getActiveChatInput(target = document.activeElement) {
return getActiveChatInputElement(target, options.chatInputSelector);
}
function restoreLobbyChatPrompt(input) {
if (!isLobbyChatInput(input)) {
return;
}
const chatBox = input.closest(".lobbyContainer .chatBox");
const instruction = chatBox ? chatBox.querySelector(".lowerInstruction") : null;
if (instruction) {
instruction.style.visibility = "inherit";
if (!(instruction.textContent || "").trim()) {
instruction.textContent = options.isTouchLobbyChatPrompt() ? options.touchLobbyChatPrompt : options.desktopLobbyChatPrompt;
}
}
if (!options.isTouchLobbyChatPrompt() && isStyledElement(input)) {
input.style.pointerEvents = "none";
}
}
function closeChatInput(input) {
if (!options.isChatFeatureEnabled() || !isChatInput(input) || !hasEditableChatValue(input) || !canBlur(input)) {
return false;
}
const closingLobbyChat = isLobbyChatInput(input);
input.value = "";
input.dispatchEvent(new Event("input", { bubbles: true }));
input.blur();
input.classList.remove("bgActive");
if (closingLobbyChat) {
restoreLobbyChatPrompt(input);
} else {
options.focusActiveRenderCanvas();
}
return true;
}
function handleChatEscape(event) {
if (!options.isChatFeatureEnabled() || !isEscapeKey(event)) {
return;
}
const input = getActiveChatInput(event.target);
const suppressingKeyup = event.type === "keyup" && Date.now() < suppressEscapeKeyUntil;
if (!input && !suppressingKeyup) {
return;
}
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
if (event.type === "keydown" && input) {
suppressEscapeKeyUntil = Date.now() + 500;
closeChatInput(input);
}
}
function installChatEscapeHooks() {
if (escapeHooksInstalled) {
return;
}
escapeHooksInstalled = true;
window.addEventListener("keydown", handleChatEscape, true);
window.addEventListener("keyup", handleChatEscape, true);
document.addEventListener("keydown", handleChatEscape, true);
document.addEventListener("keyup", handleChatEscape, true);
}
function handleChatCommandAliasKeydown(event) {
if (!options.areLobbyCommandsEnabled() || !isEnterKey(event)) {
return;
}
const input = event.target;
if (!isChatInput(input)) {
return;
}
if (hasEditableChatValue(input)) {
input.value = options.expandNativeChatAlias(input.value);
}
}
function installChatCommandAliasHooks() {
if (commandAliasHooksInstalled) {
return;
}
commandAliasHooksInstalled = true;
document.addEventListener("keydown", handleChatCommandAliasKeydown, true);
}
function patchChatTabOrder() {
if (!options.isChatFeatureEnabled()) {
return;
}
if (!document.querySelector(".inGameChat, .lobbyContainer")) {
return;
}
for (const input of document.querySelectorAll(options.chatInputSelector)) {
keepOutOfBrowserTabOrder(input);
}
}
return {
closeChatInput,
getActiveChatInput,
installChatCommandAliasHooks,
installChatEscapeHooks,
isChatInput,
patchChatTabOrder,
restoreLobbyChatPrompt
};
}
// src/features/jukebox-keyboard-focus.ts
function readProperty(source, property) {
return readObjectProperty(source, property);
}
function isNativeCallable4(value) {
return typeof value === "function";
}
function isStyleElement(value) {
return value instanceof Element && typeof readProperty(value, "style") === "object";
}
function isStyleDatasetElement(value) {
return value instanceof Element && typeof readProperty(value, "dataset") === "object" && typeof readProperty(value, "style") === "object";
}
function createJukeboxKeyboardFocusController(options) {
let tabFocusHooksInstalled = false;
function setJukeboxBottom(jukebox, bottom) {
if (isStyleElement(jukebox)) {
jukebox.style.bottom = bottom;
}
}
function openJukeboxFromKeyboardFocus(jukebox) {
if (!options.isAudioEnabled() || !jukebox) {
return;
}
options.resetBrowserScroll();
setJukeboxBottom(jukebox, "0px");
const onMouseEnter = readProperty(jukebox, "onmouseenter");
if (isNativeCallable4(onMouseEnter)) {
Reflect.apply(onMouseEnter, jukebox, []);
} else {
setJukeboxBottom(jukebox, "0px");
}
options.scheduleUiWork({ force: true, passes: options.resizeSettlePasses });
}
function closeJukeboxFromKeyboardFocus(jukebox, nextFocusTarget) {
if (!options.isAudioEnabled() || !jukebox || nextFocusTarget instanceof Element && jukebox.contains(nextFocusTarget) || jukebox.matches(":hover")) {
return;
}
const onMouseLeave = readProperty(jukebox, "onmouseleave");
if (isNativeCallable4(onMouseLeave)) {
Reflect.apply(onMouseLeave, jukebox, []);
} else {
setJukeboxBottom(jukebox, "-50px");
}
}
function focusJukeboxKnobFromTab(knob) {
if (!options.isAudioEnabled()) {
return false;
}
const jukebox = knob?.closest(".jukebox") || null;
if (!jukebox) {
return false;
}
openJukeboxFromKeyboardFocus(jukebox);
focusElementWithoutScroll(knob);
options.resetBrowserScroll();
return true;
}
function isGameplayTabFocusContext(target, knob) {
const activeCanvas = options.getActiveRenderCanvas();
return target === window || target === document || target === document.body || target === document.documentElement || target === activeCanvas || target === knob;
}
function handleGameplayTabFocus(event) {
if (!options.isAudioEnabled() || !isTabKey(event) || event.altKey || event.ctrlKey || event.metaKey || options.isChatInput(event.target) || options.getActiveRenderMode() !== "gameplay") {
return;
}
const knob = options.findJukeboxKnob();
const jukebox = knob?.closest(".jukebox") || null;
if (!knob || !jukebox || !isElementVisible(jukebox) || !isGameplayTabFocusContext(event.target, knob)) {
return;
}
event.preventDefault();
if (document.activeElement === knob) {
options.focusActiveRenderCanvas();
closeJukeboxFromKeyboardFocus(jukebox, document.activeElement);
return;
}
focusJukeboxKnobFromTab(knob);
}
function installTabFocusHooks() {
if (tabFocusHooksInstalled) {
return;
}
tabFocusHooksInstalled = true;
window.addEventListener("keydown", handleGameplayTabFocus, true);
}
function patchJukeboxKeyboardFocus(knob) {
if (!options.isAudioEnabled()) {
return;
}
const jukebox = knob?.closest(".jukebox") || null;
if (!isStyleDatasetElement(jukebox) || jukebox.dataset.qolboxKeyboardFocusPatched) {
return;
}
jukebox.dataset.qolboxKeyboardFocusPatched = "true";
jukebox.addEventListener("focusin", () => openJukeboxFromKeyboardFocus(jukebox), true);
jukebox.addEventListener(
"focusout",
(event) => closeJukeboxFromKeyboardFocus(jukebox, readProperty(event, "relatedTarget")),
true
);
}
return {
handleGameplayTabFocus,
installTabFocusHooks,
patchJukeboxKeyboardFocus
};
}
// src/features/jukebox-knob-interaction.ts
function createJukeboxKnobInteractionController(options) {
let activeKnobDrag = null;
function isKnobDragActive() {
return Boolean(activeKnobDrag);
}
function getKnobPercentFromPointer(event) {
if (!activeKnobDrag) {
return DEFAULT_JUKEBOX_PERCENT;
}
const deltaY = activeKnobDrag.startY - readJukeboxNumberProperty(event, "clientY");
return clampJukeboxPercent(activeKnobDrag.startPercent + deltaY * options.dragSensitivity);
}
function onKnobPointerMove(event) {
if (!options.isAudioEnabled() || !activeKnobDrag) {
return;
}
event.preventDefault();
options.setJukeboxPercent(getKnobPercentFromPointer(event));
}
function endKnobDrag() {
activeKnobDrag = null;
}
function patchGlobalKnobListeners() {
if (readJukeboxBooleanProperty(window, "__qolboxJukeboxGlobalsPatched")) {
return;
}
setJukeboxProperty(window, "__qolboxJukeboxGlobalsPatched", true);
window.addEventListener("pointermove", onKnobPointerMove, true);
window.addEventListener("mousemove", onKnobPointerMove, true);
window.addEventListener("pointerup", endKnobDrag, true);
window.addEventListener("mouseup", endKnobDrag, true);
window.addEventListener("blur", endKnobDrag, true);
}
function patchJukeboxKnobInteraction(knob) {
patchGlobalKnobListeners();
if (knob.dataset.qolboxJukeboxPatched) {
return;
}
knob.dataset.qolboxJukeboxPatched = "true";
knob.setAttribute("title", "Scroll, drag, or use arrow keys to adjust the jukebox volume");
knob.style.touchAction = "none";
knob.addEventListener(
"pointerdown",
(event) => {
if (!options.isAudioEnabled()) {
return;
}
event.preventDefault();
event.stopPropagation();
focusElementWithoutScroll(knob);
requestJukeboxPointerCapture(knob, event);
if (options.unmuteJukeboxIfMuted()) {
options.updateJukeboxMenuItem();
options.applyJukeboxState();
}
activeKnobDrag = {
startY: readJukeboxNumberProperty(event, "clientY"),
startPercent: options.getJukeboxPercent() ?? DEFAULT_JUKEBOX_PERCENT
};
onKnobPointerMove(event);
},
true
);
knob.addEventListener(
"wheel",
(event) => {
if (!options.isAudioEnabled()) {
return;
}
event.preventDefault();
event.stopPropagation();
focusElementWithoutScroll(knob);
options.ensureJukeboxPercent(knob);
const currentPercent = options.isJukeboxMuted() ? 0 : options.getJukeboxPercent();
options.setJukeboxPercent(
(currentPercent ?? DEFAULT_JUKEBOX_PERCENT) + (readJukeboxNumberProperty(event, "deltaY") < 0 ? options.wheelStep : -options.wheelStep)
);
},
{ passive: false }
);
knob.addEventListener(
"keydown",
(event) => {
if (!options.isAudioEnabled()) {
return;
}
const currentPercent = options.isJukeboxMuted() ? 0 : options.getEffectiveJukeboxPercent();
const nextPercent = getKeyboardPercentTarget(event, currentPercent, options.wheelStep);
if (nextPercent === null) {
return;
}
event.preventDefault();
event.stopPropagation();
options.ensureJukeboxPercent(knob);
options.setJukeboxPercent(nextPercent);
},
true
);
}
return {
isKnobDragActive,
patchJukeboxKnobInteraction
};
}
// src/features/jukebox-menu-control.ts
function createJukeboxMenuController(options) {
let currentJukeboxMenuItem = null;
function updateJukeboxMenuItem() {
if (!currentJukeboxMenuItem || !currentJukeboxMenuItem.isConnected) {
return;
}
currentJukeboxMenuItem.textContent = options.getLabel();
currentJukeboxMenuItem.setAttribute("title", "Remember the lobby radio mute state");
}
function patchJukeboxMenu() {
if (!options.isAudioEnabled()) {
return false;
}
const container = options.findSettingsContainer();
if (!container) {
return false;
}
let item = container.querySelector('.item[data-qolbox-jukebox-menu="true"]');
if (!item) {
const createdItem = document.createElement("div");
createdItem.className = "item";
createdItem.dataset.qolboxJukeboxMenu = "true";
createdItem.addEventListener(
"click",
(event) => {
event.preventDefault();
event.stopImmediatePropagation();
options.onToggleMute();
},
true
);
const beforeItem = options.findChangeControlsItem(container);
if (beforeItem) {
container.insertBefore(createdItem, beforeItem);
} else {
container.appendChild(createdItem);
}
item = createdItem;
}
currentJukeboxMenuItem = item;
updateJukeboxMenuItem();
return true;
}
function removeJukeboxMenuItem() {
if (currentJukeboxMenuItem && currentJukeboxMenuItem.isConnected) {
currentJukeboxMenuItem.remove();
}
currentJukeboxMenuItem = null;
}
return {
patchJukeboxMenu,
removeJukeboxMenuItem,
updateJukeboxMenuItem
};
}
// src/features/jukebox-knob-view.ts
function isStyleElement2(value) {
return value instanceof Element && typeof readObjectProperty(value, "style") === "object";
}
function findJukeboxKnob() {
return document.querySelector(".jukebox .knob.volumeContainer");
}
function readJukeboxPercentFromKnob(knob) {
const bar = knob ? knob.querySelector(".barSVG") : null;
if (!isStyleElement2(bar)) {
return null;
}
const inlineAngle = parseJukeboxAngleFromTransform(bar.style.transform);
if (inlineAngle !== null) {
return angleToJukeboxPercent(inlineAngle);
}
const computedAngle = parseJukeboxAngleFromTransform(window.getComputedStyle(bar).transform);
if (computedAngle !== null) {
return angleToJukeboxPercent(computedAngle);
}
return null;
}
function updateJukeboxKnobAccessibility(knob, visualPercent, state) {
if (!knob) {
return;
}
const effectivePercent = state.muted ? 0 : clampJukeboxPercent(visualPercent ?? state.percent ?? DEFAULT_JUKEBOX_PERCENT);
knob.setAttribute("aria-label", "Jukebox volume");
knob.setAttribute("aria-orientation", "vertical");
knob.setAttribute("aria-valuemin", "0");
knob.setAttribute("aria-valuemax", "100");
knob.setAttribute("aria-valuenow", String(effectivePercent));
knob.setAttribute("aria-valuetext", state.muted ? `Muted (${effectivePercent}%)` : `${effectivePercent}%`);
knob.setAttribute("role", "slider");
keepInBrowserTabOrder(knob);
}
function setJukeboxKnobVisual(knob, visualPercent, state) {
if (!knob) {
return;
}
const angle = percentToJukeboxAngle(visualPercent ?? DEFAULT_JUKEBOX_PERCENT);
const bar = knob.querySelector(".barSVG");
const arcPath = knob.querySelector(".arcSVG path");
if (isStyleElement2(bar)) {
bar.style.transform = `rotate(${angle}deg)`;
}
if (arcPath) {
const startPoint = polarToArcPoint(JUKEBOX_MIN_ANGLE);
const endPoint = polarToArcPoint(angle);
const sweepDegrees = Math.max(0, angle - JUKEBOX_MIN_ANGLE);
const largeArcFlag = sweepDegrees > 180 ? 1 : 0;
arcPath.setAttribute(
"d",
`M ${startPoint.x} ${startPoint.y} A ${JUKEBOX_ARC_RADIUS} ${JUKEBOX_ARC_RADIUS} 0 ${largeArcFlag} 1 ${endPoint.x} ${endPoint.y}`
);
}
updateJukeboxKnobAccessibility(knob, visualPercent, state);
}
// src/features/jukebox-state.ts
function createJukeboxStateController() {
let state = loadJukeboxState();
function persistState() {
saveJukeboxState(state);
}
function getEffectivePercent() {
return clampJukeboxPercent(state.percent ?? DEFAULT_JUKEBOX_PERCENT);
}
function ensurePercent(readPercent) {
if (state.percent !== null) {
return;
}
state.percent = readPercent() ?? DEFAULT_JUKEBOX_PERCENT;
persistState();
}
function setPercent(nextPercent) {
state.percent = clampJukeboxPercent(nextPercent);
state.muted = false;
persistState();
}
function toggleMuted() {
state.muted = !state.muted;
persistState();
}
function unmuteIfMuted() {
if (!state.muted) {
return false;
}
state.muted = false;
persistState();
return true;
}
function setState(nextState) {
state = {
muted: Boolean(nextState.muted),
percent: nextState.percent ?? null
};
}
function getState() {
return state;
}
function getPercent() {
return state.percent;
}
function isMuted() {
return state.muted;
}
function getMenuLabel() {
return state.muted ? "Unmute Jukebox" : "Mute Jukebox";
}
return {
ensurePercent,
getEffectivePercent,
getMenuLabel,
getPercent,
getState,
isMuted,
setPercent,
setState,
toggleMuted,
unmuteIfMuted
};
}
// src/features/jukebox-control.ts
function createJukeboxController(options) {
const jukeboxState = createJukeboxStateController();
const youTubeAdapter = createYouTubeJukeboxAdapter({
getVolume: () => percentToJukeboxVolume(getEffectiveJukeboxPercent()),
isEnabled: options.isAudioEnabled,
isMuted: () => jukeboxState.isMuted(),
maxRetries: options.youTubeHookMaxRetries,
onPlayerStateNeeded: () => applyJukeboxState(),
retryDelayMs: options.youTubeHookRetryDelayMs
});
const keyboardFocus = createJukeboxKeyboardFocusController({
resizeSettlePasses: options.resizeSettlePasses,
findJukeboxKnob,
focusActiveRenderCanvas: options.focusActiveRenderCanvas,
getActiveRenderCanvas: options.getActiveRenderCanvas,
getActiveRenderMode: options.getActiveRenderMode,
isAudioEnabled: options.isAudioEnabled,
isChatInput: options.isChatInput,
resetBrowserScroll: options.resetBrowserScroll,
scheduleUiWork: options.scheduleUiWork
});
const menuController = createJukeboxMenuController({
findChangeControlsItem: options.findChangeControlsItem,
findSettingsContainer: options.findSettingsContainer,
getLabel: getJukeboxMenuLabel,
isAudioEnabled: options.isAudioEnabled,
onToggleMute: toggleJukeboxMute
});
const knobInteraction = createJukeboxKnobInteractionController({
dragSensitivity: options.jukeboxDragSensitivity,
wheelStep: options.jukeboxWheelStep,
applyJukeboxState,
ensureJukeboxPercent,
getEffectiveJukeboxPercent,
getJukeboxPercent: () => jukeboxState.getPercent(),
isAudioEnabled: options.isAudioEnabled,
isJukeboxMuted: () => jukeboxState.isMuted(),
setJukeboxPercent,
unmuteJukeboxIfMuted: () => jukeboxState.unmuteIfMuted(),
updateJukeboxMenuItem
});
function installTabFocusHooks() {
keyboardFocus.installTabFocusHooks();
}
function patchJukeboxKeyboardFocus(knob) {
keyboardFocus.patchJukeboxKeyboardFocus(knob);
}
function getJukeboxMenuLabel() {
return jukeboxState.getMenuLabel();
}
function updateJukeboxMenuItem() {
menuController.updateJukeboxMenuItem();
}
function patchJukeboxMenu() {
return menuController.patchJukeboxMenu();
}
function getEffectiveJukeboxPercent() {
return jukeboxState.getEffectivePercent();
}
function ensureJukeboxPercent(knob) {
if (!knob) {
return;
}
jukeboxState.ensurePercent(() => readJukeboxPercentFromKnob(knob));
}
function applyJukeboxStateToKnob(knob) {
if (!options.isAudioEnabled() || !knob || knobInteraction.isKnobDragActive()) {
return;
}
ensureJukeboxPercent(knob);
setJukeboxKnobVisual(knob, jukeboxState.isMuted() ? 0 : jukeboxState.getPercent(), jukeboxState.getState());
}
function applyJukeboxState() {
if (!options.isAudioEnabled()) {
return;
}
const knob = findJukeboxKnob();
applyJukeboxStateToKnob(knob);
ensureJukeboxPercent(knob);
youTubeAdapter.applyToTrackedPlayers();
}
function installYouTubeReadyCallbackHook() {
youTubeAdapter.installReadyCallbackHook();
}
function hookYouTubePlayer() {
return youTubeAdapter.hookPlayerConstructor();
}
function setJukeboxPercent(nextPercent) {
if (!options.isAudioEnabled()) {
return;
}
jukeboxState.setPercent(nextPercent);
updateJukeboxMenuItem();
setJukeboxKnobVisual(findJukeboxKnob(), jukeboxState.getPercent(), jukeboxState.getState());
applyJukeboxState();
}
function toggleJukeboxMute() {
if (!options.isAudioEnabled()) {
return;
}
ensureJukeboxPercent(findJukeboxKnob());
jukeboxState.toggleMuted();
updateJukeboxMenuItem();
applyJukeboxState();
}
function patchJukeboxKnob() {
if (!options.isAudioEnabled()) {
return false;
}
const knob = findJukeboxKnob();
if (!isJukeboxStyleDatasetElement(knob)) {
return false;
}
ensureJukeboxPercent(knob);
applyJukeboxStateToKnob(knob);
patchJukeboxKeyboardFocus(knob);
knobInteraction.patchJukeboxKnobInteraction(knob);
return true;
}
function removeJukeboxMenuItem() {
menuController.removeJukeboxMenuItem();
}
function setJukeboxState(nextState) {
jukeboxState.setState(nextState);
}
return {
applyJukeboxState,
findJukeboxKnob,
getEffectiveJukeboxPercent,
handleGameplayTabFocus: keyboardFocus.handleGameplayTabFocus,
hookYouTubePlayer,
installTabFocusHooks,
installYouTubeReadyCallbackHook,
patchJukeboxKeyboardFocus,
patchJukeboxKnob,
patchJukeboxMenu,
removeJukeboxMenuItem,
setJukeboxState
};
}
// src/hitbox/lobby-music-adapter.ts
function isNativeCallable5(value) {
return typeof value === "function";
}
function getNativeLobbyMusicController() {
const game = readNativeReflectProperty(window, "a8");
const controller = readNativeReflectProperty(game, "cR");
return isNativeReflectTarget(controller) ? controller : null;
}
function stopNativeLobbyMusic(controller = getNativeLobbyMusicController()) {
const stop = readNativeReflectProperty(controller, "stop");
if (!isNativeCallable5(stop)) {
return false;
}
try {
Reflect.apply(stop, controller, []);
return true;
} catch {
return false;
}
}
function patchNativeLobbyMusicStart(shouldAllowStart, forcePatch = false) {
const controller = getNativeLobbyMusicController();
const start = readNativeReflectProperty(controller, "start");
if (!isNativeCallable5(start)) {
return false;
}
if (!forcePatch && readNativeReflectProperty(start, "__qolboxWrapped") === true) {
return true;
}
const originalStart = start;
const wrappedStart = function wrappedLobbyMusicStart(...args) {
if (shouldAllowStart()) {
return Reflect.apply(originalStart, this, args);
}
stopNativeLobbyMusic(this);
return void 0;
};
setNativeReflectProperty(wrappedStart, "__qolboxWrapped", true);
setNativeReflectProperty(wrappedStart, "__qolboxOriginal", originalStart);
return setNativeReflectProperty(controller, "start", wrappedStart);
}
// src/features/lobby-music-control.ts
function createLobbyMusicController(options) {
let lobbyMusicPatchInstalled = false;
function isLobbyMusicAllowed() {
return !options.isAudioEnabled() || !hasVisibleLayer(options.playLayerSelector);
}
function stopLobbyMusicIfNeeded() {
if (!options.isAudioEnabled() || isLobbyMusicAllowed()) {
return;
}
stopNativeLobbyMusic();
}
function patchLobbyMusicController() {
if (!options.isAudioEnabled() && !lobbyMusicPatchInstalled) {
return false;
}
if (!patchNativeLobbyMusicStart(isLobbyMusicAllowed, !lobbyMusicPatchInstalled)) {
return false;
}
lobbyMusicPatchInstalled = true;
stopLobbyMusicIfNeeded();
return true;
}
return {
patchLobbyMusicController,
stopLobbyMusicIfNeeded
};
}
// src/features/audio-feature-bundle.ts
function createAudioFeatureBundle(options) {
const gameVolume = createGameVolumeController({
isAudioEnabled: options.isAudioEnabled,
isReserveRetryAudioSuppressed: options.isReserveRetryAudioSuppressed
});
const jukebox = createJukeboxController({
jukeboxDragSensitivity: JUKEBOX_DRAG_SENSITIVITY,
jukeboxWheelStep: JUKEBOX_WHEEL_STEP,
resizeSettlePasses: RESIZE_SETTLE_PASSES,
youTubeHookMaxRetries: YOUTUBE_HOOK_MAX_RETRIES,
youTubeHookRetryDelayMs: YOUTUBE_HOOK_RETRY_DELAY_MS,
findChangeControlsItem,
findSettingsContainer,
focusActiveRenderCanvas: options.focusActiveRenderCanvas,
getActiveRenderCanvas: options.getActiveRenderCanvas,
getActiveRenderMode: options.getActiveRenderMode,
isAudioEnabled: options.isAudioEnabled,
isChatInput: options.isChatInput,
resetBrowserScroll: options.resetBrowserScroll,
scheduleUiWork: options.scheduleUiWork
});
const lobbyMusic = createLobbyMusicController({
playLayerSelector: FULLSCREEN_PLAY_LAYER_SELECTOR,
isAudioEnabled: options.isAudioEnabled
});
return {
...gameVolume,
...jukebox,
...lobbyMusic
};
}
// src/features/feature-side-effects.ts
function createFeatureSideEffectsController(options) {
function disableFeatureSideEffects(featureKey) {
switch (featureKey) {
case FEATURE_RESERVE:
if (options.getReserveState()) {
options.stopReserveSpot({ hideNative: false });
}
options.clearReservePasswordPromptPending();
options.syncReserveJoinButtonLabel();
break;
case FEATURE_GAME_START_ALERT:
options.disableGameStartAlerts();
break;
case FEATURE_AUDIO:
options.removeJukeboxMenuItem();
options.applyGameVolume();
break;
case FEATURE_FULLSCREEN:
options.clearFullscreenLayoutStyles();
break;
case FEATURE_MOBILE_GRAB:
options.hideMobileGrabButton();
break;
case FEATURE_CHAT:
options.clearTypingIndicators();
break;
case FEATURE_LOBBY_COMMANDS:
options.removeSwitchTeamsButton();
break;
default:
break;
}
}
function applyPersistentFeatures() {
options.applyFeatureRootClasses();
options.installPlayerPopupDismissal();
options.patchSlashCommands();
options.patchSwitchTeamsButton();
options.patchMobileQolboxHamburgerEntry();
if (options.featureGates.isReserveEnabled()) {
options.patchReserveSpotFeature();
} else {
options.syncReserveJoinButtonLabel();
}
if (options.featureGates.isGameStartAlertEnabled()) {
options.installGameStartIndicatorHooks();
options.updateGameStartIndicator();
} else {
disableFeatureSideEffects(FEATURE_GAME_START_ALERT);
}
if (options.featureGates.isChatEnabled()) {
options.patchChatTabOrder();
options.patchInGameChatScroll();
options.patchTypingIndicatorHooks();
options.syncTypingIndicators();
} else {
options.clearTypingIndicators();
}
if (options.featureGates.isMobileGrabEnabled()) {
options.patchMobileGrabButton();
} else {
options.hideMobileGrabButton();
}
if (options.featureGates.isAudioEnabled()) {
options.installTabFocusHooks();
options.hookHowlPrototype();
options.patchLobbyMusicController();
options.patchGameVolumeMenu();
options.installYouTubeReadyCallbackHook();
options.hookYouTubePlayer();
options.patchJukeboxMenu();
options.patchJukeboxKnob();
options.applyJukeboxState();
} else {
disableFeatureSideEffects(FEATURE_AUDIO);
}
}
return {
applyPersistentFeatures,
disableFeatureSideEffects
};
}
// src/hitbox/fullscreen-metric-overrides.ts
var METRIC_NAMES = ["_P", "Qp", "lg", "ug"];
function isFullscreenDimensions(value) {
return isNativeObject(value) && typeof readNativeProperty(value, "scale") === "number" && typeof readNativeProperty(value, "width") === "number" && typeof readNativeProperty(value, "height") === "number";
}
function createMetricOriginals(game) {
return {
_P: { descriptor: Object.getOwnPropertyDescriptor(game, "_P") || null },
Qp: { descriptor: Object.getOwnPropertyDescriptor(game, "Qp") || null },
lg: { descriptor: Object.getOwnPropertyDescriptor(game, "lg") || null },
ug: { descriptor: Object.getOwnPropertyDescriptor(game, "ug") || null }
};
}
function makeMetricAccessor(getter) {
return {
configurable: true,
enumerable: true,
get: getter,
set: () => {
}
};
}
function createFullscreenMetricOverrideController(options) {
function getPinnedFullscreenDimensions(game) {
const pinned = readNativeProperty(game, "__qolboxPinnedDimensions");
return isFullscreenDimensions(pinned) ? pinned : options.getFallbackDimensions();
}
function installNativeMetricOverride(game) {
if (!isNativeObject(game)) {
return false;
}
if (readNativeProperty(game, "__qolboxMetricOverrideInstalled")) {
return true;
}
setNativeReflectProperty(game, "__qolboxMetricOriginals", createMetricOriginals(game));
try {
Object.defineProperty(game, "_P", makeMetricAccessor(() => getPinnedFullscreenDimensions(game).scale));
Object.defineProperty(
game,
"Qp",
makeMetricAccessor(() => options.getNativeUiZoom(getPinnedFullscreenDimensions(game)))
);
Object.defineProperty(game, "lg", makeMetricAccessor(() => getPinnedFullscreenDimensions(game).width));
Object.defineProperty(game, "ug", makeMetricAccessor(() => getPinnedFullscreenDimensions(game).height));
setNativeReflectProperty(game, "__qolboxMetricOverrideInstalled", true);
return true;
} catch {
return false;
}
}
function restoreNativeMetricOverride(game) {
if (!isNativeObject(game) || !readNativeProperty(game, "__qolboxMetricOverrideInstalled")) {
return false;
}
const originals = readNativeProperty(game, "__qolboxMetricOriginals");
for (const metricName of METRIC_NAMES) {
const original = isNativeObject(originals) ? readNativeProperty(originals, metricName) : void 0;
const descriptor = isNativeObject(original) ? readNativeProperty(original, "descriptor") : void 0;
try {
if (descriptor && typeof descriptor === "object") {
Object.defineProperty(game, metricName, descriptor);
} else {
Reflect.deleteProperty(game, metricName);
}
} catch {
}
}
Reflect.deleteProperty(game, "__qolboxPinnedDimensions");
Reflect.deleteProperty(game, "__qolboxMetricOriginals");
Reflect.deleteProperty(game, "__qolboxMetricOverrideInstalled");
return true;
}
return {
getPinnedFullscreenDimensions,
installNativeMetricOverride,
restoreNativeMetricOverride
};
}
// src/hitbox/fullscreen-metrics-adapter.ts
function createFullscreenMetricsAdapter(options) {
const getWindowObject = () => options.windowObject || window;
const getGame = () => readNativeProperty(getWindowObject(), "a8");
const metricOverrides = createFullscreenMetricOverrideController({
getFallbackDimensions: options.getFullscreenDimensions,
getNativeUiZoom: options.getNativeUiZoom
});
function installNativeMetricOverride(game = getGame()) {
return metricOverrides.installNativeMetricOverride(game);
}
function restoreNativeMetricOverride(game = getGame()) {
return metricOverrides.restoreNativeMetricOverride(game);
}
function setNativeFullscreenSize(dimensions = options.getFullscreenDimensions()) {
const game = getGame();
if (!isNativeObject(game)) {
return false;
}
setNativeReflectProperty(game, "__qolboxPinnedDimensions", dimensions);
installNativeMetricOverride(game);
setNativeReflectProperty(game, "_P", dimensions.scale);
setNativeReflectProperty(game, "lg", dimensions.width);
setNativeReflectProperty(game, "ug", dimensions.height);
if ("Qp" in game) {
setNativeReflectProperty(game, "Qp", options.getNativeUiZoom(dimensions));
}
const layoutMethod = readNativeProperty(game, "PP");
if (typeof layoutMethod === "function") {
try {
Reflect.apply(layoutMethod, game, []);
} catch {
}
}
return true;
}
function restoreNativeFullscreenPatch(game = getGame()) {
if (!isNativeObject(game)) {
return false;
}
const resizeMethod = readNativeProperty(game, "ag");
const originalResize = readNativeProperty(resizeMethod, "__qolboxOriginal");
if (typeof resizeMethod === "function" && readNativeProperty(resizeMethod, "__qolboxWrapped") && typeof originalResize === "function") {
setNativeReflectProperty(game, "ag", originalResize);
}
restoreNativeMetricOverride(game);
const restoredResize = readNativeProperty(game, "ag");
if (typeof restoredResize === "function") {
try {
Reflect.apply(restoredResize, game, []);
} catch {
}
}
return true;
}
function installNativeFullscreenPatch() {
const game = getGame();
if (!isNativeObject(game)) {
return false;
}
installNativeMetricOverride(game);
const originalResize = readNativeProperty(game, "ag");
if (typeof originalResize !== "function" || readNativeProperty(originalResize, "__qolboxWrapped")) {
return true;
}
const wrappedResize = function wrappedResize2(...args) {
setNativeFullscreenSize(options.getFullscreenDimensions());
if (readNativeProperty(game, "__qolboxRunningNativeResize")) {
return Reflect.apply(originalResize, this, args);
}
setNativeReflectProperty(game, "__qolboxRunningNativeResize", true);
try {
const result = Reflect.apply(originalResize, this, args);
setNativeFullscreenSize(options.getFullscreenDimensions());
return result;
} finally {
setNativeReflectProperty(game, "__qolboxRunningNativeResize", false);
}
};
setNativeReflectProperty(wrappedResize, "__qolboxWrapped", true);
setNativeReflectProperty(wrappedResize, "__qolboxOriginal", originalResize);
setNativeReflectProperty(game, "ag", wrappedResize);
return true;
}
function runNativeResize(dimensions = options.getFullscreenDimensions()) {
const game = getGame();
const resizeMethod = readNativeProperty(game, "ag");
if (!isNativeObject(game) || typeof resizeMethod !== "function") {
return false;
}
setNativeFullscreenSize(dimensions);
try {
Reflect.apply(resizeMethod, game, [dimensions]);
return true;
} catch {
return false;
}
}
return {
installNativeFullscreenPatch,
restoreNativeFullscreenPatch,
runNativeResize,
setNativeFullscreenSize
};
}
// src/hitbox/native-game-adapter.ts
function getNativeGameObject() {
const game = readNativeProperty(window, "a8");
return isNativeObject(game) ? game : null;
}
function getNativeGameSlot() {
return readNativeProperty(window, "a8");
}
function readFinitePositiveNumber(source, property) {
const value = Number(readNativeProperty(source, property));
return Number.isFinite(value) && value > 0 ? value : null;
}
function hasNativeGameObject() {
return getNativeGameObject() !== null;
}
function installNativeGameReadyHook(onReady) {
if (getNativeGameSlot()) {
onReady();
return;
}
try {
let pendingGame = null;
Object.defineProperty(window, "a8", {
configurable: true,
enumerable: true,
get() {
return pendingGame;
},
set(value) {
pendingGame = value;
Object.defineProperty(window, "a8", {
configurable: true,
enumerable: true,
writable: true,
value
});
onReady();
}
});
} catch {
}
}
function getNativeBaseGameSize(fallback) {
const game = getNativeGameObject();
return {
width: readFinitePositiveNumber(game, "Xg") ?? fallback.width,
height: readFinitePositiveNumber(game, "Zg") ?? fallback.height
};
}
function getNativeFullscreenLayoutSize() {
const game = getNativeGameObject();
return {
width: Number(readNativeProperty(game, "lg")) || 0,
height: Number(readNativeProperty(game, "ug")) || 0
};
}
// src/features/fullscreen-probe-alignment.ts
function isFullscreenRenderProbeAligned(probe, dimensions) {
if (probe.renderWidth <= 0 || probe.renderHeight <= 0) {
return false;
}
return Math.abs(probe.renderWidth - dimensions.width) <= 2 && Math.abs(probe.renderHeight - dimensions.height) <= 2 && Math.abs(probe.renderLeft - dimensions.left) <= 2 && Math.abs(probe.renderTop - dimensions.top) <= 2 && probe.backingWidth > 0 && probe.backingHeight > 0;
}
function isFullscreenNativeProbeAligned(probe, dimensions) {
if (probe.nativeWidth <= 0 || probe.nativeHeight <= 0) {
return false;
}
return Math.abs(probe.nativeWidth - dimensions.width) <= 2 && Math.abs(probe.nativeHeight - dimensions.height) <= 2;
}
function buildFullscreenProbeSignature(dimensions, probe, hasNativeGame) {
return [
dimensions.mode,
dimensions.viewportWidth,
dimensions.viewportHeight,
dimensions.width,
dimensions.height,
dimensions.left,
dimensions.top,
probe.appWidth,
probe.appHeight,
probe.relativeWidth,
probe.relativeHeight,
probe.renderWidth,
probe.renderHeight,
probe.renderLeft,
probe.renderTop,
probe.backingWidth,
probe.backingHeight,
probe.rendererCount,
probe.nativeWidth,
probe.nativeHeight,
hasNativeGame
].join(":");
}
// src/features/fullscreen-geometry.ts
var MENU_FRAME_PADDING_PX = 0;
var GAMEPLAY_SAFE_TOP_PX = 0;
var GAMEPLAY_SAFE_BOTTOM_PX = 0;
var GAMEPLAY_SAFE_SIDE_PX = 0;
function createFullscreenGeometry(options) {
function getModeInsets(mode) {
if (mode === "gameplay" || mode === "editor") {
return {
left: GAMEPLAY_SAFE_SIDE_PX,
right: GAMEPLAY_SAFE_SIDE_PX,
top: GAMEPLAY_SAFE_TOP_PX,
bottom: GAMEPLAY_SAFE_BOTTOM_PX
};
}
return {
left: MENU_FRAME_PADDING_PX,
right: MENU_FRAME_PADDING_PX,
top: MENU_FRAME_PADDING_PX,
bottom: MENU_FRAME_PADDING_PX
};
}
function getFullscreenDimensions(viewport = options.getViewportSize(), mode = options.getActiveRenderMode()) {
const base = options.getBaseGameSize();
const insets = getModeInsets(mode);
const availableWidth = Math.max(1, viewport.width - insets.left - insets.right);
const availableHeight = Math.max(1, viewport.height - insets.top - insets.bottom);
const scale = Math.max(0.01, Math.min(availableWidth / base.width, availableHeight / base.height));
const width = Math.max(1, Math.round(base.width * scale));
const height = Math.max(1, Math.round(base.height * scale));
const left = insets.left + Math.max(0, Math.floor((availableWidth - width) / 2));
const top = insets.top + Math.max(0, Math.floor((availableHeight - height) / 2));
return {
viewportWidth: viewport.width,
viewportHeight: viewport.height,
baseWidth: base.width,
baseHeight: base.height,
width,
height,
scale,
left,
top,
shellLeft: 0,
shellTop: 0,
shellWidth: viewport.width,
shellHeight: viewport.height,
insets,
mode
};
}
function getNativeUiZoom(dimensions = getFullscreenDimensions()) {
return Math.min(1, dimensions.width / 1400);
}
function getRelativeContainerBounds(dimensions = getFullscreenDimensions()) {
return {
left: dimensions.left,
top: dimensions.top,
width: dimensions.width,
height: dimensions.height
};
}
function isRenderProbeAligned(probe, dimensions) {
return isFullscreenRenderProbeAligned(probe, dimensions);
}
function isNativeProbeAligned(probe, dimensions) {
return isFullscreenNativeProbeAligned(probe, dimensions);
}
function buildFullscreenSignature(dimensions, probe) {
return buildFullscreenProbeSignature(dimensions, probe, options.hasNativeGame());
}
return {
buildFullscreenSignature,
getFullscreenDimensions,
getModeInsets,
getNativeUiZoom,
getRelativeContainerBounds,
isNativeProbeAligned,
isRenderProbeAligned
};
}
// src/features/fullscreen-native-layout-fallback.ts
function getStyleDeclaration(element) {
if (isStyledElement(element)) {
return element.style;
}
return null;
}
function hasStyleSize(element) {
const style = getStyleDeclaration(element);
return Boolean(style?.width && style.height);
}
function setStyleSizeIfEmpty(element, width, height) {
const style = getStyleDeclaration(element);
if (!style || style.width || style.height) {
return;
}
style.width = width;
style.height = height;
}
function createFullscreenNativeLayoutFallback(options) {
let waitStartedAt = 0;
function hasNativeLayoutSeed() {
const appContainer = document.getElementById("appContainer");
const relativeContainer = document.getElementById("relativeContainer");
return Boolean(appContainer && relativeContainer && hasStyleSize(appContainer) && hasStyleSize(relativeContainer));
}
function shouldWaitForNativeLayoutSeed() {
if (hasNativeLayoutSeed()) {
waitStartedAt = 0;
return false;
}
if (!document.getElementById("appContainer") || !document.getElementById("relativeContainer")) {
return false;
}
if (!waitStartedAt) {
waitStartedAt = Date.now();
}
return Date.now() - waitStartedAt < options.waitMs;
}
function restoreNativeLayoutSizeFallback() {
const canvas = options.getActiveRenderCanvas();
const canvasSize = getCanvasBackingSize(canvas);
const backingWidth = canvasSize?.width ?? Number.NaN;
const backingHeight = canvasSize?.height ?? Number.NaN;
const pixelRatio = Math.max(1, Number(window.devicePixelRatio) || 1);
const width = backingWidth / pixelRatio;
const height = backingHeight / pixelRatio;
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0 || width >= window.innerWidth * 0.98 || height >= window.innerHeight * 0.98) {
return;
}
const containerWidthPx = `${Math.floor(width)}px`;
const containerHeightPx = `${Math.floor(height)}px`;
const canvasWidthPx = `${Math.round(width * 10) / 10}px`;
const canvasHeightPx = `${Math.round(height * 10) / 10}px`;
for (const element of [
document.getElementById("appContainer"),
document.getElementById("relativeContainer")
]) {
setStyleSizeIfEmpty(element, containerWidthPx, containerHeightPx);
}
setStyleSizeIfEmpty(canvas, canvasWidthPx, canvasHeightPx);
}
return {
restoreNativeLayoutSizeFallback,
shouldWaitForNativeLayoutSeed
};
}
// src/hitbox/renderer-discovery.ts
function isRendererCandidate(value) {
return isNativeObject(value) && isNativeObject(readNativeProperty(value, "Bc")) && (isNativeObject(readNativeProperty(value, "Ag")) || typeof readNativeProperty(value, "cg") === "function");
}
function getRendererView(renderer) {
const view = readNativePath(renderer, ["Ag", "view"]);
return view instanceof Element ? view : null;
}
function getRendererHost(renderer) {
const directHost = readNativeProperty(renderer, "Tg") || readNativeProperty(renderer, "dg");
if (directHost instanceof Element) {
return directHost;
}
return getRendererView(renderer)?.parentElement || null;
}
function getKnownFullscreenRenderers(windowObject = window) {
const renderers = [];
const seen = /* @__PURE__ */ new Set();
function addRenderer(candidate) {
if (!isRendererCandidate(candidate) || seen.has(candidate)) {
return;
}
seen.add(candidate);
renderers.push(candidate);
}
function collect(candidate) {
if (!candidate) {
return;
}
if (Array.isArray(candidate)) {
candidate.forEach(collect);
return;
}
addRenderer(candidate);
const nested = readNativeProperty(candidate, "hb");
addRenderer(nested);
if (Array.isArray(nested)) {
nested.forEach(addRenderer);
}
}
const multiplayerSession = readNativeProperty(windowObject, "multiplayerSession");
collect(multiplayerSession);
collect(readNativeProperty(windowObject, "A4"));
collect(readNativePath(windowObject, ["a8", "II"]));
return renderers;
}
// src/hitbox/renderer-adapter.ts
function resizeKnownRenderer(renderer, width, height) {
const backing = readNativeProperty(renderer, "Bc");
if (isNativeObject(backing)) {
setNativeReflectProperty(backing, "wc", width);
setNativeReflectProperty(backing, "mc", height);
}
const pixelRatio = Math.max(1, Number(window.devicePixelRatio) || 1);
const pixiRenderer = readNativeProperty(renderer, "Ag");
if (isNativeObject(pixiRenderer)) {
if ("autoDensity" in pixiRenderer) {
setNativeReflectProperty(pixiRenderer, "autoDensity", true);
}
if (typeof readNativeProperty(pixiRenderer, "resolution") === "number") {
setNativeReflectProperty(pixiRenderer, "resolution", pixelRatio);
}
const options = readNativeProperty(pixiRenderer, "options");
if (isNativeObject(options)) {
setNativeReflectProperty(options, "autoDensity", true);
setNativeReflectProperty(options, "resolution", pixelRatio);
}
}
try {
const nativeResize = readNativeProperty(renderer, "cg");
if (typeof nativeResize === "function") {
Reflect.apply(nativeResize, renderer, [width, height]);
} else {
const pixiResize = readNativeProperty(pixiRenderer, "resize");
if (typeof pixiResize === "function") {
Reflect.apply(pixiResize, pixiRenderer, [width, height]);
}
}
} catch {
}
}
function resizeKnownFullscreenRenderers(options) {
const { dimensions, fitElementToFrame, setImportantStyle, windowObject = window } = options;
const frameWidth = Math.max(1, Math.round(dimensions.width));
const frameHeight = Math.max(1, Math.round(dimensions.height));
for (const renderer of getKnownFullscreenRenderers(windowObject)) {
resizeKnownRenderer(renderer, frameWidth, frameHeight);
fitElementToFrame(getRendererHost(renderer), dimensions, dimensions.left, dimensions.top);
const view = getRendererView(renderer);
if (!view) {
continue;
}
setImportantStyle(view, "position", "absolute");
setImportantStyle(view, "left", "0");
setImportantStyle(view, "top", "0");
setImportantStyle(view, "right", "auto");
setImportantStyle(view, "bottom", "auto");
setImportantStyle(view, "width", `${frameWidth}px`);
setImportantStyle(view, "height", `${frameHeight}px`);
setImportantStyle(view, "max-width", "none");
setImportantStyle(view, "max-height", "none");
setImportantStyle(view, "transform", "none");
fitElementToFrame(view.parentElement, dimensions, dimensions.left, dimensions.top);
}
}
// src/features/fullscreen-render-state.ts
function createFullscreenRenderState(options) {
function getViewportSize() {
return {
width: Math.max(window.innerWidth, document.documentElement.clientWidth || 0),
height: Math.max(window.innerHeight, document.documentElement.clientHeight || 0)
};
}
function getBaseGameSize() {
return getNativeBaseGameSize({
width: options.fallbackBaseWidth,
height: options.fallbackBaseHeight
});
}
function isEditorLayer(element) {
return element instanceof Element && element.id === "editorContainer";
}
function isCanvasElement(element) {
return element instanceof Element && element.tagName === "CANVAS";
}
function isEditorCanvas(element) {
return isCanvasElement(element) && element.parentElement instanceof Element && element.parentElement.id === "editorContainer";
}
function getActiveRenderMode() {
if (options.hasVisibleLayer(options.menuLayerSelector)) {
return "menu";
}
if (options.hasVisibleLayer(options.editorLayerSelector)) {
return "editor";
}
if (options.hasVisibleLayer(options.gameplayLayerSelector)) {
return "gameplay";
}
return "menu";
}
function getActiveRenderCanvas(mode = getActiveRenderMode()) {
const selector = mode === "gameplay" ? options.gameplayLayerSelector : mode === "editor" ? options.editorLayerSelector : options.menuLayerSelector;
for (const layer of document.querySelectorAll(selector)) {
if (!options.isElementVisible(layer)) {
continue;
}
const canvas = layer.querySelector("canvas");
if (isCanvasElement(canvas)) {
return canvas;
}
}
const fallback = document.querySelector(options.renderCanvasSelector);
return isCanvasElement(fallback) ? fallback : null;
}
function getLayoutProbe() {
const appContainer = document.getElementById("appContainer");
const relativeContainer = document.getElementById("relativeContainer");
const renderLayer = getActiveRenderCanvas();
const appRect = appContainer ? appContainer.getBoundingClientRect() : null;
const relativeRect = relativeContainer ? relativeContainer.getBoundingClientRect() : null;
const renderRect = renderLayer ? renderLayer.getBoundingClientRect() : null;
const renderers = getKnownFullscreenRenderers();
const backingSize = getCanvasBackingSize(renderLayer);
const nativeLayoutSize = getNativeFullscreenLayoutSize();
return {
appWidth: appRect ? Math.round(appRect.width) : 0,
appHeight: appRect ? Math.round(appRect.height) : 0,
relativeWidth: relativeRect ? Math.round(relativeRect.width) : 0,
relativeHeight: relativeRect ? Math.round(relativeRect.height) : 0,
renderWidth: renderRect ? Math.round(renderRect.width) : 0,
renderHeight: renderRect ? Math.round(renderRect.height) : 0,
renderLeft: renderRect ? Math.round(renderRect.left) : 0,
renderTop: renderRect ? Math.round(renderRect.top) : 0,
backingWidth: backingSize ? Math.round(backingSize.width) : 0,
backingHeight: backingSize ? Math.round(backingSize.height) : 0,
rendererCount: renderers.length,
nativeWidth: nativeLayoutSize.width,
nativeHeight: nativeLayoutSize.height
};
}
return {
getActiveRenderCanvas,
getActiveRenderMode,
getBaseGameSize,
getLayoutProbe,
getViewportSize,
isEditorCanvas,
isEditorLayer
};
}
// src/features/fullscreen-style-manager.ts
function createFullscreenStyleManager() {
let fullscreenStyleSnapshots = /* @__PURE__ */ new WeakMap();
function rememberFullscreenStyle(element, property) {
if (!isStyledElement(element)) {
return;
}
let snapshot = fullscreenStyleSnapshots.get(element);
if (!snapshot) {
snapshot = /* @__PURE__ */ new Map();
fullscreenStyleSnapshots.set(element, snapshot);
}
if (snapshot.has(property)) {
return;
}
const value = element.style.getPropertyValue(property);
const priority = element.style.getPropertyPriority(property);
snapshot.set(property, {
priority,
value,
hadValue: value !== "" || priority !== ""
});
}
function setImportantStyle(element, property, value) {
if (!isStyledElement(element)) {
return;
}
rememberFullscreenStyle(element, property);
element.style.setProperty(property, value, "important");
}
function restoreFullscreenStyles(element, properties) {
if (!isStyledElement(element)) {
return;
}
const snapshot = fullscreenStyleSnapshots.get(element);
for (const property of properties) {
const original = snapshot?.get(property);
if (original?.hadValue) {
element.style.setProperty(property, original.value, original.priority);
} else {
element.style.removeProperty(property);
}
}
}
function clearFullscreenStyleSnapshots() {
fullscreenStyleSnapshots = /* @__PURE__ */ new WeakMap();
}
return {
clearFullscreenStyleSnapshots,
restoreFullscreenStyles,
setImportantStyle
};
}
// src/features/fullscreen-foundation-bundle.ts
function createFullscreenFoundationBundle() {
const renderState = createFullscreenRenderState({
editorLayerSelector: FULLSCREEN_EDITOR_LAYER_SELECTOR,
fallbackBaseHeight: FALLBACK_BASE_HEIGHT,
fallbackBaseWidth: FALLBACK_BASE_WIDTH,
gameplayLayerSelector: FULLSCREEN_GAMEPLAY_LAYER_SELECTOR,
hasVisibleLayer,
isElementVisible,
menuLayerSelector: FULLSCREEN_MENU_LAYER_SELECTOR,
renderCanvasSelector: FULLSCREEN_RENDER_CANVAS_SELECTOR
});
const nativeLayoutFallback = createFullscreenNativeLayoutFallback({
getActiveRenderCanvas: renderState.getActiveRenderCanvas,
waitMs: FULLSCREEN_NATIVE_LAYOUT_WAIT_MS
});
const styleManager = createFullscreenStyleManager();
const geometry = createFullscreenGeometry({
getActiveRenderMode: renderState.getActiveRenderMode,
getBaseGameSize: renderState.getBaseGameSize,
getViewportSize: renderState.getViewportSize,
hasNativeGame: hasNativeGameObject
});
const metricsAdapter = createFullscreenMetricsAdapter({
getFullscreenDimensions: geometry.getFullscreenDimensions,
getNativeUiZoom: geometry.getNativeUiZoom
});
return {
...renderState,
...nativeLayoutFallback,
...styleManager,
...geometry,
...metricsAdapter
};
}
// src/hitbox/session-adapter.ts
function readNativeCollectionValue(collection, key) {
if (!isNativeObject(collection) || key === null || key === void 0) {
return null;
}
const propertyValue = readNativeProperty(collection, String(key));
if (propertyValue) {
return propertyValue;
}
const getter = readNativeProperty(collection, "get");
if (typeof getter === "function") {
const value = Reflect.apply(getter, collection, [key]);
return value ?? null;
}
return propertyValue ?? null;
}
function getMultiplayerSession() {
return isNativeObject(window.multiplayerSession) ? window.multiplayerSession : null;
}
function getNativeLobbyState(session) {
return readNativeProperty(session, "JD");
}
function getSessionPlayer(session = getMultiplayerSession()) {
const lobbyState = getNativeLobbyState(session);
return readNativeCollectionValue(readNativeProperty(lobbyState, "Pi"), readNativeProperty(lobbyState, "vL"));
}
function getSessionPlayers(session = getMultiplayerSession()) {
const players = readNativeProperty(getNativeLobbyState(session), "Pi");
if (!isNativeObject(players)) {
return [];
}
if (Array.isArray(players)) {
return players.map((player, id) => ({ id, player })).filter((entry) => Boolean(entry.player));
}
const forEach = readNativeProperty(players, "forEach");
if (typeof forEach === "function") {
const entries = [];
Reflect.apply(forEach, players, [
(player, id) => {
if (player) {
entries.push({ id, player });
}
}
]);
return entries;
}
return Object.keys(players).map((id) => ({ id, player: readNativeProperty(players, id) })).filter((entry) => Boolean(entry.player));
}
function getSessionPlayerById(session, playerId) {
const players = readNativeProperty(getNativeLobbyState(session), "Pi");
return readNativeCollectionValue(players, playerId);
}
function getLocalPlayerId(session = getMultiplayerSession()) {
const playerId = readNativeProperty(getNativeLobbyState(session), "vL");
return playerId === null || playerId === void 0 ? null : playerId;
}
function hasLobbyPlayerState(session = getMultiplayerSession()) {
return isNativeObject(getNativeLobbyState(session));
}
function getPlayerTeamState(player) {
return Number(player ? readNativeProperty(player, "N") : player);
}
function getPlayerName(player) {
return readNativeProperty(player, "name");
}
function isSamePlayerId(left, right) {
return left !== null && left !== void 0 && right !== null && right !== void 0 && String(left) === String(right);
}
function isNativeTeamMode(session = getMultiplayerSession()) {
const nativeTeamMode = readNativeProperty(getNativeLobbyState(session), "Qn");
return nativeTeamMode === true || nativeTeamMode === 1;
}
function isTeamsLocked(session = getMultiplayerSession()) {
const locked = readNativeProperty(getNativeLobbyState(session), "VL");
return locked === true || locked === 1;
}
function isHostSession(session = getMultiplayerSession()) {
const lobbyState = getNativeLobbyState(session);
const hostCheck = readNativeProperty(lobbyState, "XD");
return typeof hostCheck === "function" && Boolean(Reflect.apply(hostCheck, lobbyState, []));
}
function isSessionLobbyActive(session = getMultiplayerSession()) {
const lobbyUi = readNativeProperty(session, "TJ");
const match = readNativeProperty(session, "KR");
return Boolean(readNativeProperty(lobbyUi, "NS") && !readNativeProperty(match, "SL"));
}
function isSessionMatchActive(session = getMultiplayerSession()) {
return Boolean(readNativeProperty(readNativeProperty(session, "KR"), "SL"));
}
// src/features/fullscreen-cleanup.ts
var APP_CONTAINER_PROPERTIES = [
"position",
"left",
"top",
"right",
"bottom",
"margin",
"width",
"height",
"max-width",
"max-height",
"border",
"overflow"
];
var BACKGROUND_IMAGE_PROPERTIES = ["position", "left", "top", "right", "bottom", "width", "height"];
var FRAME_PROPERTIES = [
"position",
"left",
"top",
"right",
"bottom",
"margin",
"width",
"height",
"max-width",
"max-height",
"overflow",
"transform",
"transform-origin",
"zoom"
];
var RELATIVE_CONTAINER_PROPERTIES = [
"position",
"left",
"top",
"right",
"bottom",
"margin",
"width",
"height",
"overflow"
];
function deleteDatasetValue(element, key) {
if (hasDataset(element)) {
delete element.dataset[key];
}
}
function shouldDispatchNativeResizeAfterCleanup() {
const appContainer = document.getElementById("appContainer");
const relativeContainer = document.getElementById("relativeContainer");
return appContainer?.style.position === "fixed" || relativeContainer?.style.position === "fixed" || document.documentElement.style.overflow === "hidden";
}
function dispatchNativeResizeAfterCleanup() {
try {
window.dispatchEvent(new Event("resize"));
} catch {
}
}
function createFullscreenCleanup(options) {
function clearFullscreenLayoutStyles() {
const needsNativeResize = shouldDispatchNativeResizeAfterCleanup();
options.clearFullscreenSignature();
options.restoreFullscreenStyles(document.documentElement, ["overflow"]);
options.restoreFullscreenStyles(document.body, ["overflow", "margin", "background-color"]);
options.restoreFullscreenStyles(document.getElementById("appContainer"), APP_CONTAINER_PROPERTIES);
options.restoreFullscreenStyles(document.getElementById("relativeContainer"), RELATIVE_CONTAINER_PROPERTIES);
options.restoreFullscreenStyles(document.getElementById("backgroundImage"), BACKGROUND_IMAGE_PROPERTIES);
for (const element of document.querySelectorAll(options.renderLayerSelector)) {
options.restoreFullscreenStyles(element, FRAME_PROPERTIES);
deleteDatasetValue(element, "qolboxEditorNativeWidth");
deleteDatasetValue(element, "qolboxEditorNativeHeight");
deleteDatasetValue(element, "qolboxEditorScale");
}
for (const canvas of document.querySelectorAll(options.renderCanvasSelector)) {
options.restoreFullscreenStyles(canvas, FRAME_PROPERTIES);
}
for (const overlay of document.querySelectorAll(".inGameCSS")) {
options.restoreFullscreenStyles(overlay, ["zoom", "transform-origin"]);
}
for (const scorePanel of document.querySelectorAll(".scores")) {
options.resetScorePanelLayout(scorePanel);
options.restoreFullscreenStyles(scorePanel, ["display"]);
}
for (const scoreRow of document.querySelectorAll(".scores .entryContainer")) {
options.restoreFullscreenStyles(scoreRow, ["background-color"]);
}
for (const spectateControls of document.querySelectorAll(".spectateControls")) {
options.resetSpectateControlsLayout(spectateControls);
}
for (const editorStatusWindow of document.querySelectorAll(".physicsCountWindow")) {
options.restoreFullscreenStyles(editorStatusWindow, [
"position",
"left",
"top",
"right",
"bottom",
"margin",
"transform",
"z-index"
]);
}
options.restoreNativeFullscreenPatch();
options.restoreNativeLayoutSizeFallback();
options.clearFullscreenStyleSnapshots();
if (needsNativeResize) {
dispatchNativeResizeAfterCleanup();
}
}
return {
clearFullscreenLayoutStyles
};
}
// src/features/fullscreen-container-layout.ts
function applyFullscreenContainerLayout(options, dimensions, activeDimensions, relativeBounds) {
options.setImportantStyle(document.documentElement, "overflow", "hidden");
options.setImportantStyle(document.body, "overflow", "hidden");
options.setImportantStyle(document.body, "margin", "0");
options.setImportantStyle(document.body, "background-color", "#0a0a0a");
const appContainer = document.getElementById("appContainer");
if (appContainer) {
options.setImportantStyle(appContainer, "position", "fixed");
options.setImportantStyle(appContainer, "left", `${activeDimensions.shellLeft}px`);
options.setImportantStyle(appContainer, "top", `${activeDimensions.shellTop}px`);
options.setImportantStyle(appContainer, "right", "auto");
options.setImportantStyle(appContainer, "bottom", "auto");
options.setImportantStyle(appContainer, "margin", "0");
options.setImportantStyle(appContainer, "width", `${activeDimensions.shellWidth}px`);
options.setImportantStyle(appContainer, "height", `${activeDimensions.shellHeight}px`);
options.setImportantStyle(appContainer, "max-width", "none");
options.setImportantStyle(appContainer, "max-height", "none");
options.setImportantStyle(appContainer, "border", "0");
options.setImportantStyle(appContainer, "overflow", "hidden");
}
const relativeContainer = document.getElementById("relativeContainer");
if (relativeContainer) {
options.setImportantStyle(relativeContainer, "position", "fixed");
options.setImportantStyle(relativeContainer, "left", `${relativeBounds.left}px`);
options.setImportantStyle(relativeContainer, "top", `${relativeBounds.top}px`);
options.setImportantStyle(relativeContainer, "right", "auto");
options.setImportantStyle(relativeContainer, "bottom", "auto");
options.setImportantStyle(relativeContainer, "margin", "0");
options.setImportantStyle(relativeContainer, "width", `${relativeBounds.width}px`);
options.setImportantStyle(relativeContainer, "height", `${relativeBounds.height}px`);
options.setImportantStyle(relativeContainer, "overflow", "visible");
}
const backgroundImage = document.getElementById("backgroundImage");
if (backgroundImage) {
options.setImportantStyle(backgroundImage, "position", "fixed");
options.setImportantStyle(backgroundImage, "left", "0");
options.setImportantStyle(backgroundImage, "top", "0");
options.setImportantStyle(backgroundImage, "right", "auto");
options.setImportantStyle(backgroundImage, "bottom", "auto");
options.setImportantStyle(backgroundImage, "width", `${dimensions.viewportWidth}px`);
options.setImportantStyle(backgroundImage, "height", `${dimensions.viewportHeight}px`);
}
}
// src/features/fullscreen-editor-frame-layout.ts
var EDITOR_STATUS_BLOCKER_MARGIN_PX = 8;
var EDITOR_STATUS_VIEWPORT_MARGIN_PX = 5;
var EDITOR_STATUS_RADIO_TRACKING_FRAMES = 60;
function createFullscreenEditorFrameLayout(options) {
let latestEditorFrame = null;
let observedJukebox = null;
let jukeboxStatusObserver = null;
let statusLayoutFrame = 0;
let statusLayoutFramesRemaining = 0;
function getEditorNativeSize(editorLayer, dimensions) {
const canvas = editorLayer instanceof Element ? editorLayer.querySelector("canvas") : null;
const canvasSize = getCanvasBackingSize(canvas);
const canvasWidth = Number(canvasSize?.width);
const canvasHeight = Number(canvasSize?.height);
return {
width: Number.isFinite(canvasWidth) && canvasWidth > 0 ? canvasWidth : dimensions.baseWidth,
height: Number.isFinite(canvasHeight) && canvasHeight > 0 ? canvasHeight : dimensions.baseHeight
};
}
function getScaledEditorFrame(editorLayer, dimensions) {
const nativeSize = getEditorNativeSize(editorLayer, dimensions);
const scale = Math.max(
0.01,
Math.min(dimensions.width / nativeSize.width, dimensions.height / nativeSize.height)
);
const visualWidth = Math.max(1, Math.round(nativeSize.width * scale));
const visualHeight = Math.max(1, Math.round(nativeSize.height * scale));
return {
left: dimensions.left + Math.max(0, Math.floor((dimensions.width - visualWidth) / 2)),
top: dimensions.top + Math.max(0, Math.floor((dimensions.height - visualHeight) / 2)),
width: nativeSize.width,
height: nativeSize.height,
scale,
visualWidth,
visualHeight
};
}
function getVisibleRect(element) {
const rect = element.getBoundingClientRect();
const style = window.getComputedStyle(element);
if (style.display === "none" || style.visibility === "hidden" || rect.width <= 0 || rect.height <= 0) {
return null;
}
return rect;
}
function intersectsHorizontally(rect, left, right) {
return rect.right > left && rect.left < right;
}
function getJukeboxTop(left, right) {
const jukebox = document.querySelector(".jukebox");
if (!(jukebox instanceof Element)) {
return null;
}
const candidates = [jukebox, ...Array.from(jukebox.querySelectorAll("*"))];
let top = null;
for (const candidate of candidates) {
const rect = getVisibleRect(candidate);
if (!rect || !intersectsHorizontally(rect, left, right)) {
continue;
}
if (rect.top > window.innerHeight + EDITOR_STATUS_BLOCKER_MARGIN_PX || rect.bottom < 0) {
continue;
}
top = top === null ? rect.top : Math.min(top, rect.top);
}
return top;
}
function getSpectatorControlsTop(left, right) {
let top = null;
for (const controls of document.querySelectorAll(".spectateControls")) {
const rect = getVisibleRect(controls);
if (!rect || !intersectsHorizontally(rect, left, right)) {
continue;
}
top = top === null ? rect.top : Math.min(top, rect.top);
}
return top;
}
function getEditorStatusMaxTop(left, right, height) {
const viewportMaxTop = window.innerHeight - Math.ceil(height) - EDITOR_STATUS_VIEWPORT_MARGIN_PX;
const blockerTops = [getJukeboxTop(left, right), getSpectatorControlsTop(left, right)].filter(
(top) => top !== null
);
if (!blockerTops.length) {
return viewportMaxTop;
}
const blockerMaxTop = Math.min(...blockerTops) - Math.ceil(height) - EDITOR_STATUS_BLOCKER_MARGIN_PX;
return Math.min(viewportMaxTop, blockerMaxTop);
}
function hasActiveFullscreenEditorFrame() {
const editorLayer = document.querySelector("#editorContainer");
return hasDataset(editorLayer) && Boolean(editorLayer.dataset.qolboxEditorScale);
}
function runScheduledEditorStatusLayout() {
statusLayoutFrame = 0;
if (!latestEditorFrame || !hasActiveFullscreenEditorFrame()) {
statusLayoutFramesRemaining = 0;
return;
}
layoutEditorStatusWindow(latestEditorFrame);
statusLayoutFramesRemaining -= 1;
if (statusLayoutFramesRemaining > 0) {
statusLayoutFrame = window.requestAnimationFrame(runScheduledEditorStatusLayout);
}
}
function scheduleEditorStatusLayout(frames = 1) {
statusLayoutFramesRemaining = Math.max(statusLayoutFramesRemaining, frames);
if (!statusLayoutFrame) {
statusLayoutFrame = window.requestAnimationFrame(runScheduledEditorStatusLayout);
}
}
function observeJukeboxForEditorStatusLayout() {
const jukebox = document.querySelector(".jukebox");
if (jukebox === observedJukebox) {
return;
}
jukeboxStatusObserver?.disconnect();
observedJukebox = jukebox instanceof Element ? jukebox : null;
if (!observedJukebox) {
jukeboxStatusObserver = null;
return;
}
jukeboxStatusObserver = new MutationObserver(() => {
scheduleEditorStatusLayout(EDITOR_STATUS_RADIO_TRACKING_FRAMES);
});
jukeboxStatusObserver.observe(observedJukebox, {
attributes: true,
attributeFilter: ["class", "style"],
subtree: true
});
}
function fitEditorCanvasToNative(canvas, frame) {
if (!canvas || !frame) {
return;
}
options.setImportantStyle(canvas, "position", "absolute");
options.setImportantStyle(canvas, "left", "0");
options.setImportantStyle(canvas, "top", "0");
options.setImportantStyle(canvas, "right", "auto");
options.setImportantStyle(canvas, "bottom", "auto");
options.setImportantStyle(canvas, "width", `${frame.width}px`);
options.setImportantStyle(canvas, "height", `${frame.height}px`);
options.setImportantStyle(canvas, "max-width", "none");
options.setImportantStyle(canvas, "max-height", "none");
options.setImportantStyle(canvas, "transform", "none");
}
function layoutEditorStatusWindow(frame) {
const statusWindow = document.querySelector(".physicsCountWindow");
if (!(statusWindow instanceof Element)) {
return;
}
const rect = statusWindow.getBoundingClientRect();
const width = rect.width || 186;
const height = rect.height || 22;
const left = Math.max(0, Math.round(frame.left + (frame.visualWidth - width) / 2));
const preferredTop = frame.top + frame.visualHeight + 12;
const maxTop = getEditorStatusMaxTop(left, left + width, height);
const top = Math.max(0, Math.min(Math.round(preferredTop), Math.round(maxTop)));
options.setImportantStyle(statusWindow, "position", "fixed");
options.setImportantStyle(statusWindow, "left", `${left}px`);
options.setImportantStyle(statusWindow, "top", `${top}px`);
options.setImportantStyle(statusWindow, "right", "auto");
options.setImportantStyle(statusWindow, "bottom", "auto");
options.setImportantStyle(statusWindow, "margin", "0");
options.setImportantStyle(statusWindow, "transform", "none");
options.setImportantStyle(statusWindow, "z-index", "2147483001");
}
function fitEditorLayerToFrame(layer, dimensions) {
if (!(layer instanceof Element)) {
return null;
}
const frame = getScaledEditorFrame(layer, dimensions);
latestEditorFrame = frame;
options.setImportantStyle(layer, "position", "absolute");
options.setImportantStyle(layer, "left", `${frame.left}px`);
options.setImportantStyle(layer, "top", `${frame.top}px`);
options.setImportantStyle(layer, "right", "auto");
options.setImportantStyle(layer, "bottom", "auto");
options.setImportantStyle(layer, "width", `${frame.width}px`);
options.setImportantStyle(layer, "height", `${frame.height}px`);
options.setImportantStyle(layer, "max-width", "none");
options.setImportantStyle(layer, "max-height", "none");
options.setImportantStyle(layer, "overflow", "visible");
options.setImportantStyle(layer, "transform", `scale(${frame.scale})`);
options.setImportantStyle(layer, "transform-origin", "top left");
options.setImportantStyle(layer, "zoom", "1");
if (hasDataset(layer)) {
layer.dataset.qolboxEditorNativeWidth = String(frame.width);
layer.dataset.qolboxEditorNativeHeight = String(frame.height);
layer.dataset.qolboxEditorScale = String(frame.scale);
}
const canvas = layer.querySelector("canvas");
if (canvas) {
fitEditorCanvasToNative(canvas, frame);
}
observeJukeboxForEditorStatusLayout();
layoutEditorStatusWindow(frame);
return frame;
}
return {
fitEditorCanvasToNative,
fitEditorLayerToFrame,
getScaledEditorFrame
};
}
// src/features/fullscreen-render-frame-layout.ts
function createFullscreenRenderFrameLayout(options) {
function fitElementToFrame(element, dimensions, left = 0, top = 0) {
if (!(element instanceof Element)) {
return;
}
if (options.isEditorLayer(element)) {
options.fitEditorLayerToFrame(element, dimensions);
return;
}
options.setImportantStyle(element, "position", "absolute");
options.setImportantStyle(element, "left", `${left}px`);
options.setImportantStyle(element, "top", `${top}px`);
options.setImportantStyle(element, "right", "auto");
options.setImportantStyle(element, "bottom", "auto");
options.setImportantStyle(element, "margin", "0");
options.setImportantStyle(element, "width", `${dimensions.width}px`);
options.setImportantStyle(element, "height", `${dimensions.height}px`);
options.setImportantStyle(element, "max-width", "none");
options.setImportantStyle(element, "max-height", "none");
options.setImportantStyle(element, "overflow", "hidden");
options.setImportantStyle(element, "transform", "none");
}
function fitRenderLayersToFrame(dimensions) {
for (const layer of document.querySelectorAll(options.renderLayerSelector)) {
if (options.isEditorLayer(layer)) {
options.fitEditorLayerToFrame(layer, dimensions);
continue;
}
options.setImportantStyle(layer, "position", "absolute");
options.setImportantStyle(layer, "left", `${dimensions.left}px`);
options.setImportantStyle(layer, "top", `${dimensions.top}px`);
options.setImportantStyle(layer, "right", "auto");
options.setImportantStyle(layer, "bottom", "auto");
options.setImportantStyle(layer, "width", `${dimensions.width}px`);
options.setImportantStyle(layer, "height", `${dimensions.height}px`);
options.setImportantStyle(layer, "max-width", "none");
options.setImportantStyle(layer, "max-height", "none");
options.setImportantStyle(layer, "overflow", "hidden");
options.setImportantStyle(layer, "transform", "none");
options.setImportantStyle(layer, "zoom", "1");
}
}
function fitRenderCanvasesToFrame(dimensions) {
for (const canvas of document.querySelectorAll(options.renderCanvasSelector)) {
if (options.isEditorCanvas(canvas)) {
const editorLayer = canvas.parentElement;
const frame = options.getScaledEditorFrame(editorLayer, dimensions);
options.fitEditorCanvasToNative(canvas, frame);
continue;
}
options.setImportantStyle(canvas, "position", "absolute");
options.setImportantStyle(canvas, "left", "0");
options.setImportantStyle(canvas, "top", "0");
options.setImportantStyle(canvas, "right", "auto");
options.setImportantStyle(canvas, "bottom", "auto");
options.setImportantStyle(canvas, "width", `${dimensions.width}px`);
options.setImportantStyle(canvas, "height", `${dimensions.height}px`);
options.setImportantStyle(canvas, "max-width", "none");
options.setImportantStyle(canvas, "max-height", "none");
options.setImportantStyle(canvas, "transform", "none");
}
}
return {
fitElementToFrame,
fitRenderCanvasesToFrame,
fitRenderLayersToFrame
};
}
// src/features/fullscreen-frame-layout.ts
function createFullscreenFrameLayout(options) {
const editorFrameLayout = createFullscreenEditorFrameLayout({
setImportantStyle: options.setImportantStyle
});
function getScaledEditorFrame(editorLayer, dimensions = options.getFullscreenDimensions(void 0, "editor")) {
return editorFrameLayout.getScaledEditorFrame(editorLayer, dimensions);
}
function fitEditorCanvasToNative(canvas, frame) {
editorFrameLayout.fitEditorCanvasToNative(canvas, frame);
}
function fitEditorLayerToFrame(layer, dimensions = options.getFullscreenDimensions(void 0, "editor")) {
return editorFrameLayout.fitEditorLayerToFrame(layer, dimensions);
}
const renderFrameLayout = createFullscreenRenderFrameLayout({
fitEditorCanvasToNative,
fitEditorLayerToFrame,
getScaledEditorFrame,
isEditorCanvas: options.isEditorCanvas,
isEditorLayer: options.isEditorLayer,
renderCanvasSelector: options.renderCanvasSelector,
renderLayerSelector: options.renderLayerSelector,
setImportantStyle: options.setImportantStyle
});
function fitElementToFrame(element, dimensions = options.getFullscreenDimensions(), left = 0, top = 0) {
renderFrameLayout.fitElementToFrame(element, dimensions, left, top);
}
function enforceFullscreenLayout(dimensions = options.getFullscreenDimensions()) {
options.ensureGlobalStyle();
const menuDimensions = dimensions.mode === "menu" ? dimensions : options.getFullscreenDimensions(void 0, "menu");
const playDimensions = dimensions.mode === "gameplay" || dimensions.mode === "editor" ? dimensions : options.getFullscreenDimensions(void 0, "gameplay");
const activeDimensions = dimensions.mode === "gameplay" || dimensions.mode === "editor" ? playDimensions : menuDimensions;
const relativeBounds = options.getRelativeContainerBounds(activeDimensions);
applyFullscreenContainerLayout(options, dimensions, activeDimensions, relativeBounds);
renderFrameLayout.fitRenderLayersToFrame(activeDimensions);
renderFrameLayout.fitRenderCanvasesToFrame(activeDimensions);
const uiZoom = String(options.getNativeUiZoom(activeDimensions));
for (const overlay of document.querySelectorAll(".inGameCSS")) {
options.setImportantStyle(overlay, "zoom", uiZoom);
options.setImportantStyle(overlay, "transform-origin", "top left");
}
options.layoutRelativeHud(relativeBounds, activeDimensions);
return true;
}
return {
enforceFullscreenLayout,
fitEditorCanvasToNative,
fitEditorLayerToFrame,
fitElementToFrame,
getScaledEditorFrame
};
}
// src/features/fullscreen-game-ready-hook.ts
function createFullscreenGameReadyHook(options) {
let installed = false;
function scheduleFullscreenSettle() {
options.scheduleUiWork({ force: true, passes: options.settlePasses });
}
function installGameReadyHook() {
if (installed) {
return;
}
installed = true;
installNativeGameReadyHook(scheduleFullscreenSettle);
}
return {
installGameReadyHook
};
}
// src/features/fullscreen-inline-style.ts
function getFullscreenInlineStyle(element) {
if (isStyledElement(element)) {
return element.style;
}
return null;
}
function removeFullscreenInlineProperties(element, properties) {
const style = getFullscreenInlineStyle(element);
if (!style) {
return;
}
for (const property of properties) {
style.removeProperty(property);
}
}
function getFullscreenInlineStyleProperty(element, property) {
return getFullscreenInlineStyle(element)?.getPropertyValue(property) ?? "";
}
// src/features/fullscreen-spectate-controls-layout.ts
var CLOSED_CONTROLS_BOTTOM_OFFSET_PX = 12;
var HELD_OPEN_CONTROLS_MARGIN_PX = 3;
var RADIO_NEAR_OPEN_BOTTOM_PX = -2;
var RADIO_CLOSING_REENTER_DELTA_PX = 0.5;
var isPointerTrackingInstalled = false;
var lastPointerPosition = null;
function rememberPointerPosition(event) {
lastPointerPosition = {
x: event.clientX,
y: event.clientY
};
}
function clearPointerPosition() {
lastPointerPosition = null;
}
function ensurePointerTracking() {
if (isPointerTrackingInstalled) {
return;
}
isPointerTrackingInstalled = true;
window.addEventListener("pointermove", rememberPointerPosition, true);
window.addEventListener("pointerdown", rememberPointerPosition, true);
window.addEventListener("blur", clearPointerPosition, true);
document.addEventListener(
"pointerleave",
(event) => {
if (!event.relatedTarget) {
clearPointerPosition();
}
},
true
);
}
function isPointerOverElement(element) {
if (!lastPointerPosition) {
return false;
}
const { x, y } = lastPointerPosition;
if (x < 0 || y < 0 || x > window.innerWidth || y > window.innerHeight) {
return false;
}
const hitElement = document.elementFromPoint(x, y);
if (hitElement && (hitElement === element || element.contains(hitElement))) {
return true;
}
const rect = element.getBoundingClientRect();
return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
}
function isPointerInControlsTravelCorridor(controls, jukebox) {
if (!lastPointerPosition) {
return false;
}
const { x, y } = lastPointerPosition;
const controlsRect = controls.getBoundingClientRect();
const jukeboxRect = jukebox.getBoundingClientRect();
if (controlsRect.width <= 0 || controlsRect.height <= 0 || jukeboxRect.width <= 0 || jukeboxRect.height <= 0) {
return false;
}
const corridorLeft = controlsRect.left;
const corridorRight = controlsRect.right;
const corridorTop = Math.min(controlsRect.bottom, jukeboxRect.top);
const corridorBottom = Math.max(controlsRect.bottom, jukeboxRect.top);
return x >= corridorLeft && x <= corridorRight && y >= corridorTop && y <= corridorBottom;
}
function parseBottomPx(value) {
const parsed = Number.parseFloat(typeof value === "string" ? value : "");
return Number.isFinite(parsed) ? parsed : null;
}
function hasFocusedDescendant(element) {
return document.activeElement instanceof Element && element.contains(document.activeElement);
}
function getElementBottomPx(element) {
return parseBottomPx(getFullscreenInlineStyleProperty(element, "bottom")) ?? parseBottomPx(window.getComputedStyle(element).bottom);
}
function wantsToHoldSpectateControls(controls, jukebox) {
return controls.matches(":hover") || isPointerOverElement(controls) || isPointerInControlsTravelCorridor(controls, jukebox) || hasFocusedDescendant(controls);
}
function isSpectateControlsAlreadyExpanded(controls, openOffset) {
const bottom = getElementBottomPx(controls);
return bottom !== null && bottom >= openOffset - HELD_OPEN_CONTROLS_MARGIN_PX;
}
function isNativeCallable6(value) {
return typeof value === "function";
}
function callNativeJukeboxHandler(jukebox, handlerName, fallbackBottom) {
const handler = readObjectProperty(jukebox, handlerName);
if (isNativeCallable6(handler)) {
Reflect.apply(handler, jukebox, []);
return;
}
const style = readObjectProperty(jukebox, "style");
if (style instanceof CSSStyleDeclaration) {
style.bottom = fallbackBottom;
}
}
function getSpectatorRadioLayoutState(options, spectatorRadioHoldActive, keepControlsOpenUntilRadioCloses) {
const baseOffset = CLOSED_CONTROLS_BOTTOM_OFFSET_PX;
const jukebox = document.querySelector(".jukebox");
if (!(jukebox instanceof Element) || !options.isElementVisible(jukebox)) {
return {
controlsBottomOffset: baseOffset,
jukebox: null,
jukeboxDirectlyActive: false,
jukeboxBottom: null,
openOffset: baseOffset,
shouldHoldRadioOpen: false
};
}
const style = window.getComputedStyle(jukebox);
const inlineBottom = getFullscreenInlineStyleProperty(jukebox, "bottom");
const bottom = Number.parseFloat(typeof inlineBottom === "string" ? inlineBottom : style.bottom);
const rect = jukebox.getBoundingClientRect();
const height = rect.height || Number.parseFloat(style.height) || 35;
const openOffset = Math.ceil(height + 36);
const openProgress = Number.isFinite(bottom) ? Math.max(0, Math.min(1, (bottom + 50) / 50)) : 0;
const jukeboxDirectlyActive = jukebox.matches(":hover") || hasFocusedDescendant(jukebox);
const jukeboxIsActive = jukeboxDirectlyActive || openProgress > 0.05 || spectatorRadioHoldActive;
const spectateControls = Array.from(document.querySelectorAll(options.spectateControlsSelector));
const controlsAlreadyExpanded = spectateControls.some(
(controls) => isSpectateControlsAlreadyExpanded(controls, openOffset)
);
const controlsWantHold = spectateControls.some((controls) => wantsToHoldSpectateControls(controls, jukebox));
const holdControlsOpen = controlsAlreadyExpanded && controlsWantHold && jukeboxIsActive;
const holdReleasedNearOpen = keepControlsOpenUntilRadioCloses && !controlsWantHold && !jukeboxDirectlyActive && Number.isFinite(bottom) && bottom > RADIO_NEAR_OPEN_BOTTOM_PX;
const shouldHoldRadioOpen = holdControlsOpen && !jukeboxDirectlyActive;
if (!Number.isFinite(bottom)) {
const open = holdControlsOpen || holdReleasedNearOpen || jukeboxDirectlyActive;
return {
controlsBottomOffset: open ? openOffset : baseOffset,
jukebox,
jukeboxDirectlyActive,
jukeboxBottom: null,
openOffset,
shouldHoldRadioOpen
};
}
const liveOffset = Math.round(baseOffset + (openOffset - baseOffset) * openProgress);
return {
controlsBottomOffset: holdControlsOpen || holdReleasedNearOpen ? openOffset : liveOffset,
jukebox,
jukeboxDirectlyActive,
jukeboxBottom: bottom,
openOffset,
shouldHoldRadioOpen
};
}
function createFullscreenSpectateControlsLayout(options) {
ensurePointerTracking();
let pointerSyncFrame = 0;
let keepControlsOpenUntilRadioCloses = false;
let lastHeldJukeboxBottom = null;
let spectatorRadioHoldActive = false;
function schedulePointerSync() {
if (pointerSyncFrame) {
return;
}
pointerSyncFrame = window.requestAnimationFrame(() => {
pointerSyncFrame = 0;
syncSpectateControlsBottomWithJukebox();
});
}
window.addEventListener("pointermove", schedulePointerSync, true);
window.addEventListener("pointerdown", schedulePointerSync, true);
function setSpectateControlsBottom(spectateControls, bottom) {
if (getFullscreenInlineStyleProperty(spectateControls, "bottom") === bottom) {
return false;
}
options.setImportantStyle(spectateControls, "bottom", bottom);
return true;
}
function releaseSpectatorRadioHold() {
keepControlsOpenUntilRadioCloses = false;
lastHeldJukeboxBottom = null;
if (!spectatorRadioHoldActive) {
return;
}
spectatorRadioHoldActive = false;
const jukebox = document.querySelector(".jukebox");
if (jukebox instanceof Element && !jukebox.matches(":hover") && !hasFocusedDescendant(jukebox)) {
callNativeJukeboxHandler(jukebox, "onmouseleave", "-50px");
}
}
function syncJukeboxSpectatorHold(state) {
if (!state.jukebox) {
releaseSpectatorRadioHold();
return false;
}
if (state.shouldHoldRadioOpen) {
keepControlsOpenUntilRadioCloses = false;
const radioAppearsToBeClosing = lastHeldJukeboxBottom !== null && state.jukeboxBottom !== null && state.jukeboxBottom < lastHeldJukeboxBottom - RADIO_CLOSING_REENTER_DELTA_PX;
if (!spectatorRadioHoldActive || radioAppearsToBeClosing) {
callNativeJukeboxHandler(state.jukebox, "onmouseenter", "0px");
}
spectatorRadioHoldActive = true;
if (state.jukeboxBottom !== null) {
lastHeldJukeboxBottom = state.jukeboxBottom;
}
return false;
}
if (keepControlsOpenUntilRadioCloses && (state.jukeboxDirectlyActive || state.jukeboxBottom === null || state.jukeboxBottom <= RADIO_NEAR_OPEN_BOTTOM_PX)) {
keepControlsOpenUntilRadioCloses = false;
}
if (spectatorRadioHoldActive) {
spectatorRadioHoldActive = false;
lastHeldJukeboxBottom = null;
if (!state.jukeboxDirectlyActive && state.jukeboxBottom !== null && state.jukeboxBottom > RADIO_NEAR_OPEN_BOTTOM_PX) {
keepControlsOpenUntilRadioCloses = true;
}
if (!state.jukeboxDirectlyActive) {
callNativeJukeboxHandler(state.jukebox, "onmouseleave", "-50px");
}
return keepControlsOpenUntilRadioCloses;
}
return false;
}
function resetSpectateControlsLayout(spectateControls) {
releaseSpectatorRadioHold();
removeFullscreenInlineProperties(spectateControls, [
"position",
"left",
"right",
"top",
"bottom",
"transform",
"transition",
"margin",
"z-index"
]);
}
function syncSpectateControlsBottomWithJukebox() {
if (!options.isFullscreenEnabled() || !options.isSessionMatchActive()) {
releaseSpectatorRadioHold();
return false;
}
const state = getSpectatorRadioLayoutState(
options,
spectatorRadioHoldActive,
keepControlsOpenUntilRadioCloses
);
const forceOpenControls = syncJukeboxSpectatorHold(state);
const bottom = `${forceOpenControls ? state.openOffset : state.controlsBottomOffset}px`;
let changed = false;
for (const controls of document.querySelectorAll(options.spectateControlsSelector)) {
changed = setSpectateControlsBottom(controls, bottom) || changed;
}
return changed;
}
function layoutSpectateControls(useGameplayHudLayout) {
if (!useGameplayHudLayout) {
releaseSpectatorRadioHold();
} else {
const state = getSpectatorRadioLayoutState(
options,
spectatorRadioHoldActive,
keepControlsOpenUntilRadioCloses
);
const forceOpenControls = syncJukeboxSpectatorHold(state);
const controlsBottomOffset = forceOpenControls ? state.openOffset : state.controlsBottomOffset;
for (const spectateControls of document.querySelectorAll(options.spectateControlsSelector)) {
options.setImportantStyle(spectateControls, "position", "absolute");
options.setImportantStyle(spectateControls, "left", "50%");
options.setImportantStyle(spectateControls, "right", "auto");
options.setImportantStyle(spectateControls, "top", "auto");
setSpectateControlsBottom(spectateControls, `${controlsBottomOffset}px`);
options.setImportantStyle(spectateControls, "transform", "translateX(-50%)");
options.setImportantStyle(spectateControls, "margin", "0");
options.setImportantStyle(spectateControls, "z-index", "2147483002");
}
return;
}
for (const spectateControls of document.querySelectorAll(options.spectateControlsSelector)) {
resetSpectateControlsLayout(spectateControls);
}
}
return {
layoutSpectateControls,
resetSpectateControlsLayout,
syncSpectateControlsBottomWithJukebox
};
}
// src/features/fullscreen-hud-layout.ts
function isLoadingScreenVisible() {
const loading = document.getElementById("ccLoading");
if (!loading || !loading.isConnected) {
return false;
}
const style = window.getComputedStyle(loading);
return style.display !== "none" && style.visibility !== "hidden" && style.opacity !== "0";
}
function createFullscreenHudLayout(options) {
const spectateControlsLayout = createFullscreenSpectateControlsLayout({
isElementVisible: options.isElementVisible,
isFullscreenEnabled: options.isFullscreenEnabled,
isSessionMatchActive: options.isSessionMatchActive,
setImportantStyle: options.setImportantStyle,
spectateControlsSelector: options.spectateControlsSelector
});
function resetScorePanelLayout(scorePanel) {
removeFullscreenInlineProperties(scorePanel, [
"position",
"left",
"top",
"right",
"bottom",
"transform",
"text-align",
"margin-top",
"z-index"
]);
}
function resetSpectateControlsLayout(spectateControls) {
spectateControlsLayout.resetSpectateControlsLayout(spectateControls);
}
function syncSpectateControlsBottomWithJukebox() {
return spectateControlsLayout.syncSpectateControlsBottomWithJukebox();
}
function layoutRelativeHud(_relativeBounds, dimensions) {
const isLoading = isLoadingScreenVisible();
const useGameplayHudLayout = !isLoading && (dimensions.mode === "gameplay" || options.isSessionMatchActive() && options.hasVisibleLayer(options.spectateControlsSelector));
for (const scorePanel of document.querySelectorAll(options.scoresSelector)) {
if (!useGameplayHudLayout) {
resetScorePanelLayout(scorePanel);
options.setImportantStyle(scorePanel, "display", "none");
continue;
}
options.syncScoreRowsFromPlayers(scorePanel);
options.makeScoreRowsOpaque(scorePanel);
options.syncTypingIndicators(scorePanel);
options.setImportantStyle(scorePanel, "display", "block");
options.setImportantStyle(scorePanel, "position", "absolute");
options.setImportantStyle(scorePanel, "left", "50%");
options.setImportantStyle(scorePanel, "top", "12px");
options.setImportantStyle(scorePanel, "right", "auto");
options.setImportantStyle(scorePanel, "bottom", "auto");
options.setImportantStyle(scorePanel, "transform", "translateX(-50%)");
options.setImportantStyle(scorePanel, "text-align", "center");
options.setImportantStyle(scorePanel, "margin-top", "0");
options.setImportantStyle(scorePanel, "z-index", "10");
}
spectateControlsLayout.layoutSpectateControls(useGameplayHudLayout);
}
return {
layoutRelativeHud,
resetScorePanelLayout,
resetSpectateControlsLayout,
syncSpectateControlsBottomWithJukebox
};
}
// src/features/fullscreen-resize-target-observer.ts
function createFullscreenResizeTargetObserver(options) {
let resizeObserver = null;
let observedResizeTargets = /* @__PURE__ */ new WeakSet();
function setFullscreenResizeObserver(observer) {
resizeObserver = observer;
if (!observer) {
observedResizeTargets = /* @__PURE__ */ new WeakSet();
}
}
function observeResizeTarget(element) {
if (!resizeObserver || !(element instanceof Element) || observedResizeTargets.has(element)) {
return;
}
observedResizeTargets.add(element);
resizeObserver.observe(element);
}
function refreshObservedResizeTargets() {
observeResizeTarget(document.documentElement);
observeResizeTarget(document.body);
observeResizeTarget(document.getElementById("appContainer"));
observeResizeTarget(document.getElementById("relativeContainer"));
observeResizeTarget(document.getElementById("backgroundImage"));
for (const element of document.querySelectorAll(options.renderLayerSelector)) {
observeResizeTarget(element);
}
for (const element of document.querySelectorAll(options.renderCanvasSelector)) {
observeResizeTarget(element);
}
}
return {
refreshObservedResizeTargets,
setFullscreenResizeObserver
};
}
// src/features/fullscreen-layout-feature-bundle.ts
function createFullscreenLayoutFeatureBundle(options) {
const hudLayout = createFullscreenHudLayout({
scoresSelector: ".scores",
spectateControlsSelector: ".spectateControls",
hasVisibleLayer,
isElementVisible,
isFullscreenEnabled: options.isFullscreenEnabled,
isSessionMatchActive,
makeScoreRowsOpaque: options.makeScoreRowsOpaque,
setImportantStyle: options.setImportantStyle,
syncScoreRowsFromPlayers: options.syncScoreRowsFromPlayers,
syncTypingIndicators: options.syncTypingIndicators
});
const frameLayout = createFullscreenFrameLayout({
renderCanvasSelector: FULLSCREEN_RENDER_CANVAS_SELECTOR,
renderLayerSelector: FULLSCREEN_RENDER_LAYER_SELECTOR,
ensureGlobalStyle: options.ensureGlobalStyle,
getFullscreenDimensions: options.getFullscreenDimensions,
getNativeUiZoom: options.getNativeUiZoom,
getRelativeContainerBounds: options.getRelativeContainerBounds,
isEditorCanvas: options.isEditorCanvas,
isEditorLayer: options.isEditorLayer,
layoutRelativeHud: hudLayout.layoutRelativeHud,
setImportantStyle: options.setImportantStyle
});
const cleanup = createFullscreenCleanup({
renderCanvasSelector: FULLSCREEN_RENDER_CANVAS_SELECTOR,
renderLayerSelector: FULLSCREEN_RENDER_LAYER_SELECTOR,
clearFullscreenSignature: options.clearFullscreenSignature,
clearFullscreenStyleSnapshots: options.clearFullscreenStyleSnapshots,
resetScorePanelLayout: hudLayout.resetScorePanelLayout,
resetSpectateControlsLayout: hudLayout.resetSpectateControlsLayout,
restoreFullscreenStyles: options.restoreFullscreenStyles,
restoreNativeFullscreenPatch: options.restoreNativeFullscreenPatch,
restoreNativeLayoutSizeFallback: options.restoreNativeLayoutSizeFallback
});
const gameReadyHook = createFullscreenGameReadyHook({
scheduleUiWork: options.scheduleUiWork,
settlePasses: FULLSCREEN_SETTLE_PASSES
});
const resizeTargets = createFullscreenResizeTargetObserver({
renderCanvasSelector: FULLSCREEN_RENDER_CANVAS_SELECTOR,
renderLayerSelector: FULLSCREEN_RENDER_LAYER_SELECTOR
});
function resizeFullscreenRenderers(dimensions) {
resizeKnownFullscreenRenderers({
dimensions,
fitElementToFrame: frameLayout.fitElementToFrame,
setImportantStyle: options.setImportantStyle
});
}
return {
...hudLayout,
...frameLayout,
...cleanup,
...gameReadyHook,
...resizeTargets,
resizeKnownFullscreenRenderers: resizeFullscreenRenderers
};
}
// src/features/fullscreen-hook-installer.ts
function createFullscreenHookInstaller(options) {
let installed = false;
function scheduleResizeSettle() {
options.scheduleUiWork({ force: true, passes: options.resizeSettlePasses });
}
function installFullscreenHooks() {
if (installed) {
return;
}
if (!document.documentElement) {
options.scheduleUiWork({ force: true, features: true, passes: options.fullscreenSettlePasses });
return;
}
installed = true;
options.installGameReadyHook();
options.installQolboxMenuHooks();
options.installChatEscapeHooks();
options.installChatCommandAliasHooks();
options.installGameplayBackgroundFocusHooks();
if (options.isAudioEnabled()) {
options.installTabFocusHooks();
}
if (options.isGameStartAlertEnabled()) {
options.installGameStartIndicatorHooks();
}
if (options.isReserveEnabled()) {
options.installReserveSocketCaptureHook();
}
window.addEventListener("resize", scheduleResizeSettle, true);
window.addEventListener("orientationchange", scheduleResizeSettle, true);
window.addEventListener(
"load",
() => options.scheduleUiWork({ force: true, features: true, passes: options.fullscreenSettlePasses }),
true
);
window.addEventListener(
"pageshow",
() => options.scheduleUiWork({ force: true, features: true, passes: options.resizeSettlePasses }),
true
);
document.addEventListener(
"visibilitychange",
() => {
if (!document.hidden) {
options.scheduleUiWork({ force: true, features: true, passes: options.resizeSettlePasses });
}
},
true
);
document.addEventListener("fullscreenchange", scheduleResizeSettle, true);
options.installFullscreenMutationObserver(document.documentElement);
const ResizeObserverConstructor = window.ResizeObserver;
if (typeof ResizeObserverConstructor === "function") {
options.setFullscreenResizeObserver(
new ResizeObserverConstructor(() => {
options.scheduleUiWork({ force: true, passes: 1 });
})
);
options.refreshObservedResizeTargets();
}
}
return {
installFullscreenHooks
};
}
// src/features/fullscreen-mutation-observer.ts
var FULLSCREEN_OBSERVER_OPTIONS = {
subtree: true,
childList: true,
characterData: true,
attributes: true,
attributeFilter: ["class", "style", "id"]
};
function createFullscreenMutationObserver(options) {
let fullscreenMutationObserver = null;
function handleMutationRecords(records) {
let needsLayout = false;
let needsFeatures = false;
let needsSpectateSync = false;
for (const record of records) {
if (!needsLayout && mutationTouchesSelector(record, options.layoutTargetSelector)) {
needsLayout = true;
}
if (!needsFeatures && mutationTouchesSelector(record, options.featurePatchTargetSelector)) {
needsFeatures = true;
}
if (!needsSpectateSync && mutationTouchesSelector(record, ".jukebox")) {
needsSpectateSync = true;
}
if (needsLayout && needsFeatures && needsSpectateSync) {
break;
}
}
if (needsSpectateSync) {
options.syncSpectateControlsBottomWithJukebox();
}
if (needsLayout || needsFeatures) {
options.updateGameStartIndicator();
options.scheduleUiWork({
force: needsLayout,
features: needsFeatures,
passes: needsLayout ? options.settlePasses : 1
});
}
}
function installFullscreenMutationObserver(target = document.documentElement) {
if (!target) {
return;
}
fullscreenMutationObserver = new MutationObserver(handleMutationRecords);
fullscreenMutationObserver.observe(target, FULLSCREEN_OBSERVER_OPTIONS);
}
return {
installFullscreenMutationObserver
};
}
// src/features/fullscreen-refresh-controller.ts
function createFullscreenRefreshController(options) {
let lastFullscreenSignature = "";
function clearFullscreenSignature() {
lastFullscreenSignature = "";
}
function refreshFullscreen(force = false) {
if (!options.isFullscreenEnabled()) {
options.clearFullscreenLayoutStyles();
return false;
}
if (options.shouldWaitForNativeLayoutSeed()) {
window.setTimeout(() => options.scheduleUiWork({ force: true, passes: 1 }), 100);
return false;
}
const dimensions = options.getFullscreenDimensions();
const probe = options.getLayoutProbe();
const signature = options.buildFullscreenSignature(dimensions, probe);
const transitionOverlap = options.isMenuGameplayOverlap();
options.patchLobbyMusicController();
options.stopLobbyMusicIfNeeded();
options.updateGameStartIndicator();
options.enforceFullscreenLayout(dimensions);
options.installNativeFullscreenPatch();
options.setNativeFullscreenSize(dimensions);
if (!force && signature === lastFullscreenSignature && options.isRenderProbeAligned(probe, dimensions) && options.isNativeProbeAligned(probe, dimensions)) {
return false;
}
lastFullscreenSignature = signature;
const resizedNatively = options.runNativeResize(dimensions);
const postNativeProbe = options.getLayoutProbe();
if (!transitionOverlap && (!resizedNatively || !options.isRenderProbeAligned(postNativeProbe, dimensions))) {
options.resizeKnownFullscreenRenderers(dimensions);
}
options.enforceFullscreenLayout(dimensions);
lastFullscreenSignature = options.buildFullscreenSignature(dimensions, options.getLayoutProbe());
return true;
}
return {
clearFullscreenSignature,
refreshFullscreen
};
}
// src/features/fullscreen-work-scheduler.ts
function createFullscreenWorkScheduler(options) {
let scheduledWorkRaf = 0;
let scheduledWorkForce = false;
let scheduledWorkFeatures = false;
let scheduledWorkPasses = 0;
function scheduleUiWork({ force = false, features = false, passes = 1 } = {}) {
scheduledWorkForce = scheduledWorkForce || force;
scheduledWorkFeatures = scheduledWorkFeatures || features;
scheduledWorkPasses = Math.max(scheduledWorkPasses, Math.max(1, passes));
if (scheduledWorkRaf) {
return;
}
const runScheduledWork = () => {
scheduledWorkRaf = 0;
const shouldForce = scheduledWorkForce;
const shouldPatchFeatures = scheduledWorkFeatures;
const remainingPasses = scheduledWorkPasses;
scheduledWorkForce = false;
scheduledWorkFeatures = false;
scheduledWorkPasses = 0;
options.ensureGlobalStyle();
options.applyFeatureRootClasses();
options.installFullscreenHooks();
if (shouldPatchFeatures) {
options.applyPersistentFeatures();
}
options.refreshFullscreen(shouldForce);
options.refreshObservedResizeTargets();
if (remainingPasses > 1) {
scheduleUiWork({ force: true, passes: remainingPasses - 1 });
}
};
scheduledWorkRaf = document.hidden ? window.setTimeout(runScheduledWork, 0) : window.requestAnimationFrame(runScheduledWork);
}
return {
scheduleUiWork
};
}
// src/features/fullscreen-orchestration-bundle.ts
function createFullscreenOrchestrationBundle(options) {
const { clearFullscreenSignature, refreshFullscreen } = createFullscreenRefreshController({
buildFullscreenSignature: options.buildFullscreenSignature,
clearFullscreenLayoutStyles: options.clearFullscreenLayoutStyles,
enforceFullscreenLayout: options.enforceFullscreenLayout,
getFullscreenDimensions: options.getFullscreenDimensions,
getLayoutProbe: options.getLayoutProbe,
installNativeFullscreenPatch: options.installNativeFullscreenPatch,
isFullscreenEnabled: options.isFullscreenEnabled,
isMenuGameplayOverlap: options.isMenuGameplayOverlap,
isNativeProbeAligned: options.isNativeProbeAligned,
isRenderProbeAligned: options.isRenderProbeAligned,
patchLobbyMusicController: options.patchLobbyMusicController,
resizeKnownFullscreenRenderers: options.resizeKnownFullscreenRenderers,
runNativeResize: options.runNativeResize,
scheduleUiWork: (request) => scheduleUiWork(request),
setNativeFullscreenSize: options.setNativeFullscreenSize,
shouldWaitForNativeLayoutSeed: options.shouldWaitForNativeLayoutSeed,
stopLobbyMusicIfNeeded: options.stopLobbyMusicIfNeeded,
updateGameStartIndicator: options.updateGameStartIndicator
});
const { scheduleUiWork } = createFullscreenWorkScheduler({
applyFeatureRootClasses: options.applyFeatureRootClasses,
applyPersistentFeatures: options.applyPersistentFeatures,
ensureGlobalStyle: options.ensureGlobalStyle,
installFullscreenHooks: () => installFullscreenHooks(),
refreshFullscreen,
refreshObservedResizeTargets: options.refreshObservedResizeTargets
});
const { installFullscreenMutationObserver } = createFullscreenMutationObserver({
featurePatchTargetSelector: FEATURE_PATCH_TARGET_SELECTOR,
layoutTargetSelector: FULLSCREEN_LAYOUT_TARGET_SELECTOR,
scheduleUiWork,
settlePasses: FULLSCREEN_SETTLE_PASSES,
syncSpectateControlsBottomWithJukebox: options.syncSpectateControlsBottomWithJukebox,
updateGameStartIndicator: options.updateGameStartIndicator
});
const { installFullscreenHooks } = createFullscreenHookInstaller({
fullscreenSettlePasses: FULLSCREEN_SETTLE_PASSES,
installChatCommandAliasHooks: options.installChatCommandAliasHooks,
installChatEscapeHooks: options.installChatEscapeHooks,
installFullscreenMutationObserver,
installGameReadyHook: options.installGameReadyHook,
installGameStartIndicatorHooks: options.installGameStartIndicatorHooks,
installGameplayBackgroundFocusHooks: options.installGameplayBackgroundFocusHooks,
installQolboxMenuHooks: options.installQolboxMenuHooks,
installReserveSocketCaptureHook: options.installReserveSocketCaptureHook,
installTabFocusHooks: options.installTabFocusHooks,
isAudioEnabled: options.isAudioEnabled,
isGameStartAlertEnabled: options.isGameStartAlertEnabled,
isReserveEnabled: options.isReserveEnabled,
refreshObservedResizeTargets: options.refreshObservedResizeTargets,
resizeSettlePasses: RESIZE_SETTLE_PASSES,
scheduleUiWork,
setFullscreenResizeObserver: options.setFullscreenResizeObserver
});
return {
clearFullscreenSignature,
installFullscreenHooks,
installFullscreenMutationObserver,
refreshFullscreen,
scheduleUiWork: (request) => scheduleUiWork(request)
};
}
// src/hitbox/game-start-hooks.ts
var REMOTE_START_METHODS = ["KJ", "ZJ"];
var LOCAL_START_METHOD = "_J";
function isNativeCallable7(value) {
return typeof value === "function";
}
function isWrappedGameStartMethod(method) {
return isNativeCallable7(method) && readNativeReflectProperty(method, "__qolboxWrapped") === true;
}
function markWrappedGameStartMethod(method, originalMethod) {
setNativeReflectProperty(method, "__qolboxWrapped", true);
setNativeReflectProperty(method, "__qolboxOriginal", originalMethod);
}
function areGameStartSessionHooksInstalled(session) {
return [...REMOTE_START_METHODS, LOCAL_START_METHOD].every((methodName) => {
const method = readNativeProperty(session, methodName);
return !isNativeCallable7(method) || isWrappedGameStartMethod(method);
});
}
function installGameStartSessionHooks(session, callbacks) {
if (!isNativeObject(session)) {
return false;
}
let foundRemoteStartHandler = false;
for (const methodName of REMOTE_START_METHODS) {
const originalMethod = readNativeProperty(session, methodName);
if (!isNativeCallable7(originalMethod)) {
continue;
}
foundRemoteStartHandler = true;
if (isWrappedGameStartMethod(originalMethod)) {
continue;
}
const wrappedMethod = function wrappedGameStartSessionMethod(...args) {
const snapshot = callbacks.captureStartState();
let result;
try {
result = Reflect.apply(originalMethod, this, args);
} finally {
callbacks.handleStartAfterNativeEvent(snapshot, this);
}
return result;
};
markWrappedGameStartMethod(wrappedMethod, originalMethod);
setNativeReflectProperty(session, methodName, wrappedMethod);
}
const originalStartRequest = readNativeProperty(session, LOCAL_START_METHOD);
if (isNativeCallable7(originalStartRequest) && !isWrappedGameStartMethod(originalStartRequest)) {
const wrappedStartRequest = function wrappedLocalGameStartRequest(...args) {
callbacks.noteLocalStartRequest(this);
return Reflect.apply(originalStartRequest, this, args);
};
markWrappedGameStartMethod(wrappedStartRequest, originalStartRequest);
setNativeReflectProperty(session, LOCAL_START_METHOD, wrappedStartRequest);
}
const startRequest = readNativeProperty(session, LOCAL_START_METHOD);
return foundRemoteStartHandler || isNativeCallable7(startRequest) && isWrappedGameStartMethod(startRequest);
}
// src/features/game-start-display.ts
function createGameStartDisplayController() {
let faviconLink = null;
let originalFavicon = null;
function getIndicatorDocument() {
try {
const targetWindow = window.top;
if (targetWindow && targetWindow.document) {
return targetWindow.document;
}
} catch {
}
return document;
}
function getFaviconLink() {
return getIndicatorDocument().querySelector('link[rel~="icon"]');
}
function shouldPostToTop() {
const targetWindow = window.top;
if (!targetWindow || targetWindow === window) {
return false;
}
try {
return !targetWindow.document;
} catch {
return true;
}
}
function postToTop(payload) {
if (!shouldPostToTop()) {
return;
}
try {
window.top?.postMessage(
{
...payload,
feature: "gameStartIndicator",
source: "QOLBox"
},
"*"
);
} catch {
}
}
function saveFavicon() {
if (originalFavicon) {
return;
}
const targetDocument = getIndicatorDocument();
const link = getFaviconLink();
originalFavicon = link ? {
href: link.getAttribute("href"),
link,
type: link.getAttribute("type")
} : { href: null, link: null, type: null };
faviconLink = link || targetDocument.createElement("link");
if (!link) {
faviconLink.rel = "icon";
(targetDocument.head || targetDocument.documentElement).appendChild(faviconLink);
}
}
function setFavicon(active) {
saveFavicon();
if (!faviconLink) {
return;
}
if (active) {
faviconLink.setAttribute("href", GAME_START_FAVICON_HREF);
faviconLink.setAttribute("type", "image/svg+xml");
postToTop({ action: "favicon", active: true });
return;
}
if (originalFavicon?.href) {
faviconLink.setAttribute("href", originalFavicon.href);
} else {
faviconLink.removeAttribute("href");
}
if (originalFavicon?.type) {
faviconLink.setAttribute("type", originalFavicon.type);
} else {
faviconLink.removeAttribute("type");
}
postToTop({ action: "favicon", active: false });
}
function restoreFavicon() {
if (!originalFavicon || !faviconLink) {
return;
}
if (!originalFavicon.link) {
faviconLink.remove();
} else {
setFavicon(false);
}
faviconLink = null;
originalFavicon = null;
}
function getTitle() {
return getIndicatorDocument().title || "";
}
function setTitle(title) {
getIndicatorDocument().title = title;
postToTop({ action: "title", title });
}
function postClear() {
postToTop({ action: "clear" });
}
return {
getTitle,
postClear,
restoreFavicon,
setFavicon,
setTitle
};
}
// src/features/game-start-focus-hooks.ts
function createGameStartFocusHookInstaller({
handleAway,
handleInteractionFocus,
handleReturn,
handleVisibilityChange,
initializeFocusState
}) {
let hooksInstalled = false;
function installGameStartIndicatorHooks() {
if (hooksInstalled) {
return;
}
hooksInstalled = true;
initializeFocusState();
document.addEventListener("pointerdown", handleInteractionFocus, true);
document.addEventListener("mousedown", handleInteractionFocus, true);
document.addEventListener("click", handleInteractionFocus, true);
document.addEventListener("keydown", handleInteractionFocus, true);
window.addEventListener("focus", handleReturn, true);
window.addEventListener("blur", handleAway, true);
document.addEventListener("visibilitychange", handleVisibilityChange, true);
}
return {
installGameStartIndicatorHooks
};
}
// src/features/game-start-local-transition.ts
function createLocalPlayTransitionTracker(options) {
let localTransitionSession = null;
let localTransitionUntil = 0;
function clear() {
localTransitionSession = null;
localTransitionUntil = 0;
}
function note(session = options.getSession()) {
if (!session) {
return false;
}
localTransitionSession = session;
localTransitionUntil = Date.now() + options.timeoutMs;
return true;
}
function has(session = options.getSession()) {
if (!localTransitionSession || Date.now() > localTransitionUntil) {
clear();
return false;
}
return localTransitionSession === session;
}
function consume(session = options.getSession()) {
if (!has(session)) {
return false;
}
clear();
return true;
}
return {
clear,
consume,
has,
note
};
}
// src/features/game-start-timers.ts
function createGameStartTimerController() {
let indicatorTimer = 0;
let watchTimer = 0;
let endWatchTimer = 0;
let flashTimer = 0;
function clearIndicatorTimer() {
if (indicatorTimer) {
window.clearTimeout(indicatorTimer);
indicatorTimer = 0;
}
}
function clearWatchTimer() {
if (watchTimer) {
window.clearTimeout(watchTimer);
watchTimer = 0;
}
}
function clearEndWatchTimer() {
if (endWatchTimer) {
window.clearTimeout(endWatchTimer);
endWatchTimer = 0;
}
}
function clearFlashTimer() {
if (flashTimer) {
window.clearTimeout(flashTimer);
flashTimer = 0;
}
}
function setIndicatorTimer(callback, delayMs) {
indicatorTimer = window.setTimeout(() => {
indicatorTimer = 0;
callback();
}, delayMs);
}
function setWatchTimer(callback, delayMs) {
watchTimer = window.setTimeout(() => {
watchTimer = 0;
callback();
}, delayMs);
}
function setEndWatchTimer(callback, delayMs) {
endWatchTimer = window.setTimeout(() => {
endWatchTimer = 0;
callback();
}, delayMs);
}
function setFlashTimer(callback, delayMs) {
flashTimer = window.setTimeout(callback, delayMs);
}
function hasIndicatorTimer() {
return Boolean(indicatorTimer);
}
function hasWatchTimer() {
return Boolean(watchTimer);
}
function hasEndWatchTimer() {
return Boolean(endWatchTimer);
}
return {
clearEndWatchTimer,
clearFlashTimer,
clearIndicatorTimer,
clearWatchTimer,
hasEndWatchTimer,
hasIndicatorTimer,
hasWatchTimer,
setEndWatchTimer,
setFlashTimer,
setIndicatorTimer,
setWatchTimer
};
}
// src/features/game-start-indicator.ts
function getTitlePrefix(reason) {
return reason === "pulled" ? GAME_PULLED_TITLE_PREFIX : GAME_START_TITLE_PREFIX;
}
function createGameStartIndicatorController(options) {
const display = createGameStartDisplayController();
const localPlayTransition = createLocalPlayTransitionTracker({
getSession: options.getSession,
timeoutMs: options.localTransitionTimeoutMs
});
let sessionHookTarget = null;
let indicatorActive = false;
const timers = createGameStartTimerController();
let flashOn = false;
let originalTitle = "";
let wasPlayingWhenUnfocused = false;
let wasInLobbyWhenUnfocused = false;
let indicatorReason = "started";
let pageFocused = true;
const focusHooks = createGameStartFocusHookInstaller({
handleAway,
handleInteractionFocus,
handleReturn,
handleVisibilityChange,
initializeFocusState
});
function isIndicatorPageFocused() {
return pageFocused && options.isPageFocused();
}
function getPolledReason() {
return wasInLobbyWhenUnfocused ? "started" : "pulled";
}
function clearIndicatorTimer() {
timers.clearIndicatorTimer();
}
function clearWatchTimer() {
timers.clearWatchTimer();
}
function clearEndWatchTimer() {
timers.clearEndWatchTimer();
}
function clearFlashTimer() {
timers.clearFlashTimer();
}
function flashIndicator() {
if (!indicatorActive) {
return;
}
flashOn = !flashOn;
display.setTitle(`${getTitlePrefix(indicatorReason)}${originalTitle}`);
display.setFavicon(flashOn);
timers.setFlashTimer(flashIndicator, options.getFlashIntervalMs());
}
function scheduleEndWatch() {
if (!indicatorActive || timers.hasEndWatchTimer()) {
return;
}
timers.setEndWatchTimer(() => {
if (!indicatorActive) {
return;
}
if (!options.isPlayingMatch()) {
wasPlayingWhenUnfocused = false;
wasInLobbyWhenUnfocused = options.isPlayableLobby();
clearIndicator();
if (!isIndicatorPageFocused()) {
scheduleWatch();
}
return;
}
scheduleEndWatch();
}, options.endWatchIntervalMs);
}
function showIndicator(reason = "started") {
if (!options.isEnabled()) {
return;
}
if (indicatorActive) {
scheduleEndWatch();
return;
}
indicatorReason = reason;
originalTitle = stripGameStartTitlePrefix(display.getTitle());
indicatorActive = true;
flashOn = false;
clearFlashTimer();
flashIndicator();
scheduleEndWatch();
}
function clearIndicator() {
clearIndicatorTimer();
clearEndWatchTimer();
clearFlashTimer();
if (!indicatorActive) {
return;
}
display.setTitle(originalTitle);
display.restoreFavicon();
display.postClear();
originalTitle = "";
flashOn = false;
indicatorReason = "started";
indicatorActive = false;
}
function noteLocallyInitiatedPlayTransition(session = options.getSession()) {
if (localPlayTransition.note(session)) {
clearIndicatorTimer();
}
}
function hasPendingLocalPlayTransition(session = options.getSession()) {
return localPlayTransition.has(session);
}
function consumePendingLocalPlayTransition(session = options.getSession()) {
return localPlayTransition.consume(session);
}
function scheduleIndicator(reason = "pulled") {
if (!options.isEnabled() || timers.hasIndicatorTimer() || isIndicatorPageFocused()) {
return;
}
clearWatchTimer();
indicatorReason = reason;
timers.setIndicatorTimer(() => {
if (!isIndicatorPageFocused() && !wasPlayingWhenUnfocused && !hasPendingLocalPlayTransition() && options.isPlayingMatch() && !options.isPlayableLobby()) {
showIndicator(indicatorReason);
}
}, options.getIndicatorDelayMs());
}
function scheduleWatch() {
if (!options.isEnabled() || timers.hasWatchTimer() || isIndicatorPageFocused() || indicatorActive) {
return;
}
timers.setWatchTimer(() => {
updateGameStartIndicator();
if (!indicatorActive && !isIndicatorPageFocused()) {
scheduleWatch();
}
}, options.watchIntervalMs);
}
function handleStartAfterNativeEvent(wasPlayingMatch, wasPlayableLobby, session = options.getSession()) {
const startedPlaying = !wasPlayingMatch && options.isPlayingMatch();
if (startedPlaying && consumePendingLocalPlayTransition(session)) {
wasPlayingWhenUnfocused = true;
wasInLobbyWhenUnfocused = false;
clearWatchTimer();
clearIndicatorTimer();
return;
}
if (startedPlaying && wasPlayableLobby && !isIndicatorPageFocused()) {
clearWatchTimer();
clearIndicatorTimer();
showIndicator("started");
return;
}
updateGameStartIndicator();
}
function patchMultiplayerSessionGameStartHooks(session = options.getSession()) {
if (!options.isEnabled() || !session) {
return;
}
if (session === sessionHookTarget && areGameStartSessionHooksInstalled(session)) {
return;
}
if (session !== sessionHookTarget && !isIndicatorPageFocused() && options.isPlayingMatch()) {
wasPlayingWhenUnfocused = true;
wasInLobbyWhenUnfocused = false;
clearIndicatorTimer();
}
if (installGameStartSessionHooks(session, {
captureStartState: () => ({
wasPlayingMatch: options.isPlayingMatch(),
wasPlayableLobby: options.isPlayableLobby()
}),
handleStartAfterNativeEvent: ({ wasPlayingMatch, wasPlayableLobby }, eventSession) => {
handleStartAfterNativeEvent(wasPlayingMatch, wasPlayableLobby, eventSession);
},
noteLocalStartRequest: noteLocallyInitiatedPlayTransition
})) {
sessionHookTarget = session;
}
}
function updateGameStartIndicator() {
if (!options.isEnabled()) {
wasPlayingWhenUnfocused = false;
clearWatchTimer();
clearIndicator();
return;
}
const playingMatch = options.isPlayingMatch();
const playableLobby = options.isPlayableLobby();
patchMultiplayerSessionGameStartHooks();
if (isIndicatorPageFocused()) {
wasPlayingWhenUnfocused = playingMatch;
wasInLobbyWhenUnfocused = false;
return;
}
if (playableLobby) {
wasPlayingWhenUnfocused = false;
wasInLobbyWhenUnfocused = true;
scheduleWatch();
return;
}
if (!wasPlayingWhenUnfocused && playingMatch) {
scheduleIndicator(getPolledReason());
return;
}
if (!playingMatch) {
wasPlayingWhenUnfocused = false;
wasInLobbyWhenUnfocused = false;
clearWatchTimer();
clearIndicator();
scheduleWatch();
}
}
function handleReturn() {
if (!options.isEnabled()) {
clearIndicator();
return;
}
pageFocused = true;
clearWatchTimer();
clearIndicator();
wasPlayingWhenUnfocused = options.isPlayingMatch();
wasInLobbyWhenUnfocused = false;
}
function handleInteractionFocus() {
if (options.isEnabled() && !document.hidden) {
pageFocused = true;
wasPlayingWhenUnfocused = options.isPlayingMatch();
wasInLobbyWhenUnfocused = false;
}
}
function setGameStartPageFocused(value) {
pageFocused = Boolean(value);
}
function setGameStartWasPlayingWhenUnfocused(value) {
wasPlayingWhenUnfocused = Boolean(value);
}
function setGameStartWasInLobbyWhenUnfocused(value) {
wasInLobbyWhenUnfocused = Boolean(value);
}
function handleAway() {
if (!options.isEnabled()) {
return;
}
pageFocused = false;
patchMultiplayerSessionGameStartHooks();
wasPlayingWhenUnfocused = options.isPlayingMatch();
wasInLobbyWhenUnfocused = !wasPlayingWhenUnfocused && options.isPlayableLobby();
scheduleWatch();
}
function handleVisibilityChange() {
if (!options.isEnabled()) {
return;
}
if (document.hidden) {
handleAway();
} else {
handleReturn();
}
}
function initializeFocusState() {
pageFocused = options.isPageFocused();
if (!pageFocused) {
wasPlayingWhenUnfocused = options.isPlayingMatch();
wasInLobbyWhenUnfocused = !wasPlayingWhenUnfocused && options.isPlayableLobby();
}
}
function installGameStartIndicatorHooks() {
focusHooks.installGameStartIndicatorHooks();
}
function disableGameStartAlerts() {
wasPlayingWhenUnfocused = false;
wasInLobbyWhenUnfocused = false;
localPlayTransition.clear();
clearWatchTimer();
clearIndicator();
}
return {
clearGameStartIndicator: clearIndicator,
disableGameStartAlerts,
handleGameStartInteractionFocus: handleInteractionFocus,
hasPendingLocalPlayTransition,
installGameStartIndicatorHooks,
noteLocallyInitiatedPlayTransition,
patchMultiplayerSessionGameStartHooks,
setGameStartPageFocused,
setGameStartWasInLobbyWhenUnfocused,
setGameStartWasPlayingWhenUnfocused,
updateGameStartIndicator
};
}
// src/features/gameplay-state.ts
function createGameplayStateController(options) {
function hasReserveSuccessfulJoinLayer() {
return options.hasVisibleLayer(options.lobbyLayerSelector) || options.hasVisibleLayer(options.gameplayLayerSelector);
}
function isMenuGameplayOverlap() {
return options.hasVisibleLayer(options.menuLayerSelector) && options.hasVisibleLayer(options.playLayerSelector);
}
function isPageFocused() {
return !document.hidden && (!document.hasFocus || document.hasFocus());
}
function isCurrentPlayerSpectating(session = options.getSession()) {
const player = options.getSessionPlayer(session);
const team = options.getPlayerTeamState(player);
if (Number.isFinite(team)) {
return team === 0;
}
return options.hasVisibleLayer(options.spectateControlsSelector);
}
function isPlayableLobby() {
const session = options.getSession();
if (options.isSessionMatchActive(session)) {
return false;
}
if (options.isSessionLobbyActive(session)) {
return !isCurrentPlayerSpectating(session);
}
return options.hasVisibleLayer(options.lobbyLayerSelector) && !options.hasVisibleLayer(options.spectateControlsSelector);
}
function isPlayingMatch() {
const session = options.getSession();
if (options.isSessionMatchActive(session)) {
return !isCurrentPlayerSpectating(session);
}
return options.hasVisibleLayer(options.gameplayLayerSelector) && !options.hasVisibleLayer(options.spectateControlsSelector);
}
return {
hasReserveSuccessfulJoinLayer,
isCurrentPlayerSpectating,
isMenuGameplayOverlap,
isPageFocused,
isPlayableLobby,
isPlayingMatch
};
}
// src/features/gameplay-alert-feature-bundle.ts
function createGameplayAlertFeatureBundle(options) {
const gameplayState = createGameplayStateController({
gameplayLayerSelector: FULLSCREEN_GAMEPLAY_LAYER_SELECTOR,
lobbyLayerSelector: ".lobbyContainer",
menuLayerSelector: FULLSCREEN_MENU_LAYER_SELECTOR,
playLayerSelector: FULLSCREEN_PLAY_LAYER_SELECTOR,
spectateControlsSelector: ".spectateControls",
getPlayerTeamState,
getSession: getMultiplayerSession,
getSessionPlayer,
hasVisibleLayer,
isSessionLobbyActive,
isSessionMatchActive
});
const gameStartIndicator = createGameStartIndicatorController({
endWatchIntervalMs: GAME_START_END_WATCH_INTERVAL_MS,
getFlashIntervalMs: getAdvancedGameStartFlashIntervalMs,
getIndicatorDelayMs: getAdvancedGameStartAlertDelayMs,
localTransitionTimeoutMs: GAME_START_LOCAL_TRANSITION_TIMEOUT_MS,
watchIntervalMs: GAME_START_WATCH_INTERVAL_MS,
getSession: getMultiplayerSession,
isEnabled: options.isGameStartAlertEnabled,
isPageFocused: gameplayState.isPageFocused,
isPlayableLobby: gameplayState.isPlayableLobby,
isPlayingMatch: gameplayState.isPlayingMatch
});
return {
...gameplayState,
...gameStartIndicator
};
}
// src/features/in-game-chat-scroll.ts
var CHAT_READING_CLASS = "qolboxChatReading";
var CHAT_INTERACTIVE_CLASS = "qolboxChatInteractive";
var MAX_RETAINED_MESSAGES = 1e3;
var RESTORED_HISTORY_DISPLAY_MS = 6500;
function getChatContent(chat) {
return chat.querySelector(".content");
}
function hasVisibleGameplayCanvas() {
const canvas = document.querySelector("#pixiContainer canvas");
if (!canvas || typeof canvas.getBoundingClientRect !== "function") {
return false;
}
const rect = canvas.getBoundingClientRect();
const style = typeof window.getComputedStyle === "function" ? getComputedStyle(canvas) : null;
if (!style) {
return false;
}
return style.display !== "none" && style.visibility !== "hidden" && rect.width > 0 && rect.height > 0;
}
function getChatMessageViewportHeight(chat) {
const input = chat.querySelector(".input");
if (!input) {
return chat.clientHeight;
}
const chatRect = chat.getBoundingClientRect();
const inputRect = input.getBoundingClientRect();
return Math.max(20, inputRect.top - chatRect.top);
}
function getMaxChatOffset(chat, content) {
return Math.max(0, content.scrollHeight - getChatMessageViewportHeight(chat));
}
function getChatOpacity(chat) {
const opacity = Number(getComputedStyle(chat).opacity);
return Number.isFinite(opacity) ? opacity : 1;
}
function isChatShellVisible(chat) {
const rect = chat.getBoundingClientRect();
const style = getComputedStyle(chat);
return style.display !== "none" && style.visibility !== "hidden" && rect.width > 0 && rect.height > 0 && getChatOpacity(chat) > 0.04;
}
function isChatVisible(chat) {
const hasFocusedInput = Boolean(chat.querySelector(".input:focus"));
const hasMessageText = Boolean((getChatContent(chat)?.textContent || "").trim());
return isChatShellVisible(chat) && (hasFocusedInput || hasMessageText);
}
function hasLostRetainedHistory(content, state) {
const messages = getContentMessages(content);
return messages.signatures.length > 0 && messages.signatures.length < state.historySignatures.length;
}
function shouldRestoreRetainedHistory(chat, content, state) {
return state.historyInteractionActive || state.offsetPx > 0 || chat.classList.contains(CHAT_READING_CLASS) || chat.matches(":hover") || Boolean(chat.querySelector(".input:focus")) || hasLostRetainedHistory(content, state);
}
function hasFocusedChatInput(chat) {
return Boolean(chat.querySelector(".input:focus"));
}
function getMessageNodes(content) {
const nodes = Array.from(content.childNodes).filter((node) => (node.textContent || "").trim());
return nodes;
}
function getMessageHtml(node) {
if (node instanceof Element) {
return node.outerHTML;
}
const container = document.createElement("span");
container.textContent = node.textContent || "";
return container.outerHTML;
}
function getContentMessages(content) {
const html = getMessageNodes(content).map(getMessageHtml);
return {
html,
signatures: html.map((value) => `${value.length}:${value}`)
};
}
function getMessageSignatures(html) {
return html.map((value) => `${value.length}:${value}`);
}
function getOverlapLength(left, right) {
const maxOverlap = Math.min(left.length, right.length);
for (let overlap = maxOverlap; overlap > 0; overlap -= 1) {
let matches = true;
for (let index = 0; index < overlap; index += 1) {
if (left[left.length - overlap + index] !== right[index]) {
matches = false;
break;
}
}
if (matches) {
return overlap;
}
}
return 0;
}
function rememberMessageHtml(html, state) {
if (state.restoring) {
return false;
}
const signatures = getMessageSignatures(html);
if (!signatures.length) {
return false;
}
const overlap = getOverlapLength(state.historySignatures, signatures);
const newHtml = html.slice(overlap);
const newSignatures = signatures.slice(overlap);
state.historyHtml.push(...newHtml);
state.historySignatures.push(...newSignatures);
if (state.historyHtml.length > MAX_RETAINED_MESSAGES) {
const excess = state.historyHtml.length - MAX_RETAINED_MESSAGES;
state.historyHtml.splice(0, excess);
state.historySignatures.splice(0, excess);
}
return newHtml.length > 0;
}
function rememberChatMessages(content, state) {
if (state.restoredDomActive) {
return;
}
rememberMessageHtml(getContentMessages(content).html, state);
}
function rememberAddedChatNodes(nodes, state) {
const html = nodes.filter((node) => (node.textContent || "").trim()).map(getMessageHtml);
if (rememberMessageHtml(html, state)) {
state.historyVisibleUntil = performance.now() + RESTORED_HISTORY_DISPLAY_MS;
}
}
function restoreRetainedChatMessages(content, state) {
if (!state.historyHtml.length) {
return;
}
const messages = getContentMessages(content);
if (messages.signatures.length >= state.historySignatures.length) {
return;
}
state.restoring = true;
content.innerHTML = state.historyHtml.join("");
state.restoring = false;
state.restoredDomActive = true;
}
function clearRestoredChatDom(content, state) {
if (!state.historyInteractionActive) {
return;
}
state.restoring = true;
content.innerHTML = "";
state.restoring = false;
state.restoredDomActive = false;
state.historyInteractionActive = false;
state.historyVisibleUntil = 0;
if (state.fadeSyncTimerId) {
window.clearTimeout(state.fadeSyncTimerId);
state.fadeSyncTimerId = 0;
}
state.offsetPx = 0;
content.style.transform = "";
content.style.willChange = "";
}
function applyChatOffset(chat, content, state) {
const maxOffset = getMaxChatOffset(chat, content);
state.offsetPx = Math.max(0, Math.min(maxOffset, state.offsetPx));
if (state.offsetPx > 0) {
content.style.transform = `translateY(${Math.round(state.offsetPx)}px)`;
content.style.willChange = "transform";
chat.classList.add(CHAT_READING_CLASS);
chat.dataset.qolboxChatOffset = String(Math.round(state.offsetPx));
} else {
content.style.transform = "";
content.style.willChange = "";
delete chat.dataset.qolboxChatOffset;
if (!chat.matches(":hover")) {
chat.classList.remove(CHAT_READING_CLASS);
}
}
}
function createInGameChatScrollController() {
const patchedChats = /* @__PURE__ */ new WeakSet();
const chatStates = /* @__PURE__ */ new WeakMap();
const chatObservers = /* @__PURE__ */ new WeakMap();
let keyHooksInstalled = false;
let patchScheduled = false;
function isUserReadingChat(chat, state) {
return hasFocusedChatInput(chat) || state.offsetPx > 0 || chat.matches(":hover") || chat.classList.contains(CHAT_READING_CLASS);
}
function scheduleFadeSync(chat, state, delayMs) {
if (state.fadeSyncTimerId) {
return;
}
state.fadeSyncTimerId = window.setTimeout(() => {
state.fadeSyncTimerId = 0;
syncChat(chat);
}, Math.max(50, delayMs));
}
function syncChat(chat) {
if (!(chat instanceof HTMLElement)) {
return;
}
const content = getChatContent(chat);
const state = chatStates.get(chat);
if (!content || !state) {
return;
}
rememberChatMessages(content, state);
const visible = isChatVisible(chat) || isChatShellVisible(chat) && state.historyInteractionActive && state.historyHtml.length > 0;
if (visible) {
const now = performance.now();
const userReading = isUserReadingChat(chat, state);
if (userReading) {
state.historyVisibleUntil = 0;
if (state.fadeSyncTimerId) {
window.clearTimeout(state.fadeSyncTimerId);
state.fadeSyncTimerId = 0;
}
} else if (state.historyInteractionActive && state.historyVisibleUntil <= 0) {
state.historyVisibleUntil = now + RESTORED_HISTORY_DISPLAY_MS;
}
if (!userReading && state.historyVisibleUntil > 0 && now >= state.historyVisibleUntil) {
clearRestoredChatDom(content, state);
chat.classList.remove(CHAT_INTERACTIVE_CLASS);
chat.classList.remove(CHAT_READING_CLASS);
return;
}
chat.classList.add(CHAT_INTERACTIVE_CLASS);
if (shouldRestoreRetainedHistory(chat, content, state)) {
if (hasFocusedChatInput(chat) || state.offsetPx > 0) {
state.historyInteractionActive = true;
}
restoreRetainedChatMessages(content, state);
} else {
clearRestoredChatDom(content, state);
}
applyChatOffset(chat, content, state);
if (!userReading && state.historyVisibleUntil > 0) {
scheduleFadeSync(chat, state, state.historyVisibleUntil - now + 50);
}
return;
}
clearRestoredChatDom(content, state);
chat.classList.remove(CHAT_INTERACTIVE_CLASS);
if (state.offsetPx <= 0) {
chat.classList.remove(CHAT_READING_CLASS);
}
}
function scheduleChatSync(chat) {
const state = chatStates.get(chat);
if (state?.syncScheduled) {
return;
}
if (state) {
state.syncScheduled = true;
}
window.setTimeout(() => {
if (state) {
state.syncScheduled = false;
}
syncChat(chat);
window.requestAnimationFrame(() => syncChat(chat));
}, 0);
}
function schedulePatchInGameChatScroll(delayMs = 100) {
if (patchScheduled) {
return;
}
patchScheduled = true;
window.setTimeout(() => {
patchScheduled = false;
patchInGameChatScroll();
}, delayMs);
}
function patchChat(chat) {
if (patchedChats.has(chat)) {
return;
}
patchedChats.add(chat);
if (chat instanceof HTMLElement) {
chat.dataset.qolboxChatScrollPatched = "true";
}
const state = {
historyInteractionActive: false,
historyHtml: [],
historySignatures: [],
historyVisibleUntil: 0,
fadeSyncTimerId: 0,
offsetPx: 0,
restoredDomActive: false,
restoring: false,
syncScheduled: false
};
chatStates.set(chat, state);
chat.addEventListener("pointerenter", () => {
if (chat instanceof HTMLElement && isChatVisible(chat)) {
chat.classList.add(CHAT_READING_CLASS);
}
});
chat.addEventListener("pointerleave", () => {
if (state.offsetPx <= 0) {
chat.classList.remove(CHAT_READING_CLASS);
}
scheduleChatSync(chat);
});
chat.addEventListener(
"wheel",
(event) => {
const wheelEvent = event;
const target = event.target instanceof Element ? event.target : null;
if (target?.closest("input, textarea, select, button, .qolboxMenuOverlay")) {
return;
}
if (!(chat instanceof HTMLElement)) {
return;
}
const content2 = getChatContent(chat);
if (!content2) {
return;
}
state.offsetPx -= wheelEvent.deltaY;
applyChatOffset(chat, content2, state);
event.preventDefault();
event.stopPropagation();
},
{ capture: true, passive: false }
);
chat.addEventListener("focusin", () => scheduleChatSync(chat), true);
chat.addEventListener("focusout", () => scheduleChatSync(chat), true);
const chatObserver = new MutationObserver((records) => {
const content2 = getChatContent(chat);
const currentState = chatStates.get(chat);
if (content2 && currentState) {
for (const record of records) {
if (record.type === "childList" && record.target === content2 && record.addedNodes.length) {
rememberAddedChatNodes(Array.from(record.addedNodes), currentState);
}
}
}
scheduleChatSync(chat);
});
chatObserver.observe(chat, {
attributes: true,
attributeFilter: ["class", "style"]
});
const content = getChatContent(chat);
if (content) {
chatObserver.observe(content, { childList: true });
}
chatObservers.set(chat, chatObserver);
syncChat(chat);
}
function installKeyHooks() {
if (keyHooksInstalled) {
return;
}
keyHooksInstalled = true;
document.addEventListener("keydown", () => schedulePatchInGameChatScroll(0), true);
document.addEventListener("keyup", () => schedulePatchInGameChatScroll(0), true);
}
function patchInGameChatScroll() {
installKeyHooks();
if (!hasVisibleGameplayCanvas()) {
return;
}
for (const chat of document.querySelectorAll(".inGameChat")) {
patchChat(chat);
syncChat(chat);
}
}
return {
patchInGameChatScroll
};
}
// src/hitbox/mobile-controls-adapter.ts
function isNativeMobileMode() {
const game = window.a8;
return Boolean(readNativeProperty(game, "xm") || readNativeProperty(game, "PD"));
}
function isNativeTouchLobbyChatPrompt() {
return Boolean(readNativeProperty(window.a8, "xm"));
}
function getNativeMobileControls() {
return readNativeProperty(window.a8, "PD") ?? null;
}
function getControlSlot(controls, key) {
return readNativeProperty(controls, key);
}
function getControlInputState(control) {
const inputState = readNativeProperty(control, "hg");
return isNativeObject(inputState) ? inputState : null;
}
function getNativeMobileControlInputState(controls = getNativeMobileControls()) {
for (const key of ["oz", "rz", "az", "nz"]) {
const inputState = getControlInputState(getControlSlot(controls, key));
if (inputState) {
return inputState;
}
}
return null;
}
function getLiveMultiplayerInputState() {
const inputState = readNativePath(window.multiplayerSession, ["KR", "hg"]);
return isNativeObject(inputState) ? inputState : null;
}
function getNativeMobileAbilityButtonElements() {
const controls = getNativeMobileControls();
if (!controls) {
return [];
}
const buttons = [];
for (const key of ["oz", "rz", "az"]) {
const element = readNativeProperty(getControlSlot(controls, key), "hf");
if (element instanceof Element) {
buttons.push(element);
}
}
return buttons;
}
function setGrabInputPressed(inputState, pressed) {
if (!isNativeObject(inputState)) {
return false;
}
setNativeReflectProperty(inputState, "Fn", pressed);
return true;
}
function installNativeMobileControlHooks(hooks) {
const controls = getNativeMobileControls();
if (!isNativeObject(controls)) {
return false;
}
if (readNativeProperty(controls, "__qolboxMobileGrabPatched")) {
return true;
}
setNativeReflectProperty(controls, "__qolboxMobileGrabPatched", true);
const setInputState = readNativeProperty(controls, "ED");
if (typeof setInputState === "function") {
const originalSetInputState = setInputState;
setNativeReflectProperty(
controls,
"ED",
function wrappedMobileControlInputState(inputState, ...rest) {
hooks.onInputStateObserved(inputState);
const result = Reflect.apply(originalSetInputState, this, [inputState, ...rest]);
hooks.afterInputStateSet(inputState);
hooks.onControlsShown();
return result;
}
);
}
const showControls = readNativeProperty(controls, "NL");
if (typeof showControls === "function") {
const originalShowControls = showControls;
setNativeReflectProperty(controls, "NL", function wrappedMobileControlsShow(...args) {
const result = Reflect.apply(originalShowControls, this, args);
hooks.onControlsShown();
return result;
});
}
const hideControls = readNativeProperty(controls, "_L");
if (typeof hideControls === "function") {
const originalHideControls = hideControls;
setNativeReflectProperty(controls, "_L", function wrappedMobileControlsHide(...args) {
const result = Reflect.apply(originalHideControls, this, args);
hooks.onControlsHidden();
return result;
});
}
return true;
}
// src/features/gameplay-background-focus-events.ts
function readGameplayFocusProperty(source, property) {
return readObjectProperty(source, property);
}
function readStringProperty2(source, property) {
const value = readGameplayFocusProperty(source, property);
return typeof value === "string" ? value : "";
}
function readNumberProperty2(source, property) {
const value = readGameplayFocusProperty(source, property);
return typeof value === "number" ? value : Number(value);
}
function readGameplayFocusBooleanProperty(source, property) {
return readGameplayFocusProperty(source, property) === true;
}
function canPreventGameplayDefault(event) {
return typeof event === "object" && event !== null && typeof readGameplayFocusProperty(event, "preventDefault") === "function";
}
function canDispatchEvents(element) {
return element instanceof Element && typeof readGameplayFocusProperty(element, "dispatchEvent") === "function";
}
function canBlurGameplayFocusTarget(element) {
return typeof element === "object" && element !== null && typeof readGameplayFocusProperty(element, "blur") === "function";
}
function hasTabIndexApi(element) {
return element instanceof Element && typeof readGameplayFocusProperty(element, "hasAttribute") === "function" && typeof readGameplayFocusProperty(element, "tabIndex") === "number";
}
function ensureGameplayFocusTargetFocusable(element) {
if (hasTabIndexApi(element) && !element.hasAttribute("tabindex")) {
element.tabIndex = -1;
}
}
function getPointerEventType(event) {
return readStringProperty2(event, "type");
}
function isPrimaryGameplayMouseButton(event) {
const button = readGameplayFocusProperty(event, "button");
return button === void 0 || button === 0;
}
function clampPointerToRect(value, min, max) {
if (!Number.isFinite(value)) {
return (min + max) / 2;
}
return Math.max(min, Math.min(max, value));
}
function createForwardedPointerEvent(event, clientX, clientY) {
const eventType = getPointerEventType(event);
const commonInit = {
bubbles: true,
cancelable: true,
button: 0,
buttons: eventType === "click" ? 0 : 1,
clientX,
clientY,
ctrlKey: readGameplayFocusBooleanProperty(event, "ctrlKey"),
shiftKey: readGameplayFocusBooleanProperty(event, "shiftKey"),
altKey: readGameplayFocusBooleanProperty(event, "altKey"),
metaKey: readGameplayFocusBooleanProperty(event, "metaKey")
};
if (/^pointer/i.test(eventType) && typeof PointerEvent === "function") {
return new PointerEvent(eventType, {
...commonInit,
pointerId: readNumberProperty2(event, "pointerId") || 1,
pointerType: readStringProperty2(event, "pointerType") || "mouse",
isPrimary: readGameplayFocusProperty(event, "isPrimary") !== false
});
}
return new MouseEvent(eventType, commonInit);
}
function forwardGameplayPointerToCanvas(event, canvas) {
const eventType = getPointerEventType(event);
if (!canDispatchEvents(canvas) || !/^(?:pointerdown|mousedown|click)$/i.test(eventType)) {
return false;
}
const rect = canvas.getBoundingClientRect();
if (!rect.width || !rect.height) {
return false;
}
const rectRight = Number.isFinite(rect.right) ? rect.right : rect.left + rect.width;
const rectBottom = Number.isFinite(rect.bottom) ? rect.bottom : rect.top + rect.height;
const clientX = clampPointerToRect(readNumberProperty2(event, "clientX"), rect.left + 1, rectRight - 1);
const clientY = clampPointerToRect(readNumberProperty2(event, "clientY"), rect.top + 1, rectBottom - 1);
const forwardedEvent = createForwardedPointerEvent(event, clientX, clientY);
setObjectProperty(forwardedEvent, "__qolboxForwardedGameplayPointer", true);
canvas.dispatchEvent(forwardedEvent);
return true;
}
// src/features/gameplay-background-focus.ts
function createGameplayBackgroundFocusController(options) {
let hooksInstalled = false;
function focusActiveRenderCanvas() {
const canvas = options.getActiveRenderCanvas();
if (!canvas) {
return;
}
ensureGameplayFocusTargetFocusable(canvas);
focusElementWithoutScroll(canvas);
}
function captureGameplayInputFocus() {
if (typeof window.focus === "function") {
try {
window.focus();
} catch {
}
}
focusActiveRenderCanvas();
const activeElement = document.activeElement;
if (options.getActiveChatInput() === activeElement && canBlurGameplayFocusTarget(activeElement)) {
activeElement.blur();
}
const canvasConstructor = typeof HTMLCanvasElement === "function" ? HTMLCanvasElement : null;
if (!(canvasConstructor && document.activeElement instanceof canvasConstructor) && document.body) {
if (typeof document.body.hasAttribute !== "function" || !document.body.hasAttribute("tabindex")) {
document.body.tabIndex = -1;
}
focusElementWithoutScroll(document.body);
focusActiveRenderCanvas();
}
}
function forwardGameplayBackgroundPointer(event) {
return forwardGameplayPointerToCanvas(event, options.getActiveRenderCanvas());
}
function isGameplayRenderTarget(target) {
return Boolean(
target instanceof Element && (target.matches(options.renderCanvasSelector) || target.closest(options.renderLayerSelector))
);
}
function shouldCaptureGameplayBackgroundFocus(event) {
if (!options.isPlayingMatch() || !options.isQolboxMenuClosed() || options.getActiveChatInput() || readGameplayFocusBooleanProperty(event, "__qolboxForwardedGameplayPointer") || readGameplayFocusBooleanProperty(event, "defaultPrevented")) {
return false;
}
if (!isPrimaryGameplayMouseButton(event)) {
return false;
}
const target = readGameplayFocusProperty(event, "target");
if (!(target instanceof Element)) {
return false;
}
if (target.closest(options.exclusionSelector)) {
return false;
}
return !isGameplayRenderTarget(target);
}
function handleGameplayBackgroundFocus(event) {
if (!shouldCaptureGameplayBackgroundFocus(event)) {
return;
}
if (readGameplayFocusBooleanProperty(event, "cancelable") && canPreventGameplayDefault(event)) {
event.preventDefault();
}
captureGameplayInputFocus();
forwardGameplayBackgroundPointer(event);
window.setTimeout(() => {
if (shouldCaptureGameplayBackgroundFocus(event)) {
captureGameplayInputFocus();
}
}, 0);
}
function installGameplayBackgroundFocusHooks() {
if (hooksInstalled) {
return;
}
hooksInstalled = true;
document.addEventListener("pointerdown", handleGameplayBackgroundFocus, true);
document.addEventListener("mousedown", handleGameplayBackgroundFocus, true);
document.addEventListener("click", handleGameplayBackgroundFocus, true);
}
return {
captureGameplayInputFocus,
forwardGameplayBackgroundPointer,
handleGameplayBackgroundFocus,
installGameplayBackgroundFocusHooks,
shouldCaptureGameplayBackgroundFocus
};
}
// src/features/render-canvas-focus.ts
function createRenderCanvasFocusController(options) {
function resetBrowserScroll() {
try {
window.scrollTo(0, 0);
} catch {
}
document.documentElement.scrollTop = 0;
document.body.scrollTop = 0;
}
function focusActiveRenderCanvas() {
const canvas = options.getActiveRenderCanvas();
if (!canvas) {
return;
}
if (isTabbableElement(canvas) && !canvas.hasAttribute("tabindex")) {
canvas.tabIndex = -1;
}
options.focusElementWithoutScroll(canvas);
}
return {
focusActiveRenderCanvas,
resetBrowserScroll
};
}
// src/hitbox/chat-send-adapter.ts
function isNativeCallable8(value) {
return typeof value === "function";
}
function getAccurateNativeHelpText(text) {
return text === "/settings -- view all gameplay commands" ? "/settings -- view normal gameplay settings" : text;
}
function callNativeChatSend(nativeSendChat, session, message, rest) {
return Reflect.apply(nativeSendChat, session, [message, ...rest]);
}
function callNativeChatSendWithSettingsHelpCorrection(nativeSendChat, session, message, rest) {
const nativeShowStatus = readNativeProperty(session, "vG");
if (!isNativeObject(session) || !isNativeCallable8(nativeShowStatus)) {
return callNativeChatSend(nativeSendChat, session, message, rest);
}
const accurateShowStatus = function showAccurateNativeSettingsHelp(text, ...statusRest) {
return Reflect.apply(nativeShowStatus, this, [getAccurateNativeHelpText(text), ...statusRest]);
};
setNativeReflectProperty(session, "vG", accurateShowStatus);
try {
return callNativeChatSend(nativeSendChat, session, message, rest);
} finally {
setNativeReflectProperty(session, "vG", nativeShowStatus);
}
}
function installNativeChatSendInterceptor(session, options) {
if (!isNativeObject(session)) {
return false;
}
const nativeSendChat = readNativeProperty(session, "CJ");
if (!isNativeCallable8(nativeSendChat) || readNativeProperty(session, "__qolboxSlashCommandsPatched")) {
return false;
}
const wrappedSendChat = function wrappedQolboxSlashCommand(message, ...rest) {
return options.handleSend({
message,
rest,
session: this,
sendNativeChat: (nextMessage) => callNativeChatSend(nativeSendChat, this, nextMessage, rest),
sendNativeChatWithSettingsHelpCorrection: (nextMessage) => callNativeChatSendWithSettingsHelpCorrection(nativeSendChat, this, nextMessage, rest)
});
};
setNativeReflectProperty(session, "CJ", wrappedSendChat);
setNativeReflectProperty(session, "__qolboxSlashCommandsPatched", true);
setNativeReflectProperty(session, "__qolboxSlashCommandsOriginalCJ", nativeSendChat);
return true;
}
// src/features/slash-command-interceptor.ts
function expandNativeChatAlias(message) {
if (typeof message !== "string" || !areAdvancedCommandAliasesEnabled()) {
return message;
}
return message.replace(/^(\s*)\/rec(?=\s|$)/i, "$1/record");
}
function installSlashCommandInterceptor(session, dependencies) {
return installNativeChatSendInterceptor(session, {
handleSend(nativeChat) {
if (dependencies.areCommandsEnabled() && dependencies.handleCommand(nativeChat.message)) {
return void 0;
}
const commandsEnabled = dependencies.areCommandsEnabled();
let nextMessage = commandsEnabled ? expandNativeChatAlias(nativeChat.message) : nativeChat.message;
if (commandsEnabled && dependencies.prepareNativeCommand) {
const preparedMessage = dependencies.prepareNativeCommand(nextMessage);
if (preparedMessage === null) {
return void 0;
}
nextMessage = preparedMessage;
}
const isNativeHelp = commandsEnabled && /^\/help\s*$/.test(String(nextMessage || "").trim());
const result = isNativeHelp ? nativeChat.sendNativeChatWithSettingsHelpCorrection(nextMessage) : nativeChat.sendNativeChat(nextMessage);
if (isNativeHelp) {
dependencies.showHelp(nativeChat.session);
}
return result;
}
});
}
// src/features/input-focus-feature-bundle.ts
function createInputFocusFeatureBundle(options) {
const { focusActiveRenderCanvas, resetBrowserScroll } = createRenderCanvasFocusController({
focusElementWithoutScroll,
getActiveRenderCanvas: options.getActiveRenderCanvas
});
const chatInput = createChatInputController({
chatInputSelector: CHAT_INPUT_SELECTOR,
lobbyChatInputSelector: ".lobbyContainer .chatBox .input",
desktopLobbyChatPrompt: DESKTOP_LOBBY_CHAT_PROMPT,
touchLobbyChatPrompt: TOUCH_LOBBY_CHAT_PROMPT,
isChatFeatureEnabled: options.isChatFeatureEnabled,
areLobbyCommandsEnabled: options.areLobbyCommandsEnabled,
isTouchLobbyChatPrompt: isNativeTouchLobbyChatPrompt,
focusActiveRenderCanvas,
expandNativeChatAlias
});
const gameplayBackgroundFocus = createGameplayBackgroundFocusController({
exclusionSelector: GAMEPLAY_FOCUS_EXCLUSION_SELECTOR,
renderCanvasSelector: FULLSCREEN_RENDER_CANVAS_SELECTOR,
renderLayerSelector: FULLSCREEN_RENDER_LAYER_SELECTOR,
getActiveChatInput: chatInput.getActiveChatInput,
getActiveRenderCanvas: options.getActiveRenderCanvas,
isPlayingMatch: options.isPlayingMatch,
isQolboxMenuClosed: options.isQolboxMenuClosed
});
return {
...chatInput,
...gameplayBackgroundFocus,
focusActiveRenderCanvas,
resetBrowserScroll
};
}
// src/hitbox/player-appearance-adapter.ts
var PLAYER_NAME_FIELDS = ["name", "Nm", "username", "playerName"];
function getPlayerDisplayName(player) {
if (!isNativeObject(player)) {
return "";
}
for (const key of PLAYER_NAME_FIELDS) {
const value = readNativeProperty(player, key);
if (typeof value === "string" && value.trim()) {
return value.trim();
}
}
return "";
}
function getPlayerColorCandidates(player) {
if (!isNativeObject(player)) {
return [];
}
return Object.entries(player).filter(([key]) => /(colou?r|color|fill|tint)/i.test(key)).map(([, value]) => value);
}
// src/features/lobby-command-player-targets.ts
var GROUP_TARGETS = /* @__PURE__ */ new Set(["all", "playing", "spectators"]);
function normalizePlayerName(name) {
return String(name || "").replace(/\s+/g, " ").trim().toLowerCase();
}
function formatCommandPlayerName(player) {
const name = getPlayerName(player);
return name ? String(name) : "Player";
}
function findPlayerByName(name, session = getMultiplayerSession()) {
const query = normalizePlayerName(name);
if (!query) {
return { status: "missing", matches: [] };
}
const players = getSessionPlayers(session);
const tiers = [
players.filter(({ player }) => normalizePlayerName(getPlayerName(player)) === query),
players.filter(({ player }) => normalizePlayerName(getPlayerName(player)).startsWith(query)),
players.filter(({ player }) => normalizePlayerName(getPlayerName(player)).includes(query))
];
for (const matches of tiers) {
const uniqueMatches = [];
const seenIds = /* @__PURE__ */ new Set();
for (const match of matches) {
const id = String(match.id);
if (!seenIds.has(id)) {
seenIds.add(id);
uniqueMatches.push(match);
}
}
if (uniqueMatches.length === 1) {
return { status: "found", match: uniqueMatches[0], matches: uniqueMatches };
}
if (uniqueMatches.length > 1) {
return { status: "ambiguous", matches: uniqueMatches };
}
}
return { status: "missing", matches: [] };
}
function parseCommandTarget(argument) {
const value = String(argument || "").trim();
const quotedMatch = value.match(/^(["'])(.*)\1$/);
const normalizedValue = normalizePlayerName(value);
if (!quotedMatch && GROUP_TARGETS.has(normalizedValue)) {
return { group: normalizedValue, type: "group", value };
}
return { type: "player", value: quotedMatch ? quotedMatch[2] : value };
}
// src/hitbox/team-state.ts
var TEAM_STATE_SPECTATE = 0;
var TEAM_STATE_FFA = 1;
var TEAM_STATE_RED = 2;
var TEAM_STATE_BLUE = 3;
// src/hitbox/lobby-actions.ts
function getLobbySocket(session) {
return readNativePath(session, ["JD", "ZD"]);
}
function getCommandEventId() {
const eventId = readNativeProperty(window.a8, "VP");
return eventId === void 0 ? 1 : eventId;
}
function getCommandId(property, fallback) {
const commandId = readNativeProperty(window.a8, property);
return commandId === void 0 ? fallback : commandId;
}
function emitLobbyCommand(session, payload) {
const socket = getLobbySocket(session);
const emit = readNativeProperty(socket, "emit");
if (!isNativeObject(socket) || typeof emit !== "function") {
return false;
}
Reflect.apply(emit, socket, [getCommandEventId(), [...payload]]);
return true;
}
function requestOwnTeamChange(session, team) {
return emitLobbyCommand(session, [getCommandId("gE", 24), team]);
}
function movePlayerToTeam(session, playerId, team) {
return emitLobbyCommand(session, [getCommandId("jE", 47), { i: playerId, t: team }]);
}
function setTeamsLocked(session, locked) {
return emitLobbyCommand(session, [getCommandId("HE", 52), Boolean(locked)]);
}
function giveHostToPlayer(session, playerId) {
return emitLobbyCommand(session, [getCommandId("qolboxGiveHost", 44), playerId]);
}
// src/features/lobby-command-team-targets.ts
function getBulkTeamTargets(team, session = getMultiplayerSession(), targetGroup = "all") {
return getSessionPlayers(session).filter(({ player }) => {
const currentTeam = getPlayerTeamState(player);
if (currentTeam === Number(team)) {
return false;
}
if (targetGroup === "playing" && currentTeam === TEAM_STATE_SPECTATE) {
return false;
}
if (targetGroup === "spectators" && currentTeam !== TEAM_STATE_SPECTATE) {
return false;
}
if (team === TEAM_STATE_SPECTATE) {
return currentTeam !== TEAM_STATE_SPECTATE;
}
if (team === TEAM_STATE_FFA) {
return currentTeam === TEAM_STATE_SPECTATE;
}
return currentTeam === TEAM_STATE_SPECTATE || currentTeam === TEAM_STATE_RED || currentTeam === TEAM_STATE_BLUE;
});
}
function getSwitchableTeamPlayers(session = getMultiplayerSession()) {
return getSessionPlayers(session).filter(({ player }) => {
const team = getPlayerTeamState(player);
return team === TEAM_STATE_RED || team === TEAM_STATE_BLUE;
});
}
// src/features/lobby-command-team-state-text.ts
function getTeamStateName(team) {
switch (Number(team)) {
case TEAM_STATE_SPECTATE:
return "spectator";
case TEAM_STATE_RED:
return "red";
case TEAM_STATE_BLUE:
return "blue";
case TEAM_STATE_FFA:
default:
return "FFA";
}
}
function getBulkTeamActionName(team) {
if (team === TEAM_STATE_SPECTATE) {
return "spectate";
}
if (team === TEAM_STATE_FFA) {
return "join";
}
return `move to ${getTeamStateName(team)}`;
}
function formatBulkTeamMoveMessage(moved, team) {
return `Moving ${moved} eligible player${moved === 1 ? "" : "s"} to ${getTeamStateName(team)}.`;
}
// src/features/lobby-command-team-state-request.ts
function requestPlayerTeamState(session, playerId, team, localPlayerId = getLocalPlayerId(session)) {
return isSamePlayerId(playerId, localPlayerId) ? requestOwnTeamChange(session, team) : movePlayerToTeam(session, playerId, team);
}
// src/features/lobby-command-team-actions.ts
var SWITCH_SETTLE_MS = 900;
function createLobbyCommandTeamActions(dependencies) {
let switchLockedUntil = 0;
let switchUnlockTimer = 0;
function isSwitchingTeams() {
return Date.now() < switchLockedUntil;
}
function lockSwitchOperation() {
switchLockedUntil = Date.now() + SWITCH_SETTLE_MS;
if (switchUnlockTimer) {
window.clearTimeout(switchUnlockTimer);
}
switchUnlockTimer = window.setTimeout(() => {
switchUnlockTimer = 0;
if (Date.now() >= switchLockedUntil) {
switchLockedUntil = 0;
}
}, SWITCH_SETTLE_MS + 50);
}
function requestTeamState(playerId, team, { requireTeamMode = false } = {}) {
const session = getMultiplayerSession();
if (!hasLobbyPlayerState(session)) {
dependencies.showStatus("No active lobby or game session.");
return false;
}
if (requireTeamMode && !dependencies.isTeamMode(session)) {
dependencies.showStatus(`${getTeamStateName(team)} is only available in team modes.`);
return false;
}
const player = getSessionPlayerById(session, playerId);
if (!player) {
dependencies.showStatus("Could not find that player.");
return false;
}
if (getPlayerTeamState(player) === team) {
dependencies.showStatus(`${formatCommandPlayerName(player)} is already ${getTeamStateName(team)}.`);
return true;
}
const localPlayerId = getLocalPlayerId(session);
if (isSamePlayerId(playerId, localPlayerId)) {
if (team !== TEAM_STATE_SPECTATE && isSessionMatchActive(session) && dependencies.isCurrentPlayerSpectating(session)) {
dependencies.noteLocallyInitiatedPlayTransition(session);
}
if (!requestPlayerTeamState(session, playerId, team, localPlayerId)) {
dependencies.showStatus("Could not send the team change command.");
return false;
}
return true;
}
if (!isHostSession(session)) {
dependencies.showStatus("Only the host can move other players between teams.");
return false;
}
if (!requestPlayerTeamState(session, playerId, team, localPlayerId)) {
dependencies.showStatus("Could not send the team move command.");
return false;
}
return true;
}
function requestBulkTeamState(team, { requireTeamMode = false, targetGroup = "all" } = {}) {
const session = getMultiplayerSession();
if (!hasLobbyPlayerState(session)) {
dependencies.showStatus("No active lobby or game session.");
return false;
}
if (requireTeamMode && !dependencies.isTeamMode(session)) {
dependencies.showStatus(`${getTeamStateName(team)} is only available in team modes.`);
return false;
}
const players = getBulkTeamTargets(team, session, targetGroup);
if (!players.length) {
dependencies.showStatus(`No eligible players need to ${getBulkTeamActionName(team)}.`);
return true;
}
const localPlayerId = getLocalPlayerId(session);
if (!isHostSession(session) && players.some(({ id }) => !isSamePlayerId(id, localPlayerId))) {
dependencies.showStatus("Only the host can move other players.");
return false;
}
let moved = 0;
for (const { id } of players) {
if (requestPlayerTeamState(session, id, team, localPlayerId)) {
moved += 1;
}
}
if (moved !== players.length) {
dependencies.showStatus("Could not move every eligible player.");
return false;
}
dependencies.showStatus(formatBulkTeamMoveMessage(moved, team));
return true;
}
function switchTeamPlayers() {
const session = getMultiplayerSession();
if (!hasLobbyPlayerState(session)) {
dependencies.showStatus("No active lobby or game session.");
return false;
}
if (!dependencies.isTeamMode(session)) {
dependencies.showStatus("SWITCH is only available in team modes.");
return false;
}
if (!isHostSession(session)) {
dependencies.showStatus("Only the host can switch teams.");
return false;
}
if (isSwitchingTeams()) {
dependencies.showStatus("Team switch is still settling.");
return false;
}
const localPlayerId = getLocalPlayerId(session);
const players = getSwitchableTeamPlayers(session);
if (!players.length) {
dependencies.showStatus("There are no red or blue players to switch.");
return false;
}
let moved = 0;
let failed = 0;
const switchTargets = players.map(({ id, player }) => ({
id,
nextTeam: getPlayerTeamState(player) === TEAM_STATE_RED ? TEAM_STATE_BLUE : TEAM_STATE_RED
}));
lockSwitchOperation();
for (const { id, nextTeam } of switchTargets) {
if (requestPlayerTeamState(session, id, nextTeam, localPlayerId)) {
moved += 1;
} else {
failed += 1;
}
}
if (failed) {
dependencies.showStatus("Could not switch every player.");
return false;
}
dependencies.showStatus(`Switching ${moved} player${moved === 1 ? "" : "s"} between red and blue.`);
return true;
}
function setTeamsLocked2(locked) {
const session = getMultiplayerSession();
if (!hasLobbyPlayerState(session)) {
dependencies.showStatus("No active lobby or game session.");
return false;
}
if (!isHostSession(session)) {
dependencies.showStatus(`Only the host can ${locked ? "lock" : "unlock"} teams.`);
return false;
}
if (isTeamsLocked(session) === locked) {
dependencies.showStatus(`Teams are already ${locked ? "locked" : "unlocked"}.`);
return true;
}
if (!setTeamsLocked(session, locked)) {
dependencies.showStatus("Could not send the team-lock command.");
return false;
}
return true;
}
return {
isSwitchingTeams,
requestBulkTeamState,
requestTeamState,
setTeamsLocked: setTeamsLocked2,
switchTeamPlayers
};
}
// src/features/lobby-command-player-resolver.ts
function formatPlayerNameMatches(matches, getPlayerDisplayName2) {
return matches.map(({ player }) => getPlayerDisplayName2(player) || "Unnamed Player").filter(Boolean).slice(0, 4).join(", ");
}
function createCommandPlayerResolver(options) {
function resolveNamedCommandPlayer(argument, session = getMultiplayerSession()) {
const result = findPlayerByName(argument, session);
if (result.status === "missing") {
options.showStatus(`Couldn't find player '${argument}'.`);
return null;
}
if (result.status === "ambiguous") {
const matches = formatPlayerNameMatches(result.matches, options.getPlayerDisplayName);
options.showStatus(`Player name '${argument}' is ambiguous${matches ? `: ${matches}` : ""}.`);
return null;
}
return result.match;
}
return {
resolveNamedCommandPlayer
};
}
// src/features/lobby-command-host-actions.ts
function createLobbyCommandHostActions(dependencies) {
function handleHostSlashCommand(argument) {
const session = getMultiplayerSession();
if (!hasLobbyPlayerState(session)) {
dependencies.showStatus("No active lobby or game session.");
return false;
}
if (!argument) {
dependencies.showStatus("Usage: /host playername");
return false;
}
if (!isHostSession(session)) {
dependencies.showStatus("Only the host can give host to another player.");
return false;
}
const target = dependencies.resolveNamedCommandPlayer(argument, session);
if (!target) {
return false;
}
if (isSamePlayerId(target.id, getLocalPlayerId(session))) {
dependencies.showStatus("You are already host.");
return true;
}
if (!giveHostToPlayer(session, target.id)) {
dependencies.showStatus("Could not send the host transfer command.");
return false;
}
dependencies.showStatus(`Giving host to ${formatCommandPlayerName(target.player)}.`);
return true;
}
return {
handleHostSlashCommand
};
}
// src/hitbox/chat-adapter.ts
function canWriteChatLine(session) {
return hasNativeMethod(session, "vG");
}
function writeChatLine(session, line) {
return callNativeMethod(session, "vG", [line]).called;
}
// src/hitbox/host-settings-adapter.ts
var EXTRA_HOST_SETTINGS = [
["bbPower", "it"],
["bbRange", "st"],
["bbAngleVariance", "ht"],
["bbFireOn", "nt"],
["bbFireFramesLength", "at"],
["bbHideAfterFireFrames", "lt"],
["bbResetOn", "ut"],
["bbInitAmmoCost", "ot"],
["bbHoldAmmoCost", "rt"],
["egEnabled", "Ot"],
["egSize", "Rt"],
["egAge", "Dt"],
["egGravityScale", "Lt"],
["egRestitution", "Ut"],
["egExplodeRadius", "jt"],
["egStartSpin", "Wt"],
["egMaxThrowPower", "Jt"],
["egAmmoNeeded", "Gt"],
["egDelay1", "Ht"],
["egDelay2", "zt"],
["egDelayBeforeAmmoUse", "Yt"],
["egAimRate", "qt"],
["egShape", "Vt"]
];
function getHostSettingsObject(session) {
return readNativePath(session, ["JD", "$L"]) || readNativePath(session, ["KR", "uL", "settings", 0]) || readNativePath(session, ["TJ", "JD", "tP", 0, "state", "settings", 0]) || readNativePath(session, ["JD", "tP", 0, "state", "settings", 0]) || null;
}
function readAllHostSettingLines(session) {
const settings = getHostSettingsObject(session);
if (!isNativeObject(settings)) {
return null;
}
const nativeResult = callNativeMethod(settings, "pi");
if (!nativeResult.called || !Array.isArray(nativeResult.result) || !nativeResult.result.every((line) => typeof line === "string")) {
return null;
}
const lines = nativeResult.result.slice();
if (lines[lines.length - 1] === "===") {
lines.pop();
}
for (const [name, field] of EXTRA_HOST_SETTINGS) {
const value = readNativeProperty(settings, field);
if (value !== void 0) {
lines.push(`${name}: ${String(value)}`);
}
}
lines.push("===");
return lines;
}
// src/features/lobby-command-help.ts
var QOLBOX_COMMAND_HELP_LINES = [
"QOLBox commands:",
"/spec -- spectate yourself",
"/spec playername -- spectate a player",
"/spec all|playing|spectators -- spectate a group",
"/join -- join play yourself (non-team modes)",
"/join playername -- join a player (non-team modes)",
"/join all|playing|spectators -- join a group (non-team modes)",
"/red -- join red yourself (team modes)",
"/red playername -- move a player to red (team modes)",
"/red all|playing|spectators -- move a group to red (team modes)",
"/blue -- join blue yourself (team modes)",
"/blue playername -- move a player to blue (team modes)",
"/blue all|playing|spectators -- move a group to blue (team modes)",
"/switch -- swap red and blue teams",
"/lock -- lock team switching",
"/unlock -- unlock team switching",
"/host playername -- give host to a player",
"/start -- start the game",
"/end -- end the current game",
"/restart -- end and start a new game",
"/r -- same as /restart",
"/record -- record the current replay",
"/rec -- same as /record",
"/settings -- view normal gameplay settings",
"/settings all -- view normal and hidden gameplay settings",
"Native /kick and /ban accept exact or unique partial player names.",
'Tip: to target players named all, playing, or spectators, quote the name: /spec "all".'
];
function writeQolboxCommandHelp(session) {
for (const line of QOLBOX_COMMAND_HELP_LINES) {
writeChatLine(session, line);
}
}
// src/features/lobby-command-output-actions.ts
function createLobbyCommandOutputActions(dependencies) {
function showAllHostSettings() {
const session = getMultiplayerSession();
const lines = readAllHostSettingLines(session);
if (!lines || !canWriteChatLine(session)) {
dependencies.showStatus("Could not read the current host settings.", session);
return false;
}
lines.forEach((line) => writeChatLine(session, line));
return true;
}
function showQolboxCommandHelp(session = getMultiplayerSession()) {
writeQolboxCommandHelp(session);
}
return { showAllHostSettings, showQolboxCommandHelp };
}
// src/features/lobby-command-play-actions.ts
function createLobbyCommandPlayActions(dependencies) {
function handleJoinSlashCommand(argument) {
const session = getMultiplayerSession();
if (!hasLobbyPlayerState(session)) {
dependencies.showStatus("No active lobby or game session.");
return false;
}
if (dependencies.isTeamMode(session)) {
dependencies.showStatus("Use /red or /blue to join in team modes.");
return false;
}
const targetArgument = parseCommandTarget(argument);
if (targetArgument.type === "group") {
return dependencies.requestBulkTeamState(TEAM_STATE_FFA, { targetGroup: targetArgument.group });
}
const target = argument ? dependencies.resolveNamedCommandPlayer(targetArgument.value, session) : { id: getLocalPlayerId(session), player: null };
if (!target) {
return false;
}
const player = getSessionPlayerById(session, target.id);
if (player && getPlayerTeamState(player) === TEAM_STATE_FFA) {
dependencies.showStatus(`${formatCommandPlayerName(player)} is already playing.`);
return true;
}
return dependencies.requestTeamState(target.id, TEAM_STATE_FFA);
}
function handleSpecSlashCommand(argument) {
const session = getMultiplayerSession();
const targetArgument = parseCommandTarget(argument);
if (targetArgument.type === "group") {
return dependencies.requestBulkTeamState(TEAM_STATE_SPECTATE, { targetGroup: targetArgument.group });
}
const target = argument ? dependencies.resolveNamedCommandPlayer(targetArgument.value, session) : { id: getLocalPlayerId(session), player: null };
return target ? dependencies.requestTeamState(target.id, TEAM_STATE_SPECTATE) : false;
}
return {
handleJoinSlashCommand,
handleSpecSlashCommand
};
}
// src/features/lobby-command-actions.ts
function createLobbyCommandActions(dependencies) {
const teamActions = createLobbyCommandTeamActions({
isCurrentPlayerSpectating: dependencies.isCurrentPlayerSpectating,
isTeamMode: dependencies.isTeamMode,
noteLocallyInitiatedPlayTransition: dependencies.noteLocallyInitiatedPlayTransition,
showStatus: dependencies.showStatus
});
const playerResolver = createCommandPlayerResolver({
getPlayerDisplayName: dependencies.getPlayerDisplayName,
showStatus: dependencies.showStatus
});
const outputActions = createLobbyCommandOutputActions({
showStatus: dependencies.showStatus
});
const hostActions = createLobbyCommandHostActions({
resolveNamedCommandPlayer: playerResolver.resolveNamedCommandPlayer,
showStatus: dependencies.showStatus
});
const playActions = createLobbyCommandPlayActions({
isTeamMode: dependencies.isTeamMode,
requestBulkTeamState,
requestTeamState,
resolveNamedCommandPlayer: playerResolver.resolveNamedCommandPlayer,
showStatus: dependencies.showStatus
});
function requestTeamState(playerId, team, options = {}) {
return teamActions.requestTeamState(playerId, team, options);
}
function requestBulkTeamState(team, options = {}) {
return teamActions.requestBulkTeamState(team, options);
}
function switchTeamPlayers() {
return teamActions.switchTeamPlayers();
}
function setTeamsLocked2(locked) {
return teamActions.setTeamsLocked(locked);
}
function isSwitchingTeams() {
return teamActions.isSwitchingTeams();
}
function handleTeamSlashCommand(commandName, argument) {
const session = getMultiplayerSession();
const targetTeam = commandName === "/blue" ? TEAM_STATE_BLUE : TEAM_STATE_RED;
if (!argument) {
return requestTeamState(getLocalPlayerId(session), targetTeam, { requireTeamMode: true });
}
const targetArgument = parseCommandTarget(argument);
if (targetArgument.type === "group") {
return requestBulkTeamState(targetTeam, { requireTeamMode: true, targetGroup: targetArgument.group });
}
if (!dependencies.isTeamMode(session)) {
dependencies.showStatus(`${getTeamStateName(targetTeam)} is only available in team modes.`);
return false;
}
const target = playerResolver.resolveNamedCommandPlayer(targetArgument.value, session);
return target ? requestTeamState(target.id, targetTeam, { requireTeamMode: true }) : false;
}
function handleJoinSlashCommand(argument) {
return playActions.handleJoinSlashCommand(argument);
}
function handleSpecSlashCommand(argument) {
return playActions.handleSpecSlashCommand(argument);
}
return {
findPlayerByName,
handleHostSlashCommand: hostActions.handleHostSlashCommand,
handleJoinSlashCommand,
handleSpecSlashCommand,
handleTeamSlashCommand,
normalizePlayerName,
requestBulkTeamState,
requestTeamState,
setTeamsLocked: setTeamsLocked2,
showAllHostSettings: outputActions.showAllHostSettings,
showQolboxCommandHelp: outputActions.showQolboxCommandHelp,
isSwitchingTeams,
switchTeamPlayers
};
}
// src/hitbox/match-actions.ts
function canEndMatch(session) {
return hasNativeMethod(session, "PJ");
}
function endMatch(session) {
return callNativeMethod(session, "PJ").called;
}
function canStartMatch(session) {
return hasNativeMethod(session, "_J");
}
function startMatch(session) {
return callNativeMethod(session, "_J").called;
}
// src/features/lobby-command-dispatcher.ts
function hasTextValue(value) {
return typeof value === "object" && value !== null && "value" in value && typeof value.value === "string";
}
function clearHandledChatDraft() {
for (const input of document.querySelectorAll(".inGameChat .input, .lobbyContainer .chatBox .input")) {
if (hasTextValue(input)) {
input.value = "";
}
}
}
function createLobbyCommandDispatcher(dependencies) {
function endCurrentGame() {
const session = getMultiplayerSession();
if (!isSessionMatchActive(session)) {
dependencies.showStatus("There is no active game to end.");
return false;
}
if (!isHostSession(session)) {
dependencies.showStatus("Only the host can end the current game.");
return false;
}
if (!canEndMatch(session)) {
dependencies.showStatus("The native end-game action is unavailable.");
return false;
}
clearHandledChatDraft();
endMatch(session);
return true;
}
function restartCurrentGame() {
const session = getMultiplayerSession();
if (!isSessionMatchActive(session)) {
dependencies.showStatus("There is no active game to restart.");
return false;
}
if (!isHostSession(session)) {
dependencies.showStatus("Only the host can restart the current game.");
return false;
}
if (!canEndMatch(session) || !canStartMatch(session)) {
dependencies.showStatus("The native restart actions are unavailable.");
return false;
}
if (dependencies.areGameStartAlertsEnabled()) {
dependencies.installStartAlertHooks(session);
}
clearHandledChatDraft();
endMatch(session);
dependencies.noteLocallyInitiatedPlayTransition(session);
startMatch(session);
return true;
}
function startCurrentGame() {
const session = getMultiplayerSession();
if (isSessionMatchActive(session)) {
dependencies.showStatus("There is already an active game.");
return false;
}
if (!isHostSession(session)) {
dependencies.showStatus("Only the host can start the game.");
return false;
}
if (!canStartMatch(session)) {
dependencies.showStatus("The native start-game action is unavailable.");
return false;
}
if (dependencies.areGameStartAlertsEnabled()) {
dependencies.installStartAlertHooks(session);
}
clearHandledChatDraft();
dependencies.noteLocallyInitiatedPlayTransition(session);
startMatch(session);
return true;
}
function handleQolboxSlashCommand(message) {
const text = String(message || "").trim();
const match = text.match(/^\/(switch|lock|unlock|spec|red|blue|join|host|start|end|restart|r|settings)(?:\s+(.+))?$/i);
if (!match) {
return false;
}
const commandName = `/${match[1].toLowerCase()}`;
const argument = (match[2] || "").trim();
if (commandName === "/r" && !areAdvancedCommandAliasesEnabled()) {
return false;
}
if (commandName === "/switch") {
if (argument) {
dependencies.showStatus("/switch does not take a player name.");
return true;
}
dependencies.actions.switchTeamPlayers();
return true;
}
if (commandName === "/lock" || commandName === "/unlock") {
if (argument) {
dependencies.showStatus(`${commandName} does not take an argument.`);
return true;
}
dependencies.actions.setTeamsLocked(commandName === "/lock");
return true;
}
if (commandName === "/spec") {
dependencies.actions.handleSpecSlashCommand(argument);
return true;
}
if (commandName === "/join") {
dependencies.actions.handleJoinSlashCommand(argument);
return true;
}
if (commandName === "/host") {
dependencies.actions.handleHostSlashCommand(argument);
return true;
}
if (commandName === "/end") {
if (argument) {
dependencies.showStatus("/end does not take an argument.");
return true;
}
endCurrentGame();
return true;
}
if (commandName === "/start") {
if (argument) {
dependencies.showStatus("/start does not take an argument.");
return true;
}
startCurrentGame();
return true;
}
if (commandName === "/restart" || commandName === "/r") {
if (argument) {
dependencies.showStatus(`${commandName} does not take an argument.`);
return true;
}
restartCurrentGame();
return true;
}
if (commandName === "/settings") {
if (dependencies.actions.normalizePlayerName(argument) !== "all") {
return false;
}
dependencies.actions.showAllHostSettings();
return true;
}
dependencies.actions.handleTeamSlashCommand(commandName, argument);
return true;
}
return { endCurrentGame, handleQolboxSlashCommand, restartCurrentGame, startCurrentGame };
}
// src/features/qolbox-chat-status.ts
function createQolboxChatStatusWriter(options) {
function showQolboxChatStatus(message, session = options.getSession()) {
writeChatLine(session, `* ${message}`);
}
return {
showQolboxChatStatus
};
}
// src/features/player-popup-dismissal.ts
var dismissalListenersInstalled = false;
function getRightClickMenus() {
return Array.from(document.querySelectorAll(".rightClickMenu"));
}
function removePlayerPopups() {
const menus = getRightClickMenus();
for (const menu of menus) {
menu.remove();
}
return menus.length > 0;
}
function isInsidePopupActionList(target) {
return target instanceof Element && Boolean(target.closest(".rightClickMenu .container"));
}
function handlePointerOutsidePlayerPopup(event) {
if (!getRightClickMenus().length || isInsidePopupActionList(event.target)) {
return;
}
removePlayerPopups();
}
function handlePlayerPopupEscape(event) {
if (event.key !== "Escape" || !removePlayerPopups()) {
return;
}
event.preventDefault();
event.stopImmediatePropagation();
}
function installPlayerPopupDismissal() {
if (dismissalListenersInstalled) {
return;
}
dismissalListenersInstalled = true;
document.addEventListener("pointerdown", handlePointerOutsidePlayerPopup, true);
document.addEventListener("mousedown", handlePointerOutsidePlayerPopup, true);
document.addEventListener("keydown", handlePlayerPopupEscape, true);
}
// src/features/switch-teams-button.ts
function createSwitchTeamsButtonController(dependencies) {
function removeSwitchTeamsButton() {
for (const button of document.querySelectorAll(".qolboxSwitchTeamsButton")) {
button.remove();
}
}
function handleSwitchTeamsButtonClick(event) {
event.preventDefault();
event.stopPropagation();
if (dependencies.isSwitching()) {
return;
}
dependencies.switchTeams();
}
function patchSwitchTeamsButton() {
const session = getMultiplayerSession();
const container = document.querySelector(".lobbyContainer .playerBox .teamsButtonContainer");
if (!dependencies.isEnabled() || !container || !isHostSession(session) || !dependencies.isTeamMode(session) || !isElementVisible(container)) {
removeSwitchTeamsButton();
return false;
}
let button = container.querySelector(".qolboxSwitchTeamsButton");
if (!button) {
button = document.createElement("div");
button.className = "teamButton qolboxSwitchTeamsButton";
button.dataset.qolboxSwitchTeams = "true";
}
const switching = dependencies.isSwitching();
button.onclick = handleSwitchTeamsButtonClick;
button.classList.toggle("qolboxSwitchTeamsButtonBusy", switching);
button.setAttribute("aria-disabled", switching ? "true" : "false");
button.setAttribute("title", switching ? "Switching teams..." : "Switch red and blue teams");
const label = switching ? "SWITCHING" : "SWITCH";
if (button.textContent !== label) {
button.textContent = label;
}
const blueButton = Array.from(container.querySelectorAll(".teamButton")).find(
(element) => /^\s*JOIN\s+BLUE\s*$/i.test(element.textContent || "")
);
if (blueButton && blueButton !== button && button.nextElementSibling !== blueButton) {
container.insertBefore(button, blueButton);
} else if (button.parentElement !== container) {
container.appendChild(button);
}
return true;
}
return { patchSwitchTeamsButton, removeSwitchTeamsButton };
}
// src/features/team-mode-detector.ts
var TEAM_MODE_VALUES = /* @__PURE__ */ new Set([3, 4, 5]);
function getSelectedLobbyModeValue() {
const modeSelect = document.querySelector("select.modeDropdown.left, select.modeDropdown");
if (!modeSelect) {
return null;
}
const mode = Number(modeSelect.value);
return Number.isFinite(mode) ? mode : null;
}
function hasVisibleTeamModeControls() {
for (const element of document.querySelectorAll("button, .button, .bottomButton, .item, div")) {
if (!isElementVisible(element)) {
continue;
}
if (/^\s*JOIN\s+(RED|BLUE)\s*$/i.test(element.textContent || "")) {
return true;
}
}
return false;
}
function isTeamMode(session = getMultiplayerSession()) {
if (isNativeTeamMode(session)) {
return true;
}
const selectedMode = getSelectedLobbyModeValue();
if (TEAM_MODE_VALUES.has(selectedMode ?? Number.NaN)) {
return true;
}
if (hasVisibleTeamModeControls()) {
return true;
}
return getSessionPlayers(session).some(({ player }) => {
const team = getPlayerTeamState(player);
return team === TEAM_STATE_RED || team === TEAM_STATE_BLUE;
});
}
// src/features/lobby-commands-feature-bundle.ts
function createLobbyCommandsFeatureBundle(options) {
const { showQolboxChatStatus } = createQolboxChatStatusWriter({
getSession: getMultiplayerSession
});
const lobbyCommandActions = createLobbyCommandActions({
getPlayerDisplayName,
isCurrentPlayerSpectating: options.isCurrentPlayerSpectating,
isTeamMode,
noteLocallyInitiatedPlayTransition: options.noteLocallyInitiatedPlayTransition,
showStatus: showQolboxChatStatus
});
const dispatcher = createLobbyCommandDispatcher({
actions: lobbyCommandActions,
areGameStartAlertsEnabled: options.areGameStartAlertsEnabled,
installStartAlertHooks: options.installStartAlertHooks,
noteLocallyInitiatedPlayTransition: options.noteLocallyInitiatedPlayTransition,
showStatus: showQolboxChatStatus
});
const switchTeamsButton = createSwitchTeamsButtonController({
isEnabled: options.areLobbyCommandsEnabled,
isSwitching: lobbyCommandActions.isSwitchingTeams,
isTeamMode,
switchTeams: lobbyCommandActions.switchTeamPlayers
});
function prepareNativePlayerCommand(message) {
if (typeof message !== "string") {
return message;
}
const match = message.match(/^(\s*)\/(kick|ban)\s+(.+?)\s*$/i);
if (!match) {
return message;
}
const [, leadingSpace, commandName, rawTarget] = match;
const quotedTarget = rawTarget.match(/^(["'])(.*)\1$/);
const targetName = quotedTarget ? quotedTarget[2] : rawTarget;
const result = lobbyCommandActions.findPlayerByName(targetName);
if (result.status === "found") {
return `${leadingSpace}/${commandName.toLowerCase()} ${formatCommandPlayerName(result.match.player)}`;
}
if (result.status === "ambiguous") {
const matches = result.matches.map(({ player }) => getPlayerDisplayName(player) || "Unnamed Player").slice(0, 4).join(", ");
showQolboxChatStatus(`Player name '${targetName}' is ambiguous${matches ? `: ${matches}` : ""}.`);
return null;
}
showQolboxChatStatus(`Couldn't find player '${targetName}'.`);
return null;
}
function patchSlashCommands() {
return installSlashCommandInterceptor(getMultiplayerSession(), {
areCommandsEnabled: options.areLobbyCommandsEnabled,
handleCommand: dispatcher.handleQolboxSlashCommand,
prepareNativeCommand: prepareNativePlayerCommand,
showHelp: lobbyCommandActions.showQolboxCommandHelp
});
}
return {
...lobbyCommandActions,
...dispatcher,
...switchTeamsButton,
installPlayerPopupDismissal,
patchSlashCommands,
showQolboxChatStatus
};
}
// src/features/mobile-grab-context.ts
function getMobileAbilityButtons() {
const nativeButtons = getNativeMobileAbilityButtonElements();
if (nativeButtons.length) {
return nativeButtons;
}
return Array.from(document.querySelectorAll(".buttonArea.bat, .buttonArea.push, .buttonArea.rocket"));
}
function areNativeMobileAbilityButtonsVisible() {
const buttons = getMobileAbilityButtons();
return buttons.length > 0 && buttons.every(isElementVisible);
}
function isMobileGameModeContext() {
return isNativeMobileMode() || areNativeMobileAbilityButtonsVisible();
}
function isMobileQolboxMenuContextValue() {
if (isNativeMobileMode()) {
return true;
}
const nav = window.navigator || (typeof navigator !== "undefined" ? navigator : null);
const touchPoints = Number(
nav && (readObjectProperty(nav, "maxTouchPoints") || readObjectProperty(nav, "msMaxTouchPoints") || 0)
);
if (!touchPoints || typeof window.matchMedia !== "function") {
return false;
}
try {
return window.matchMedia("(hover: none) and (pointer: coarse)").matches;
} catch {
return false;
}
}
// src/features/mobile-grab-button-element.ts
function createMobileGrabButtonElement(container, handlers) {
const button = document.createElement("div");
button.className = "buttonArea qolboxMobileGrabButton";
button.setAttribute("aria-label", "Grab");
button.setAttribute("role", "button");
button.dataset.qolboxMobileGrab = "true";
button.addEventListener("touchstart", handlers.onTouchStart, {
passive: false,
capture: true
});
button.addEventListener("pointerdown", handlers.onPointerStart, true);
container.appendChild(button);
return button;
}
function hideMobileGrabButtonElement(button) {
if (button && button.style) {
button.style.display = "none";
}
}
function removeMobileGrabButtonElement(button) {
hideMobileGrabButtonElement(button);
if (button && button.isConnected) {
button.remove();
}
return null;
}
// src/features/mobile-grab-input.ts
var MOBILE_GRAB_FALLBACK_KEY = "v";
var MOBILE_GRAB_FALLBACK_CODE = "KeyV";
var MOBILE_GRAB_FALLBACK_KEY_CODE = 86;
function createMobileGrabInputController() {
let mobileGrabPointerDown = false;
let mobileGrabInputState = null;
let mobileGrabControlledInputState = null;
let mobileGrabKeyboardFallbackActive = false;
function getMobileGrabInputState() {
const controlInputState = getNativeMobileControlInputState(getNativeMobileControls());
if (controlInputState) {
return controlInputState;
}
const sessionInputState = getLiveMultiplayerInputState();
if (sessionInputState) {
return sessionInputState;
}
return mobileGrabInputState;
}
function dispatchMobileGrabKeyboardFallback(pressed) {
if (pressed === mobileGrabKeyboardFallbackActive) {
return;
}
mobileGrabKeyboardFallbackActive = pressed;
const event = new KeyboardEvent(pressed ? "keydown" : "keyup", {
bubbles: true,
cancelable: true,
code: MOBILE_GRAB_FALLBACK_CODE,
composed: true,
key: MOBILE_GRAB_FALLBACK_KEY
});
const legacyKeyProperties = [
["keyCode", MOBILE_GRAB_FALLBACK_KEY_CODE],
["which", MOBILE_GRAB_FALLBACK_KEY_CODE]
];
for (const [property, value] of legacyKeyProperties) {
try {
Object.defineProperty(event, property, { get: () => value });
} catch {
}
}
window.dispatchEvent(event);
}
function setMobileGrabPressed(pressed) {
const nextPressed = Boolean(pressed);
if (!nextPressed && !mobileGrabPointerDown && !mobileGrabControlledInputState && !mobileGrabKeyboardFallbackActive) {
return;
}
mobileGrabPointerDown = nextPressed;
if (!mobileGrabPointerDown) {
if (mobileGrabControlledInputState) {
setGrabInputPressed(mobileGrabControlledInputState, false);
mobileGrabControlledInputState = null;
}
if (mobileGrabKeyboardFallbackActive) {
dispatchMobileGrabKeyboardFallback(false);
}
return;
}
const inputState = getMobileGrabInputState();
if (inputState && setGrabInputPressed(inputState, true)) {
mobileGrabInputState = inputState;
mobileGrabControlledInputState = inputState;
if (mobileGrabKeyboardFallbackActive) {
dispatchMobileGrabKeyboardFallback(false);
}
return;
}
dispatchMobileGrabKeyboardFallback(true);
}
function observeMobileGrabInputState(inputState) {
mobileGrabInputState = inputState;
}
function restoreMobileGrabPressedOnInputState(inputState) {
if (mobileGrabPointerDown && setGrabInputPressed(inputState, true)) {
mobileGrabControlledInputState = inputState;
}
}
function isMobileGrabPressed() {
return mobileGrabPointerDown;
}
return {
isMobileGrabPressed,
observeMobileGrabInputState,
restoreMobileGrabPressedOnInputState,
setMobileGrabPressed
};
}
// src/features/mobile-grab-layout.ts
function getCssScale(element, options) {
const rect = element.getBoundingClientRect();
const cssWidth = element.clientWidth || Number.parseFloat(window.getComputedStyle(element).width) || rect.width;
const cssHeight = element.clientHeight || Number.parseFloat(window.getComputedStyle(element).height) || rect.height;
return {
x: cssWidth > 0 && rect.width > 0 ? rect.width / cssWidth : 1,
y: cssHeight > 0 && rect.height > 0 ? rect.height / cssHeight : 1,
width: cssWidth || options.fallbackBaseWidth,
height: cssHeight || options.fallbackBaseHeight
};
}
function getMobileAbilityGapCss(buttons, scaleY) {
const rects = buttons.map((button) => button.getBoundingClientRect()).filter((rect) => rect.width > 0 && rect.height > 0).sort((left, right) => left.top - right.top);
let gap = Infinity;
for (let index = 1; index < rects.length; index += 1) {
const currentGap = rects[index].top - rects[index - 1].bottom;
if (currentGap > 0) {
gap = Math.min(gap, currentGap);
}
}
return Number.isFinite(gap) ? Math.round(gap / Math.max(0.01, scaleY)) : 10;
}
function positionMobileGrabButton(button, options) {
const container = document.getElementById("relativeContainer");
const abilityButtons = options.getAbilityButtons();
const referenceButton = document.querySelector(".buttonArea.bat") || abilityButtons[0];
if (!container || !referenceButton || !isElementVisible(referenceButton)) {
button.style.left = "auto";
button.style.top = "auto";
button.style.right = "40px";
button.style.bottom = "0px";
button.style.width = "90px";
button.style.height = "90px";
return;
}
const containerRect = container.getBoundingClientRect();
const referenceRect = referenceButton.getBoundingClientRect();
const scale = getCssScale(container, options);
const gap = getMobileAbilityGapCss(abilityButtons, scale.y);
const width = Math.round(referenceRect.width / Math.max(0.01, scale.x)) || 90;
const height = Math.round(referenceRect.height / Math.max(0.01, scale.y)) || 90;
const desiredLeft = (referenceRect.left - containerRect.left) / Math.max(0.01, scale.x) - width - gap;
const desiredTop = (referenceRect.top - containerRect.top) / Math.max(0.01, scale.y);
const containerRight = Number.isFinite(containerRect.right) ? containerRect.right : containerRect.left + containerRect.width;
const containerBottom = Number.isFinite(containerRect.bottom) ? containerRect.bottom : containerRect.top + containerRect.height;
const viewportWidth = window.innerWidth || containerRight;
const viewportHeight = window.innerHeight || containerBottom;
const visibleLeft = Math.max(0, containerRect.left);
const visibleTop = Math.max(0, containerRect.top);
const visibleRight = Math.min(viewportWidth, containerRight);
const visibleBottom = Math.min(viewportHeight, containerBottom);
const minLeft = Math.max(0, Math.round((visibleLeft - containerRect.left) / Math.max(0.01, scale.x)));
const minTop = Math.max(0, Math.round((visibleTop - containerRect.top) / Math.max(0.01, scale.y)));
const maxLeft = Math.max(
minLeft,
Math.min(scale.width - width, Math.round((visibleRight - containerRect.left) / Math.max(0.01, scale.x) - width))
);
const maxTop = Math.max(
minTop,
Math.min(scale.height - height, Math.round((visibleBottom - containerRect.top) / Math.max(0.01, scale.y) - height))
);
const left = Math.max(minLeft, Math.min(maxLeft, Math.round(desiredLeft)));
const top = Math.max(minTop, Math.min(maxTop, Math.round(desiredTop)));
button.style.width = `${width}px`;
button.style.height = `${height}px`;
button.style.left = `${left}px`;
button.style.top = `${top}px`;
button.style.right = "auto";
button.style.bottom = "auto";
}
// src/features/mobile-grab-events.ts
function getChangedTouches(event) {
const changedTouches = readObjectProperty(event, "changedTouches");
const length = Number(readObjectProperty(changedTouches, "length"));
if (!Number.isFinite(length) || length <= 0) {
return [];
}
const touches = [];
for (let index = 0; index < length; index += 1) {
const touch = readObjectProperty(changedTouches, index);
if (touch) {
touches.push(touch);
}
}
return touches;
}
function getTouchIdentifier(touch) {
return readObjectProperty(touch, "identifier");
}
function isPrimaryPointerStart(event) {
const button = readObjectProperty(event, "button");
return button === void 0 || button === 0;
}
function callEventMethod(event, methodName) {
const method = readObjectProperty(event, methodName);
if (isReflectableObject(event) && typeof method === "function") {
Reflect.apply(method, event, []);
}
}
function stopMobileGrabEvent(event) {
if (readObjectProperty(event, "cancelable") !== false) {
callEventMethod(event, "preventDefault");
}
callEventMethod(event, "stopImmediatePropagation");
}
// src/features/mobile-grab-press.ts
function createMobileGrabPressController(options) {
let activeTouchId = null;
let releaseHooksInstalled = false;
function resetMobileGrabPress() {
activeTouchId = null;
options.setPressed(false);
}
function handleMobileGrabTouchStart(event) {
if (!options.getButton() || !options.shouldShow()) {
return;
}
const touch = getChangedTouches(event)[0];
if (!touch) {
return;
}
stopMobileGrabEvent(event);
activeTouchId = getTouchIdentifier(touch);
options.setPressed(true);
}
function handleMobileGrabTouchEnd(event) {
if (activeTouchId === null) {
return;
}
for (const touch of getChangedTouches(event)) {
if (getTouchIdentifier(touch) === activeTouchId) {
activeTouchId = null;
options.setPressed(false);
return;
}
}
}
function handleMobileGrabPointerStart(event) {
if (!options.getButton() || !options.shouldShow()) {
return;
}
if (!isPrimaryPointerStart(event)) {
return;
}
stopMobileGrabEvent(event);
options.setPressed(true);
}
function handleMobileGrabPointerEnd(event) {
if (!options.isPressed()) {
return;
}
stopMobileGrabEvent(event);
options.setPressed(false);
}
function installMobileGrabReleaseHooks() {
if (releaseHooksInstalled) {
return;
}
releaseHooksInstalled = true;
document.addEventListener("touchend", handleMobileGrabTouchEnd, true);
document.addEventListener("touchcancel", handleMobileGrabTouchEnd, true);
document.addEventListener("pointerup", handleMobileGrabPointerEnd, true);
document.addEventListener("pointercancel", handleMobileGrabPointerEnd, true);
window.addEventListener("touchend", handleMobileGrabTouchEnd, true);
window.addEventListener("touchcancel", handleMobileGrabTouchEnd, true);
window.addEventListener("pointerup", handleMobileGrabPointerEnd, true);
window.addEventListener("pointercancel", handleMobileGrabPointerEnd, true);
window.addEventListener("blur", resetMobileGrabPress, true);
}
return {
handleMobileGrabPointerStart,
handleMobileGrabTouchStart,
installMobileGrabReleaseHooks,
resetMobileGrabPress
};
}
// src/features/mobile-grab-button.ts
var MOBILE_GRAB_ICON_HREF = "data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 64 64%22 fill=%22none%22%3E%3Cpath d=%22M22 36V13a5 5 0 0 1 10 0v20V9a5 5 0 0 1 10 0v25V13a5 5 0 0 1 10 0v23V22a4 4 0 0 1 8 0v18c0 13-9 21-23 21h-7c-7 0-11-4-15-10l-6-9a5 5 0 0 1 8-6l8 9%22 stroke=%22%23f4f4f4%22 stroke-width=%226%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22/%3E%3C/svg%3E";
function createMobileGrabController(dependencies) {
let mobileGrabButton = null;
const mobileGrabInput = createMobileGrabInputController();
const mobileGrabPress = createMobileGrabPressController({
getButton: () => mobileGrabButton,
isPressed: () => mobileGrabInput.isMobileGrabPressed(),
setPressed: (pressed) => setMobileGrabPressed(pressed),
shouldShow: () => shouldShowMobileGrabButton()
});
function isMobileGameMode() {
return isMobileGameModeContext();
}
function isMobileQolboxMenuContext() {
return isMobileQolboxMenuContextValue();
}
function setMobileGrabPressed(pressed) {
mobileGrabInput.setMobileGrabPressed(pressed);
}
function hideMobileGrabButton() {
mobileGrabPress.resetMobileGrabPress();
hideMobileGrabButtonElement(mobileGrabButton);
}
function removeMobileGrabButton() {
hideMobileGrabButton();
mobileGrabButton = removeMobileGrabButtonElement(mobileGrabButton);
}
function handleMobileGrabPointerStart(event) {
mobileGrabPress.handleMobileGrabPointerStart(event);
}
function ensureMobileGrabButton() {
if (mobileGrabButton && mobileGrabButton.isConnected) {
return mobileGrabButton;
}
const container = document.getElementById("relativeContainer");
if (!container) {
return null;
}
const button = createMobileGrabButtonElement(container, {
onPointerStart: handleMobileGrabPointerStart,
onTouchStart: mobileGrabPress.handleMobileGrabTouchStart
});
mobileGrabButton = button;
mobileGrabPress.installMobileGrabReleaseHooks();
return button;
}
function layoutMobileGrabButton(button) {
positionMobileGrabButton(button, {
fallbackBaseHeight: dependencies.fallbackBaseHeight,
fallbackBaseWidth: dependencies.fallbackBaseWidth,
getAbilityButtons: getMobileAbilityButtons
});
}
function shouldShowMobileGrabButton() {
return Boolean(dependencies.isEnabled() && isMobileGameMode() && areNativeMobileAbilityButtonsVisible());
}
function syncMobileGrabButton() {
if (!isMobileGameMode()) {
removeMobileGrabButton();
return false;
}
const button = ensureMobileGrabButton();
if (!button) {
return false;
}
if (!shouldShowMobileGrabButton()) {
hideMobileGrabButton();
return false;
}
layoutMobileGrabButton(button);
button.style.display = "block";
return true;
}
function installMobileGrabControlHooks() {
return installNativeMobileControlHooks({
onInputStateObserved(inputState) {
mobileGrabInput.observeMobileGrabInputState(inputState);
},
afterInputStateSet(inputState) {
mobileGrabInput.restoreMobileGrabPressedOnInputState(inputState);
},
onControlsShown() {
syncMobileGrabButton();
},
onControlsHidden() {
hideMobileGrabButton();
}
});
}
function patchMobileGrabButton() {
if (!dependencies.isEnabled()) {
hideMobileGrabButton();
return false;
}
installMobileGrabControlHooks();
return syncMobileGrabButton();
}
return {
handleMobileGrabPointerStart,
hideMobileGrabButton,
isMobileGameMode,
isMobileQolboxMenuContext,
layoutMobileGrabButton,
patchMobileGrabButton,
setMobileGrabPressed,
shouldShowMobileGrabButton,
syncMobileGrabButton
};
}
// src/features/mobile-qolbox-menu-entry.ts
function createMobileQolboxMenuEntryController({
findChangeControlsItem: findChangeControlsItem2,
getSettingsContainer,
isMobileQolboxMenuContext,
openMenu
}) {
function removeMobileQolboxHamburgerEntry() {
for (const item of document.querySelectorAll('.item[data-qolbox-mobile-menu="true"]')) {
item.remove();
}
}
function patchMobileQolboxHamburgerEntry() {
const container = getSettingsContainer();
if (!container) {
return false;
}
if (!isMobileQolboxMenuContext()) {
removeMobileQolboxHamburgerEntry();
return false;
}
let item = container.querySelector('.item[data-qolbox-mobile-menu="true"]');
if (!item) {
item = document.createElement("div");
item.className = "item";
item.dataset.qolboxMobileMenu = "true";
item.addEventListener(
"click",
(event) => {
event.preventDefault();
event.stopImmediatePropagation();
openMenu();
},
true
);
}
const beforeItem = findChangeControlsItem2(container);
if (beforeItem && beforeItem !== item) {
container.insertBefore(item, beforeItem);
} else if (item.parentElement !== container) {
container.appendChild(item);
}
item.textContent = "QOLBox";
return true;
}
return { patchMobileQolboxHamburgerEntry, removeMobileQolboxHamburgerEntry };
}
// src/features/mobile-feature-bundle.ts
function createMobileFeatureBundle(options) {
const mobileGrabController = createMobileGrabController({
fallbackBaseHeight: FALLBACK_BASE_HEIGHT,
fallbackBaseWidth: FALLBACK_BASE_WIDTH,
isEnabled: options.isMobileGrabEnabled
});
const { patchMobileQolboxHamburgerEntry } = createMobileQolboxMenuEntryController({
findChangeControlsItem,
getSettingsContainer: findSettingsContainer,
isMobileQolboxMenuContext: mobileGrabController.isMobileQolboxMenuContext,
openMenu: options.openMenu
});
return {
...mobileGrabController,
patchMobileQolboxHamburgerEntry
};
}
// src/settings/onboarding-storage.ts
var ONBOARDING_COMPLETE_KEY = "vm.hitbox.qolboxOnboardingComplete";
function loadOnboardingComplete() {
try {
return localStorage.getItem(ONBOARDING_COMPLETE_KEY) === "true";
} catch {
return false;
}
}
function saveOnboardingComplete() {
try {
localStorage.setItem(ONBOARDING_COMPLETE_KEY, "true");
} catch {
}
}
// src/config/qolbox-version.ts
var QOLBOX_VERSION = "2.0.0";
var QOLBOX_VERSION_LABEL = `v${QOLBOX_VERSION}`;
var QOLBOX_GREASYFORK_URL = "https://greasyfork.org/en/scripts/568667-qolbox";
var QOLBOX_GITHUB_URL = "https://github.com/AggressiveCombo/QOLBox";
// src/config/qolbox-release-notes.ts
var GREASYFORK_VERSIONS_URL = "https://greasyfork.org/en/scripts/568667-qolbox/versions.json";
var GITHUB_RELEASES_URL = "https://api.github.com/repos/AggressiveCombo/QOLBox/releases?per_page=100";
var RELEASE_HISTORY_CACHE_KEY = "vm.hitbox.qolboxReleaseHistory.v1";
var RELEASE_HISTORY_CACHE_TTL_MS = 12 * 60 * 60 * 1e3;
var RELEASE_HISTORY_FETCH_TIMEOUT_MS = 7e3;
var LOCAL_CURRENT_RELEASE_FALLBACK = [
{
version: QOLBOX_VERSION,
source: "local-fallback",
notes: [
"Adds grouped lobby tools, expanded slash commands, mobile Grab, setup controls, and a compact update notice.",
"Improves fullscreen, reserve, audio, chat, spectator, and away-tab behavior for the v2.0.0 release."
]
}
];
function isRecord5(value) {
return typeof value === "object" && value !== null;
}
function normalizeVersionKey(version) {
return String(version || "").trim().replace(/^v/i, "").toLowerCase();
}
function areVersionKeysEquivalent(left, right) {
const leftPoint = parseVersionPoint(left);
const rightPoint = parseVersionPoint(right);
if (leftPoint && rightPoint) {
return compareVersionPoints(leftPoint, rightPoint) === 0;
}
return normalizeVersionKey(left) === normalizeVersionKey(right);
}
function parseVersionPoint(version) {
const normalized = normalizeVersionKey(version);
if (!normalized) {
return null;
}
const [main, prerelease = ""] = normalized.split("-", 2);
const rawParts = main.split(".");
const parts = [];
let wildcardIndex = null;
for (let index = 0; index < 3; index += 1) {
const rawPart = rawParts[index] ?? "0";
if (/^(x|\*)$/i.test(rawPart)) {
if (wildcardIndex === null) {
wildcardIndex = index;
}
parts.push(0);
continue;
}
if (!/^\d+$/.test(rawPart)) {
return null;
}
parts.push(Number(rawPart));
}
return {
parts,
prereleaseWeight: prerelease ? -1 : 0,
wildcardIndex
};
}
function compareVersionPoints(left, right) {
for (let index = 0; index < 3; index += 1) {
const delta = left.parts[index] - right.parts[index];
if (delta) {
return delta;
}
}
return left.prereleaseWeight - right.prereleaseWeight;
}
function getWildcardUpperBound(point) {
if (point.wildcardIndex === null) {
return point;
}
const parts = [...point.parts];
for (let index = point.wildcardIndex; index < 3; index += 1) {
parts[index] = Number.MAX_SAFE_INTEGER;
}
return {
parts,
prereleaseWeight: 0,
wildcardIndex: null
};
}
function isVersionInUpgradeRange(version, previousVersion, currentVersion) {
const versionPoint = parseVersionPoint(version);
const currentPoint = parseVersionPoint(currentVersion);
if (!versionPoint) {
return false;
}
if (currentPoint && compareVersionPoints(versionPoint, currentPoint) > 0) {
return false;
}
if (!previousVersion) {
return true;
}
const previousPoint = parseVersionPoint(previousVersion);
if (!previousPoint) {
return true;
}
const previousUpperBound = getWildcardUpperBound(previousPoint);
return compareVersionPoints(versionPoint, previousUpperBound) > 0;
}
function compareReleaseVersionsNewestFirst(left, right) {
const leftPoint = parseVersionPoint(left.version);
const rightPoint = parseVersionPoint(right.version);
if (leftPoint && rightPoint) {
const versionDelta = compareVersionPoints(rightPoint, leftPoint);
if (versionDelta) {
return versionDelta;
}
}
return getReleaseTimestamp(right) - getReleaseTimestamp(left);
}
function getReleaseTimestamp(entry) {
const timestamp = entry.publishedAt ? Date.parse(entry.publishedAt) : 0;
return Number.isFinite(timestamp) ? timestamp : 0;
}
function getSourcePriority(source) {
switch (source) {
case "github":
return 3;
case "greasyfork":
return 2;
case "local-fallback":
default:
return 1;
}
}
function shouldReplaceReleaseEntry(next, current) {
const timestampDelta = getReleaseTimestamp(next) - getReleaseTimestamp(current);
if (timestampDelta) {
return timestampDelta > 0;
}
return getSourcePriority(next.source) > getSourcePriority(current.source);
}
function dedupeLatestReleaseEntries(entries) {
const byVersion = /* @__PURE__ */ new Map();
for (const entry of entries) {
const versionKey = normalizeVersionKey(entry.version);
if (!versionKey || !entry.notes.length) {
continue;
}
const current = byVersion.get(versionKey);
if (!current || shouldReplaceReleaseEntry(entry, current)) {
byVersion.set(versionKey, entry);
}
}
return Array.from(byVersion.values()).sort(compareReleaseVersionsNewestFirst);
}
function cleanReleaseText(text) {
return text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/[`*_>#]+/g, "").replace(/\s+/g, " ").trim();
}
function extractMarkdownNotes(markdown) {
if (typeof markdown !== "string") {
return [];
}
const notes = [];
for (const rawLine of markdown.split(/\r?\n/)) {
const line = rawLine.trim();
if (!line || /^#{1,6}\s+/.test(line)) {
continue;
}
const bulletMatch = line.match(/^[-*+]\s+(.+)$/) || line.match(/^\d+[.)]\s+(.+)$/);
if (bulletMatch) {
const note = cleanReleaseText(bulletMatch[1]);
if (note) {
notes.push(note);
}
}
}
if (notes.length) {
return notes;
}
const fallback = markdown.split(/\r?\n/).map(cleanReleaseText).find((line) => line && !/^#{1,6}\s+/.test(line));
return fallback ? [fallback] : [];
}
function parseGitHubReleaseEntries(rawValue) {
if (!Array.isArray(rawValue)) {
return [];
}
return rawValue.filter((record) => isRecord5(record)).filter((record) => record.draft !== true).map((record) => {
const version = normalizeVersionKey(record.tag_name);
const notes = extractMarkdownNotes(record.body);
return {
version,
source: "github",
publishedAt: typeof record.published_at === "string" ? record.published_at : void 0,
url: typeof record.html_url === "string" ? record.html_url : void 0,
notes: notes.length ? notes : [cleanReleaseText(String(record.name || `QOLBox ${version}`))]
};
}).filter((entry) => entry.version && entry.notes.length);
}
function parseGreasyForkDetailEntry(record, fallback) {
const version = normalizeVersionKey(record.version || fallback.version);
if (!version) {
return null;
}
const description = typeof record.description === "string" ? cleanReleaseText(record.description) : "";
return {
version,
source: "greasyfork",
publishedAt: typeof record.code_updated_at === "string" ? record.code_updated_at : typeof fallback.created_at === "string" ? fallback.created_at : void 0,
url: typeof record.url === "string" ? record.url : void 0,
notes: description ? [description] : [`Published on GreasyFork as QOLBox ${version}.`]
};
}
async function fetchJson(url, headers = {}) {
const controller = new AbortController();
const timer = window.setTimeout(() => controller.abort(), RELEASE_HISTORY_FETCH_TIMEOUT_MS);
try {
const response = await fetch(url, {
headers: {
Accept: "application/json",
...headers
},
signal: controller.signal
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} finally {
window.clearTimeout(timer);
}
}
async function fetchGitHubReleaseEntries() {
return parseGitHubReleaseEntries(await fetchJson(GITHUB_RELEASES_URL));
}
async function fetchGreasyForkReleaseEntries() {
const rawVersions = await fetchJson(GREASYFORK_VERSIONS_URL);
if (!Array.isArray(rawVersions)) {
return [];
}
const versions = rawVersions.filter((record) => isRecord5(record));
const detailResults = await Promise.allSettled(
versions.map(async (versionRecord) => {
if (typeof versionRecord.url !== "string") {
return null;
}
const detail = await fetchJson(versionRecord.url);
return isRecord5(detail) ? parseGreasyForkDetailEntry(detail, versionRecord) : null;
})
);
return detailResults.filter((result) => result.status === "fulfilled").map((result) => result.value).filter((entry) => Boolean(entry));
}
function safeGetLocalStorage(key) {
try {
return localStorage.getItem(key);
} catch {
return null;
}
}
function safeSetLocalStorage(key, value) {
try {
localStorage.setItem(key, value);
} catch {
}
}
function parseCachedReleaseHistory(rawValue) {
if (!rawValue) {
return null;
}
try {
const parsed = JSON.parse(rawValue);
if (!isRecord5(parsed) || typeof parsed.fetchedAt !== "number" || !Array.isArray(parsed.entries)) {
return null;
}
const entries = parsed.entries.filter((entry) => isRecord5(entry) && typeof entry.version === "string" && Array.isArray(entry.notes)).map((entry) => ({
version: entry.version,
source: entry.source === "github" || entry.source === "greasyfork" || entry.source === "local-fallback" ? entry.source : "local-fallback",
publishedAt: typeof entry.publishedAt === "string" ? entry.publishedAt : void 0,
url: typeof entry.url === "string" ? entry.url : void 0,
notes: entry.notes.map((note) => String(note)).filter(Boolean)
}));
return { fetchedAt: parsed.fetchedAt, entries };
} catch {
return null;
}
}
function getCachedReleaseHistoryEntries(allowStale = false) {
const cached = parseCachedReleaseHistory(safeGetLocalStorage(RELEASE_HISTORY_CACHE_KEY));
if (!cached) {
return null;
}
if (!allowStale && Date.now() - cached.fetchedAt > RELEASE_HISTORY_CACHE_TTL_MS) {
return null;
}
return dedupeLatestReleaseEntries([...LOCAL_CURRENT_RELEASE_FALLBACK, ...cached.entries]);
}
function saveReleaseHistoryCache(entries) {
safeSetLocalStorage(RELEASE_HISTORY_CACHE_KEY, JSON.stringify({
fetchedAt: Date.now(),
entries
}));
}
async function fetchExternalReleaseHistoryEntries() {
const [githubResult, greasyForkResult] = await Promise.allSettled([
fetchGitHubReleaseEntries(),
fetchGreasyForkReleaseEntries()
]);
const externalEntries = [
...githubResult.status === "fulfilled" ? githubResult.value : [],
...greasyForkResult.status === "fulfilled" ? greasyForkResult.value : []
];
if (!externalEntries.length) {
throw new Error("No public release history entries loaded.");
}
const entries = dedupeLatestReleaseEntries([...LOCAL_CURRENT_RELEASE_FALLBACK, ...externalEntries]);
saveReleaseHistoryCache(entries);
return entries;
}
function getReleaseNotesBetween(previousVersion, currentVersion = QOLBOX_VERSION, releaseHistory = LOCAL_CURRENT_RELEASE_FALLBACK) {
const entries = dedupeLatestReleaseEntries(releaseHistory);
const selectedEntries = entries.filter((entry) => isVersionInUpgradeRange(entry.version, previousVersion, currentVersion));
if (selectedEntries.length > 1 || !previousVersion) {
return selectedEntries;
}
const previousEntry = entries.find((entry) => areVersionKeysEquivalent(entry.version, previousVersion));
return previousEntry && !selectedEntries.some((entry) => areVersionKeysEquivalent(entry.version, previousEntry.version)) ? [...selectedEntries, previousEntry] : selectedEntries;
}
function getReleaseHistorySourceLabel(notes) {
const sources = new Set(notes.map((note) => note.source));
if (sources.has("github") && sources.has("greasyfork")) {
return "GitHub releases and GreasyFork version history";
}
if (sources.has("github")) {
return "GitHub releases";
}
if (sources.has("greasyfork")) {
return "GreasyFork version history";
}
return "bundled fallback notes";
}
function createInitialReleaseHistoryState(previousVersion, currentVersion = QOLBOX_VERSION) {
const cachedEntries = getCachedReleaseHistoryEntries();
if (cachedEntries) {
const notes2 = getReleaseNotesBetween(previousVersion, currentVersion, cachedEntries);
return {
status: "ready",
notes: notes2,
sourceLabel: getReleaseHistorySourceLabel(notes2),
message: "Loaded cached public version history."
};
}
const notes = getReleaseNotesBetween(previousVersion, currentVersion);
return {
status: "loading",
notes,
sourceLabel: getReleaseHistorySourceLabel(notes),
message: "Loading public version history..."
};
}
async function loadReleaseHistoryState(previousVersion, currentVersion = QOLBOX_VERSION) {
try {
const entries = await fetchExternalReleaseHistoryEntries();
const notes = getReleaseNotesBetween(previousVersion, currentVersion, entries);
return {
status: "ready",
notes,
sourceLabel: getReleaseHistorySourceLabel(notes),
message: "Loaded public version history."
};
} catch {
const cachedEntries = getCachedReleaseHistoryEntries(true);
if (cachedEntries) {
const notes2 = getReleaseNotesBetween(previousVersion, currentVersion, cachedEntries);
return {
status: "fallback",
notes: notes2,
sourceLabel: getReleaseHistorySourceLabel(notes2),
message: "Could not refresh public version history. Showing cached history."
};
}
const notes = getReleaseNotesBetween(previousVersion, currentVersion);
return {
status: "fallback",
notes,
sourceLabel: getReleaseHistorySourceLabel(notes),
message: "Could not load public version history. Showing minimal bundled notes."
};
}
}
// src/settings/update-notice-storage.ts
var LAST_VERSION_KEY = "vm.hitbox.qolboxLastVersion";
var ACK_VERSION_KEY = "vm.hitbox.qolboxAcknowledgedVersion";
function safeGetLocalStorage2(key) {
try {
return localStorage.getItem(key);
} catch {
return null;
}
}
function safeSetLocalStorage2(key, value) {
try {
localStorage.setItem(key, value);
} catch {
}
}
function loadPendingUpdateNotice(currentVersion = QOLBOX_VERSION, existingInstallWithoutVersion = false) {
const previousVersion = safeGetLocalStorage2(LAST_VERSION_KEY);
const acknowledgedVersion = safeGetLocalStorage2(ACK_VERSION_KEY);
if (!previousVersion) {
if (existingInstallWithoutVersion) {
return { previousVersion: "a pre-version-tracking build", currentVersion };
}
safeSetLocalStorage2(LAST_VERSION_KEY, currentVersion);
safeSetLocalStorage2(ACK_VERSION_KEY, currentVersion);
return null;
}
if (previousVersion === currentVersion || acknowledgedVersion === currentVersion) {
if (previousVersion !== currentVersion) {
safeSetLocalStorage2(LAST_VERSION_KEY, currentVersion);
}
return null;
}
return { previousVersion, currentVersion };
}
function acknowledgeUpdateNotice(currentVersion = QOLBOX_VERSION) {
safeSetLocalStorage2(LAST_VERSION_KEY, currentVersion);
safeSetLocalStorage2(ACK_VERSION_KEY, currentVersion);
}
// src/features/first-boot-onboarding.ts
function createFirstBootOnboardingScheduler(options) {
function scheduleFirstBootOnboarding() {
if (options.isOnboardingComplete()) {
return;
}
const show = () => {
window.setTimeout(options.showFirstBootOnboarding, 0);
};
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", show, { once: true });
} else {
show();
}
}
return {
scheduleFirstBootOnboarding
};
}
// src/features/qolbox-menu-keyboard.ts
function isModifiedQolboxMenuShortcut(event) {
return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
}
function isQolboxMenuShortcut(event, menuKey) {
return !isModifiedQolboxMenuShortcut(event) && (event.key === menuKey || event.code === menuKey);
}
// src/features/qolbox-menu-view.ts
function findQolboxMenuPanel(menuId) {
const menu = document.getElementById(menuId);
return menu ? menu.querySelector(".qolboxMenuPanel") : null;
}
function focusFirstQolboxMenuControl(panel) {
window.setTimeout(() => {
const focusTarget = panel.querySelector(".qolboxMenuButton.primary, .qolboxMenuToggle.active, .qolboxMenuButton");
focusElementWithoutScroll(focusTarget);
}, 0);
}
function renderQolboxMenuPanel(menuId, markup) {
const panel = findQolboxMenuPanel(menuId);
if (!panel) {
return;
}
panel.innerHTML = markup;
focusFirstQolboxMenuControl(panel);
}
function ensureQolboxMenuOverlay(options) {
let menu = document.getElementById(options.menuId);
if (menu) {
return menu;
}
const host = document.body || document.documentElement;
if (!host) {
return null;
}
menu = document.createElement("div");
menu.id = options.menuId;
menu.className = "qolboxMenuOverlay";
menu.setAttribute("role", "dialog");
menu.setAttribute("aria-modal", "true");
menu.innerHTML = '<div class="qolboxMenuPanel"></div>';
menu.addEventListener("pointerdown", options.onPointerEvent, true);
menu.addEventListener("mousedown", options.onPointerEvent, true);
menu.addEventListener("mouseup", options.onPointerEvent, true);
menu.addEventListener("wheel", options.onPointerEvent, { capture: true, passive: true });
menu.addEventListener("click", options.onClick, true);
menu.addEventListener("change", options.onInput, true);
menu.addEventListener("input", options.onInput, true);
host.appendChild(menu);
return menu;
}
// src/features/qolbox-menu-controller.ts
var FEATURE_PAGE_KEYS = [
FEATURE_FULLSCREEN,
FEATURE_RESERVE,
FEATURE_CHAT,
FEATURE_GAME_START_ALERT,
FEATURE_MOBILE_GRAB
];
var ADVANCED_TIMING_KEYS = [
ADVANCED_RESERVE_RETRY_INTERVAL_MS,
ADVANCED_ALERT_DELAY_MS,
ADVANCED_ALERT_FLASH_INTERVAL_MS,
ADVANCED_TYPING_DURATION_MS
];
function createQolboxMenuController(options) {
let onboardingComplete = options.initialOnboardingComplete;
let onboardingStepIndex = 0;
let settingsDraft = null;
let settingsErrors = {};
let settingsPage = "features";
let updateNoticePageIndex = 0;
let mode = "closed";
let hooksInstalled = false;
function isOnboardingComplete() {
return onboardingComplete;
}
function getMode() {
return mode;
}
function isClosed() {
return mode === "closed";
}
function renderQolboxMenu() {
if (mode === "settings" && !settingsDraft) {
settingsDraft = options.createSettingsDraft();
}
if (mode === "update") {
updateNoticePageIndex = Math.max(
0,
Math.min(updateNoticePageIndex, Math.max(1, options.getUpdateNoticePageCount()) - 1)
);
}
const markup = mode === "settings" ? options.getSettingsMenuMarkup(settingsDraft, settingsPage, settingsErrors) : mode === "update" ? options.getUpdateNoticeMarkup(updateNoticePageIndex) : options.getOnboardingStepMarkup(onboardingStepIndex);
renderQolboxMenuPanel(options.menuId, markup);
}
function stopQolboxMenuPointerEvent(event) {
if (mode !== "closed") {
event.stopPropagation();
}
}
function closeQolboxMenu() {
mode = "closed";
settingsDraft = null;
settingsErrors = {};
options.onMenuModeChanged();
const menu = document.getElementById(options.menuId);
if (menu) {
menu.remove();
}
}
function completeOnboarding() {
onboardingComplete = true;
closeQolboxMenu();
options.onCompleteOnboarding();
}
function openQolboxMenu(nextMode = "settings") {
options.onBeforeOpen();
if (!ensureQolboxMenu()) {
return;
}
mode = nextMode;
if (nextMode === "onboarding") {
settingsDraft = null;
settingsErrors = {};
onboardingStepIndex = 0;
} else if (nextMode === "settings") {
settingsDraft = options.createSettingsDraft();
settingsErrors = {};
settingsPage = "features";
} else if (nextMode === "update") {
updateNoticePageIndex = 0;
}
options.onMenuModeChanged();
renderQolboxMenu();
}
function getAdvancedDefinition(key) {
return ADVANCED_SETTING_DEFINITIONS.find((definition) => definition.key === key) || null;
}
function getDraftAdvancedValue(key) {
const panel = document.getElementById(options.menuId);
const input = panel ? Array.from(panel.querySelectorAll("[data-qolbox-advanced-input]")).find((element) => element.dataset.qolboxAdvancedInput === key) : null;
return input ? input.value : settingsDraft?.advanced[key];
}
function updateDraftAdvancedValue(key, value) {
const definition = getAdvancedDefinition(key);
if (!definition || !settingsDraft) {
return;
}
settingsDraft.advanced[definition.key] = value;
if (settingsErrors[definition.key]) {
delete settingsErrors[definition.key];
}
}
function validateAdvancedValue(definition, value) {
if (definition.kind === "number") {
const numericValue = Number(value);
if (!Number.isFinite(numericValue)) {
return "Enter a number.";
}
if (numericValue < definition.min || numericValue > definition.max) {
return `Use ${definition.min}-${definition.max}${definition.unit ? ` ${definition.unit}` : ""}.`;
}
return null;
}
return value === true || value === false || value === "true" || value === "false" ? null : "Choose Enabled or Off.";
}
function getErrorPage(key) {
if (key === ADVANCED_COMMAND_ALIASES) {
return "commands";
}
return "advanced";
}
function validateSettingsDraft() {
if (!settingsDraft) {
return null;
}
const errors = {};
const sanitized = {};
for (const definition of ADVANCED_SETTING_DEFINITIONS) {
const value = getDraftAdvancedValue(definition.key);
settingsDraft.advanced[definition.key] = value;
const error = validateAdvancedValue(definition, value);
if (error) {
errors[definition.key] = error;
} else {
sanitized[definition.key] = sanitizeAdvancedSetting(definition, value);
}
}
settingsErrors = errors;
const firstError = ADVANCED_SETTING_DEFINITIONS.find((definition) => errors[definition.key]);
if (firstError) {
settingsPage = getErrorPage(firstError.key);
return null;
}
return sanitized;
}
function resetFeatureDraft(keys) {
if (!settingsDraft) {
return;
}
const defaults = getDefaultFeatureSettings();
for (const key of keys) {
settingsDraft.features[key] = defaults[key];
}
}
function resetAdvancedDraft(keys) {
if (!settingsDraft) {
return;
}
const defaults = getDefaultAdvancedSettings();
for (const key of keys) {
settingsDraft.advanced[key] = defaults[key];
delete settingsErrors[key];
}
}
function resetSettingsPageDraft() {
switch (settingsPage) {
case "commands":
resetFeatureDraft([FEATURE_LOBBY_COMMANDS]);
resetAdvancedDraft([ADVANCED_COMMAND_ALIASES]);
break;
case "audio":
resetFeatureDraft([FEATURE_AUDIO]);
break;
case "advanced":
resetAdvancedDraft(ADVANCED_TIMING_KEYS);
break;
case "features":
resetFeatureDraft(FEATURE_PAGE_KEYS);
break;
case "about":
default:
break;
}
renderQolboxMenu();
}
function saveSettingsDraft() {
const sanitized = validateSettingsDraft();
if (!settingsDraft || !sanitized) {
renderQolboxMenu();
return;
}
const featureDraft = { ...settingsDraft.features };
options.onCommitSettingsDraft(featureDraft, sanitized);
closeQolboxMenu();
}
function handleQolboxMenuClick(event) {
if (mode !== "closed") {
event.stopPropagation();
}
const actionElement = event.target instanceof Element ? event.target.closest("[data-qolbox-action]") : null;
if (!actionElement) {
return;
}
const action = actionElement.dataset.qolboxAction;
event.preventDefault();
event.stopImmediatePropagation();
switch (action) {
case "set-feature":
options.onSetFeatureEnabled(actionElement.dataset.feature, actionElement.dataset.enabled === "true");
break;
case "draft-feature":
if (settingsDraft && isKnownFeature(actionElement.dataset.feature || "")) {
settingsDraft.features[actionElement.dataset.feature] = actionElement.dataset.enabled === "true";
renderQolboxMenu();
}
break;
case "draft-advanced":
updateDraftAdvancedValue(actionElement.dataset.advanced, actionElement.dataset.value);
renderQolboxMenu();
break;
case "settings-page":
if (isSettingsPage(actionElement.dataset.page)) {
settingsPage = actionElement.dataset.page;
renderQolboxMenu();
}
break;
case "reset-page":
resetSettingsPageDraft();
break;
case "save-settings":
saveSettingsDraft();
break;
case "cancel-settings":
closeQolboxMenu();
break;
case "choose-express":
options.onChooseExpressSetup();
onboardingStepIndex = options.getOnboardingStepCount() - 1;
renderQolboxMenu();
break;
case "choose-custom":
onboardingStepIndex = Math.min(1, options.getOnboardingStepCount() - 1);
renderQolboxMenu();
break;
case "next":
onboardingStepIndex = Math.min(onboardingStepIndex + 1, options.getOnboardingStepCount() - 1);
renderQolboxMenu();
break;
case "back":
onboardingStepIndex = Math.max(0, onboardingStepIndex - 1);
renderQolboxMenu();
break;
case "skip-onboarding":
case "finish-onboarding":
completeOnboarding();
break;
case "acknowledge-update":
options.onAcknowledgeUpdateNotice();
closeQolboxMenu();
break;
case "update-newer":
updateNoticePageIndex = Math.max(0, updateNoticePageIndex - 1);
renderQolboxMenu();
break;
case "update-older":
updateNoticePageIndex = Math.min(
Math.max(1, options.getUpdateNoticePageCount()) - 1,
updateNoticePageIndex + 1
);
renderQolboxMenu();
break;
case "redo-onboarding":
openQolboxMenu("onboarding");
break;
default:
break;
}
}
function ensureQolboxMenu() {
return ensureQolboxMenuOverlay({
menuId: options.menuId,
onClick: handleQolboxMenuClick,
onInput: handleQolboxMenuInput,
onPointerEvent: stopQolboxMenuPointerEvent
});
}
function isSettingsPage(value) {
return value === "features" || value === "commands" || value === "audio" || value === "advanced" || value === "about";
}
function handleQolboxMenuInput(event) {
if (mode !== "settings" || !settingsDraft || !(event.target instanceof HTMLInputElement || event.target instanceof HTMLSelectElement)) {
return;
}
const key = event.target.dataset.qolboxAdvancedInput;
if (!key) {
return;
}
updateDraftAdvancedValue(key, event.target.value);
}
function handleQolboxMenuKey(event) {
if (mode !== "closed" && isEscapeKey(event)) {
event.preventDefault();
event.stopImmediatePropagation();
if (mode === "settings") {
closeQolboxMenu();
}
return;
}
if (!isQolboxMenuShortcut(event, options.menuKey)) {
return;
}
event.preventDefault();
event.stopImmediatePropagation();
if (mode === "settings") {
closeQolboxMenu();
return;
}
if (mode === "onboarding") {
return;
}
openQolboxMenu(onboardingComplete ? "settings" : "onboarding");
}
function installQolboxMenuHooks() {
if (hooksInstalled) {
return;
}
hooksInstalled = true;
window.addEventListener("keydown", handleQolboxMenuKey, true);
document.addEventListener("keydown", handleQolboxMenuKey, true);
}
function showFirstBootOnboarding() {
if (onboardingComplete || mode !== "closed") {
return;
}
openQolboxMenu("onboarding");
}
function showUpdateNotice() {
if (!onboardingComplete || mode !== "closed") {
return;
}
openQolboxMenu("update");
}
return {
closeQolboxMenu,
getMode,
installQolboxMenuHooks,
isClosed,
isOnboardingComplete,
openQolboxMenu,
renderQolboxMenu,
showFirstBootOnboarding,
showUpdateNotice
};
}
// src/features/qolbox-menu-markup.ts
var SETTINGS_PAGES = [
{ key: "features", title: "Features" },
{ key: "commands", title: "Commands" },
{ key: "audio", title: "Audio" },
{ key: "advanced", title: "Advanced" },
{ key: "about", title: "About" }
];
var SETTINGS_PAGE_TITLES = {
features: "Feature Settings",
commands: "Command Settings",
audio: "Audio Settings",
advanced: "Advanced Settings",
about: "About QOLBox"
};
var FEATURE_PAGE_KEYS2 = [
FEATURE_FULLSCREEN,
FEATURE_RESERVE,
FEATURE_CHAT,
FEATURE_GAME_START_ALERT,
FEATURE_MOBILE_GRAB
];
var ADVANCED_TIMING_KEYS2 = [
ADVANCED_RESERVE_RETRY_INTERVAL_MS,
ADVANCED_ALERT_DELAY_MS,
ADVANCED_ALERT_FLASH_INTERVAL_MS,
ADVANCED_TYPING_DURATION_MS
];
var GREASYFORK_ICON_DATA_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3ggEBCQHM3fXsAAAAVdJREFUOMudkz2qwkAUhc/goBaGJBgUtBCZyj0ILkpwAW7Bws4yO3AHLiCtEFD8KVREkoiFxZzX5A2KGfN4F04zMN+ce+5c4LMUgDmANYBnrnV+plBSi+FwyHq9TgA2LQpvCiEiABwMBtzv95RSfoNEHy8DYBzHrNVqVEr9BWKcqNFoxF6vx3a7zc1mYyC73a4MogBg7vs+z+czO50OW60Wt9stK5UKp9Mpj8cjq9WqDTBHnjAdxzGQZrPJw+HA31oulzbAWgLoA0CWZVBKIY5jzGYzdLtdE9DlcrFNrY98zobqOA6TJKHW2jg4nU5sNBpFDp6mhVe5rsvVasUwDHm9Xqm15u12o+/7Hy0gD8KatOd5vN/v1FozTVN6nkchxFuI6hsAAIMg4OPxMJCXdtTbR7JJCMEgCJhlGUlyPB4XfumozInrupxMJpRSRtZlKoNYl+m/6/wDuWAjtPfsQuwAAAAASUVORK5CYII=";
function getCheckedText(enabled) {
return enabled ? "Enabled" : "Off";
}
function getAdvancedSettingDefinition2(key) {
return ADVANCED_SETTING_DEFINITIONS.find((definition) => definition.key === key);
}
function getAdvancedSettingValueText(definition, value) {
if (definition.kind === "boolean") {
return value === true || value === "true" ? "Enabled" : "Off";
}
return `${value}${definition.unit ? ` ${definition.unit}` : ""}`;
}
function getFeatureDefinition(featureDefinitions, featureKey) {
return featureDefinitions.find((feature) => feature.key === featureKey);
}
function createQolboxMenuMarkup(options) {
function getOnboardingSteps() {
const featureSteps = options.featureDefinitions.map((feature) => ({
type: "feature",
featureKey: feature.key,
title: feature.title,
text: feature.onboardingText || feature.summary
}));
return [
{
type: "intro",
title: "Welcome to QOLBox",
text: `${options.versionLabel} adds fullscreen layout, full-lobby reserves, audio controls, away-tab alerts, mobile Grab, readable chat, lobby commands, and setup controls. Choose Express for the recommended setup or Custom to decide feature by feature.`
},
...featureSteps,
{
type: "finish",
title: "QOLBox is ready",
text: `On desktop, press ${options.menuKeyLabel} to open QOLBox later. On mobile, open the site's hamburger dropdown and choose QOLBox. The menu lets you change features, advanced defaults, and setup choices at any time.`
}
];
}
function getToggleMarkup({
action,
active,
ariaLabel,
dataName,
dataValue
}) {
return `
<div class="qolboxMenuToggleGroup" role="group" aria-label="${escapeMenuText(ariaLabel)}">
<button class="qolboxMenuToggle${active ? " active" : ""}" data-qolbox-action="${action}" ${dataName}="${escapeMenuText(dataValue)}" data-enabled="true" data-value="true" aria-pressed="${active ? "true" : "false"}">Enabled</button>
<button class="qolboxMenuToggle${active ? "" : " active"}" data-qolbox-action="${action}" ${dataName}="${escapeMenuText(dataValue)}" data-enabled="false" data-value="false" aria-pressed="${active ? "false" : "true"}">Off</button>
</div>
`;
}
function getOnboardingToggleMarkup(featureKey) {
return getToggleMarkup({
action: "set-feature",
active: options.isFeatureEnabled(featureKey),
ariaLabel: `${featureKey} setting`,
dataName: "data-feature",
dataValue: featureKey
});
}
function getDraftFeatureToggleMarkup(featureKey, draft) {
return getToggleMarkup({
action: "draft-feature",
active: draft.features[featureKey] !== false,
ariaLabel: `${featureKey} setting`,
dataName: "data-feature",
dataValue: featureKey
});
}
function getOnboardingSummaryMarkup() {
const enabledFeatures = options.featureDefinitions.filter((feature) => options.isFeatureEnabled(feature.key)).map((feature) => feature.shortTitle).join(", ");
return `
<div class="qolboxMenuInfoBox">
<div class="qolboxMenuFeatureName">Selected setup</div>
<div class="qolboxMenuFeatureSummary">${escapeMenuText(enabledFeatures || "No optional features enabled")}</div>
</div>
`;
}
function getOnboardingStepMarkup(onboardingStepIndex) {
const steps = getOnboardingSteps();
const step = steps[Math.max(0, Math.min(onboardingStepIndex, steps.length - 1))];
const isFeatureStep = step.type === "feature";
const isFirstStep = onboardingStepIndex === 0;
const isFinalStep = onboardingStepIndex === steps.length - 1;
const progress = steps.map((_, index) => `<span class="qolboxMenuDot${index === onboardingStepIndex ? " active" : ""}"></span>`).join("");
if (isFirstStep) {
return `
<div class="qolboxMenuBody">
<div class="qolboxMenuHeaderLine">
<h1 class="qolboxMenuTitle">${escapeMenuText(step.title)}</h1>
</div>
<p class="qolboxMenuText">${escapeMenuText(step.text)}</p>
<div class="qolboxMenuChoiceGrid">
<button class="qolboxMenuChoice primary" data-qolbox-action="choose-express">
<span>Express</span>
<small>Recommended defaults. You can change everything later.</small>
</button>
<button class="qolboxMenuChoice" data-qolbox-action="choose-custom">
<span>Custom</span>
<small>Review each feature during setup.</small>
</button>
</div>
<div class="qolboxMenuActions">
<button class="qolboxMenuButton" data-qolbox-action="skip-onboarding">Skip</button>
</div>
</div>
`;
}
return `
<div class="qolboxMenuBody">
<div class="qolboxMenuHeaderLine">
<h1 class="qolboxMenuTitle">${escapeMenuText(step.title)}</h1>
</div>
<p class="qolboxMenuText">${escapeMenuText(step.text)}</p>
${isFeatureStep && step.featureKey ? getOnboardingToggleMarkup(step.featureKey) : getOnboardingSummaryMarkup()}
<div class="qolboxMenuProgress" aria-hidden="true">${progress}</div>
<div class="qolboxMenuActions">
<button class="qolboxMenuButton" data-qolbox-action="back">Back</button>
<button class="qolboxMenuButton primary" data-qolbox-action="${isFinalStep ? "finish-onboarding" : "next"}">${isFinalStep ? "Finish" : "Next"}</button>
</div>
</div>
`;
}
function getSettingsTabsMarkup(activePage) {
return `
<div class="qolboxMenuTabs" role="tablist" aria-label="QOLBox settings sections">
${SETTINGS_PAGES.map((page) => `
<button class="qolboxMenuTab${page.key === activePage ? " active" : ""}" role="tab" aria-selected="${page.key === activePage ? "true" : "false"}" data-qolbox-action="settings-page" data-page="${page.key}">${escapeMenuText(page.title)}</button>
`).join("")}
</div>
`;
}
function getFeatureRowMarkup(featureKey, draft) {
const feature = getFeatureDefinition(options.featureDefinitions, featureKey);
return `
<div class="qolboxMenuFeatureRow">
<div>
<div class="qolboxMenuFeatureName">${escapeMenuText(feature.title)}</div>
<div class="qolboxMenuFeatureSummary">${escapeMenuText(feature.summary)} Current draft: ${escapeMenuText(getCheckedText(draft.features[feature.key] !== false))}</div>
</div>
${getDraftFeatureToggleMarkup(feature.key, draft)}
</div>
`;
}
function getAdvancedInputMarkup(definition, draft, errors) {
const value = draft.advanced[definition.key];
const error = errors[definition.key];
const invalidClass = error ? " invalid" : "";
if (definition.kind === "boolean") {
const enabled = value === true || value === "true";
return getToggleMarkup({
action: "draft-advanced",
active: enabled,
ariaLabel: `${definition.title} setting`,
dataName: "data-advanced",
dataValue: definition.key
});
}
return `
<input class="qolboxMenuInput${invalidClass}" type="number" value="${escapeMenuText(String(value))}" min="${definition.min}" max="${definition.max}" step="${definition.step}" data-qolbox-advanced-input="${escapeMenuText(definition.key)}">
${error ? `<div class="qolboxMenuFieldError">${escapeMenuText(error)}</div>` : ""}
`;
}
function getAdvancedRowMarkup(key, draft, errors) {
const definition = getAdvancedSettingDefinition2(key);
const value = draft.advanced[definition.key];
return `
<div class="qolboxMenuFeatureRow compact">
<div>
<div class="qolboxMenuFeatureName">${escapeMenuText(definition.title)}</div>
<div class="qolboxMenuFeatureSummary">${escapeMenuText(definition.description)} Current draft: ${escapeMenuText(getAdvancedSettingValueText(definition, value))}</div>
</div>
<div class="qolboxMenuFieldControl">
${getAdvancedInputMarkup(definition, draft, errors)}
</div>
</div>
`;
}
function getFeaturePageMarkup(draft) {
return `
<div class="qolboxMenuSettingsList">
${FEATURE_PAGE_KEYS2.map((featureKey) => getFeatureRowMarkup(featureKey, draft)).join("")}
</div>
<div class="qolboxMenuActions slim">
<button class="qolboxMenuButton" data-qolbox-action="reset-page">Reset Features</button>
</div>
`;
}
function getCommandsPageMarkup(draft, errors) {
return `
<div class="qolboxMenuSettingsList">
${getFeatureRowMarkup(FEATURE_LOBBY_COMMANDS, draft)}
${getAdvancedRowMarkup(ADVANCED_COMMAND_ALIASES, draft, errors)}
</div>
<div class="qolboxMenuInfoBox">Group targets: all, playing, spectators. Quote those words to target a player with that exact name. Native /kick and /ban accept exact or unique partial player names.</div>
<div class="qolboxMenuActions slim">
<button class="qolboxMenuButton" data-qolbox-action="reset-page">Reset Commands</button>
</div>
`;
}
function getAudioPageMarkup(draft) {
return `
<div class="qolboxMenuSettingsList">
${getFeatureRowMarkup(FEATURE_AUDIO, draft)}
</div>
<div class="qolboxMenuInfoBox">Game and jukebox volume levels are still adjusted from Hitbox's native hamburger menu.</div>
<div class="qolboxMenuActions slim">
<button class="qolboxMenuButton" data-qolbox-action="reset-page">Reset Audio</button>
</div>
`;
}
function getAdvancedPageMarkup(draft, errors) {
return `
<p class="qolboxMenuText">Timing and behavior defaults. These are staged here and saved together with OK.</p>
<div class="qolboxMenuSettingsList">
${ADVANCED_TIMING_KEYS2.map((key) => getAdvancedRowMarkup(key, draft, errors)).join("")}
</div>
<div class="qolboxMenuActions slim">
<button class="qolboxMenuButton" data-qolbox-action="reset-page">Reset Advanced</button>
</div>
`;
}
function getCreditsMarkup() {
return `
<div class="qolboxMenuAboutLinks">
<a class="qolboxMenuCredit" href="${escapeMenuText(options.greaseForkUrl)}" target="_blank" rel="noreferrer">
<img class="qolboxMenuCreditIcon" src="${GREASYFORK_ICON_DATA_URI}" alt="" aria-hidden="true">
<span>GreasyFork</span>
</a>
<a class="qolboxMenuCredit" href="${escapeMenuText(options.githubUrl)}" target="_blank" rel="noreferrer">
<svg class="qolboxMenuCreditSvg" viewBox="0 0 16 16" aria-hidden="true"><path fill="currentColor" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38v-1.49c-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82A7.65 7.65 0 0 1 8 3.86c.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48v2.2c0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z"/></svg>
<span>GitHub</span>
</a>
</div>
`;
}
function getAboutPageMarkup() {
return `
<div class="qolboxMenuInfoBox">
<div class="qolboxMenuFeatureName">QOLBox ${escapeMenuText(options.versionLabel)}</div>
<div class="qolboxMenuFeatureSummary">Fullscreen layout, reserve spots, audio controls, away-tab alerts, mobile Grab, readable chat, lobby commands, and setup options for hitbox.io.</div>
</div>
${getCreditsMarkup()}
`;
}
function getSettingsPageMarkup(draft, page, errors) {
switch (page) {
case "commands":
return getCommandsPageMarkup(draft, errors);
case "audio":
return getAudioPageMarkup(draft);
case "advanced":
return getAdvancedPageMarkup(draft, errors);
case "about":
return getAboutPageMarkup();
case "features":
default:
return getFeaturePageMarkup(draft);
}
}
function getSettingsMenuMarkup(draft, page, errors) {
const pageTitle = SETTINGS_PAGES.find((candidate) => candidate.key === page)?.title || "Features";
const settingsTitle = SETTINGS_PAGE_TITLES[page];
return `
<div class="qolboxMenuBody">
<div class="qolboxMenuHeaderLine">
<h1 class="qolboxMenuTitle">${escapeMenuText(settingsTitle)}</h1>
</div>
${getSettingsTabsMarkup(page)}
<div class="qolboxMenuPage" aria-label="${escapeMenuText(pageTitle)} settings">
${getSettingsPageMarkup(draft, page, errors)}
</div>
<div class="qolboxMenuActions">
<button class="qolboxMenuButton" data-qolbox-action="redo-onboarding">Redo Setup</button>
<button class="qolboxMenuButton" data-qolbox-action="cancel-settings">Cancel</button>
<button class="qolboxMenuButton primary" data-qolbox-action="save-settings">OK</button>
</div>
</div>
`;
}
function getReleaseSourceText(release) {
switch (release.source) {
case "github":
return "GitHub release";
case "greasyfork":
return "GreasyFork version history";
case "local-fallback":
default:
return "Bundled fallback";
}
}
function getReleaseDateText(release) {
if (!release.publishedAt) {
return "";
}
const timestamp = Date.parse(release.publishedAt);
return Number.isFinite(timestamp) ? ` - ${new Date(timestamp).toLocaleDateString()}` : "";
}
function getUpdateNoticeMarkup(notice, releaseHistory, pageIndex) {
const releaseNotes = releaseHistory.notes;
const safePageIndex = Math.max(0, Math.min(pageIndex, Math.max(0, releaseNotes.length - 1)));
const release = releaseNotes[safePageIndex] || null;
const notes = release ? `
<div class="qolboxMenuInfoBox">
<div class="qolboxMenuFeatureName">${escapeMenuText(release.version)}</div>
<div class="qolboxMenuFeatureSummary">${escapeMenuText(getReleaseSourceText(release))}${escapeMenuText(getReleaseDateText(release))}</div>
<ul class="qolboxMenuNoteList">
${release.notes.map((note) => `<li>${escapeMenuText(note)}</li>`).join("")}
</ul>
</div>
` : '<p class="qolboxMenuText">No version-history entries are available for this upgrade range.</p>';
const pageCount = Math.max(1, releaseNotes.length);
return `
<div class="qolboxMenuBody">
<div class="qolboxMenuHeaderLine">
<h1 class="qolboxMenuTitle">QOLBox Updated</h1>
</div>
<p class="qolboxMenuText">Updated from ${escapeMenuText(notice.previousVersion)} to ${escapeMenuText(notice.currentVersion)}.</p>
<p class="qolboxMenuText">${escapeMenuText(releaseHistory.message)} Source: ${escapeMenuText(releaseHistory.sourceLabel)}.</p>
${notes}
<div class="qolboxMenuHeaderLine">
<button class="qolboxMenuButton" data-qolbox-action="update-newer" ${safePageIndex <= 0 ? "disabled" : ""}>Newer</button>
<span class="qolboxMenuFeatureSummary">Version ${releaseNotes.length ? safePageIndex + 1 : 0} of ${pageCount}</span>
<button class="qolboxMenuButton" data-qolbox-action="update-older" ${safePageIndex >= releaseNotes.length - 1 ? "disabled" : ""}>Older</button>
</div>
<div class="qolboxMenuActions">
<button class="qolboxMenuButton primary" data-qolbox-action="acknowledge-update">OK</button>
</div>
</div>
`;
}
return {
getOnboardingStepMarkup,
getOnboardingSteps,
getSettingsMenuMarkup,
getUpdateNoticeMarkup
};
}
// src/features/qolbox-menu-feature-bundle.ts
function createQolboxMenuFeatureBundle(options) {
const initialOnboardingComplete = loadOnboardingComplete();
let pendingUpdateNotice = loadPendingUpdateNotice(void 0, initialOnboardingComplete);
let updateReleaseHistory = pendingUpdateNotice ? createInitialReleaseHistoryState(pendingUpdateNotice.previousVersion, pendingUpdateNotice.currentVersion) : null;
let updateReleaseHistoryRefreshStarted = false;
const { getOnboardingStepMarkup, getOnboardingSteps, getSettingsMenuMarkup, getUpdateNoticeMarkup } = createQolboxMenuMarkup({
featureDefinitions: FEATURE_DEFINITIONS,
greaseForkUrl: QOLBOX_GREASYFORK_URL,
githubUrl: QOLBOX_GITHUB_URL,
isFeatureEnabled: options.isFeatureEnabled,
menuKeyLabel: MENU_KEY_LABEL,
versionLabel: QOLBOX_VERSION_LABEL
});
function createSettingsDraft() {
const features = {};
for (const definition of FEATURE_DEFINITIONS) {
features[definition.key] = options.isFeatureEnabled(definition.key);
}
return {
advanced: { ...options.getAdvancedSettings() },
features
};
}
const menuController = createQolboxMenuController({
createSettingsDraft,
getOnboardingStepMarkup,
getOnboardingStepCount: () => getOnboardingSteps().length,
getSettingsMenuMarkup,
getUpdateNoticeMarkup: (pageIndex) => pendingUpdateNotice ? getUpdateNoticeMarkup(
pendingUpdateNotice,
updateReleaseHistory || createInitialReleaseHistoryState(
pendingUpdateNotice.previousVersion,
pendingUpdateNotice.currentVersion
),
pageIndex
) : getSettingsMenuMarkup(createSettingsDraft(), "features", {}),
getUpdateNoticePageCount: () => Math.max(1, updateReleaseHistory?.notes.length || 1),
initialOnboardingComplete,
menuId: QOLBOX_MENU_ID,
menuKey: MENU_KEY,
onAcknowledgeUpdateNotice: () => {
acknowledgeUpdateNotice();
pendingUpdateNotice = null;
updateReleaseHistory = null;
},
onBeforeOpen: options.ensureGlobalStyle,
onChooseExpressSetup: () => {
options.setAllFeatureSettings(getDefaultFeatureSettings());
},
onCompleteOnboarding: () => {
saveOnboardingComplete();
options.applyFeatureRootClasses();
options.applyPersistentFeatures();
options.scheduleUiWork({ force: true, features: true, passes: FULLSCREEN_SETTLE_PASSES });
},
onCommitSettingsDraft: (features, advanced) => {
options.setAllFeatureSettings(features);
options.setAdvancedSettings(advanced);
},
onMenuModeChanged: options.applyFeatureRootClasses,
onSetFeatureEnabled: options.setFeatureEnabled
});
const { scheduleFirstBootOnboarding } = createFirstBootOnboardingScheduler({
isOnboardingComplete: menuController.isOnboardingComplete,
showFirstBootOnboarding: menuController.showFirstBootOnboarding
});
function scheduleStartupQolboxNotice() {
if (!menuController.isOnboardingComplete()) {
scheduleFirstBootOnboarding();
return;
}
if (!pendingUpdateNotice) {
return;
}
refreshUpdateReleaseHistory();
const show = () => {
window.setTimeout(menuController.showUpdateNotice, 0);
};
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", show, { once: true });
} else {
show();
}
}
function refreshUpdateReleaseHistory() {
if (!pendingUpdateNotice || updateReleaseHistoryRefreshStarted) {
return;
}
updateReleaseHistoryRefreshStarted = true;
loadReleaseHistoryState(pendingUpdateNotice.previousVersion, pendingUpdateNotice.currentVersion).then((nextHistory) => {
updateReleaseHistory = nextHistory;
if (menuController.getMode() === "update") {
menuController.renderQolboxMenu();
}
}).catch(() => {
});
}
return {
...menuController,
getOnboardingSteps,
scheduleFirstBootOnboarding: scheduleStartupQolboxNotice
};
}
// src/features/feature-root-classes.ts
function getFeatureRootClass(featureKey) {
return `qolbox-feature-${featureKey}`;
}
function createFeatureRootClassController(options) {
function applyFeatureRootClasses() {
const root = document.documentElement;
if (!root || !root.classList) {
return;
}
for (const feature of options.featureDefinitions) {
root.classList.toggle(getFeatureRootClass(feature.key), options.isFeatureActive(feature.key));
}
root.classList.toggle(options.menuRootClass, !options.isMenuClosed());
}
return {
applyFeatureRootClasses
};
}
// src/features/global-style-fullscreen.ts
function prefixSelectorList(prefix, selectorList) {
return selectorList.split(",").map((selector) => `${prefix} ${selector.trim()}`).join(",\n ");
}
function getFullscreenGlobalStyleText(options) {
return `
html.qolbox-feature-fullscreen,
html.qolbox-feature-fullscreen body {
width: 100vw !important;
height: 100vh !important;
margin: 0 !important;
overflow: hidden !important;
background: #0a0a0a !important;
}
html.qolbox-feature-fullscreen #appContainer,
html.qolbox-feature-fullscreen #relativeContainer {
margin: 0 !important;
max-width: none !important;
max-height: none !important;
border: 0 !important;
}
html.qolbox-feature-fullscreen #backgroundImage,
html.qolbox-feature-fullscreen .mainMenuFancy {
position: fixed !important;
left: 0 !important;
top: 0 !important;
right: auto !important;
bottom: auto !important;
width: 100vw !important;
height: 100vh !important;
max-width: none !important;
max-height: none !important;
}
${prefixSelectorList("html.qolbox-feature-fullscreen", options.fullscreenRenderLayerSelector)} {
position: absolute !important;
margin: 0 !important;
max-width: none !important;
max-height: none !important;
overflow: hidden !important;
transform: none !important;
}
html.qolbox-feature-fullscreen #editorContainer {
overflow: visible !important;
transform-origin: top left !important;
}
${prefixSelectorList("html.qolbox-feature-fullscreen", options.fullscreenRenderCanvasSelector)} {
display: block !important;
max-width: none !important;
max-height: none !important;
transform: none !important;
}
/* Keep game keyboard focus after chat closes without drawing a browser focus ring over the playfield. */
${prefixSelectorList("html.qolbox-feature-chat", options.fullscreenRenderCanvasFocusSelector)} {
outline: 0 !important;
outline-color: transparent !important;
outline-style: none !important;
outline-width: 0 !important;
}
html.qolbox-feature-fullscreen .scores {
display: none !important;
}
html.qolbox-feature-fullscreen .spectateControls {
bottom: 12px !important;
}
html.qolbox-feature-fullscreen .scores .title {
background-color: rgb(56, 56, 56) !important;
}
html.qolbox-feature-fullscreen .scores .title,
html.qolbox-feature-fullscreen .scores .entryContainer,
html.qolbox-feature-fullscreen .scores .entryContainer .number,
html.qolbox-feature-fullscreen .scores .entryContainer .name {
vertical-align: middle !important;
}
`;
}
// src/features/global-style-reserve.ts
function getReserveGlobalStyleText() {
return `
html.qolbox-feature-reserve body.qolbox-reserve-active .connectingWindowContainer:not(.qolboxReserveWindowContainer) {
display: none !important;
}
.qolboxReserveWindowContainer {
display: none;
z-index: 10000;
}
html.qolbox-feature-reserve body.qolbox-reserve-active .qolboxReserveWindowContainer {
display: block !important;
}
html.qolbox-feature-reserve .roomListContainer .bottomButton.right.qolboxReserveUnavailable {
cursor: not-allowed !important;
filter: grayscale(1) saturate(0.35) !important;
opacity: 0.48 !important;
}
.qolboxReserveWindowContainer .qolboxReserveContent {
align-items: center;
bottom: 48px;
display: flex;
flex-direction: column;
gap: 4px;
justify-content: center;
left: 16px;
pointer-events: none;
position: absolute;
right: 16px;
text-align: center;
top: 50px;
}
.qolboxReserveWindowContainer .connectingWindow .spinner {
bottom: auto !important;
flex: 0 0 auto;
left: auto !important;
margin: 0 auto;
order: 2;
position: static !important;
right: auto !important;
top: auto !important;
}
.qolboxReserveWindowContainer .qolboxReserveStatus,
.qolboxReserveWindowContainer .qolboxReserveCountdown,
.qolboxReserveWindowContainer .qolboxReserveMessage {
width: 100%;
}
.qolboxReserveWindowContainer .qolboxReserveStatus {
color: rgb(205, 210, 218);
font-size: 11px;
line-height: 14px;
min-height: 14px;
order: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.qolboxReserveWindowContainer .qolboxReserveCountdown {
color: rgb(242, 242, 242);
font-size: 13px;
line-height: 16px;
min-height: 16px;
order: 3;
white-space: nowrap;
}
.qolboxReserveWindowContainer .qolboxReserveMessage {
color: rgb(242, 242, 242);
font-size: 13px;
line-height: 16px;
order: 1;
white-space: normal;
}
`;
}
// src/features/global-style-chat.ts
function getChatGlobalStyleText() {
return `
html.qolbox-feature-chat .inGameChat {
pointer-events: none;
}
html.qolbox-feature-chat .inGameChat.qolboxChatInteractive {
pointer-events: auto;
}
html.qolbox-feature-chat .inGameChat .input {
pointer-events: none;
}
html.qolbox-feature-chat .inGameChat .input:focus,
html.qolbox-feature-chat .inGameChat .input.bgActive {
pointer-events: auto;
}
html.qolbox-feature-chat .inGameChat:hover,
html.qolbox-feature-chat .inGameChat.qolboxChatReading {
opacity: 1 !important;
}
html.qolbox-feature-chat .inGameChat.qolboxChatReading {
overflow: hidden !important;
overscroll-behavior: contain;
}
`;
}
// src/features/global-style-menu.ts
function getQolboxMenuGlobalStyleText() {
return `
.qolboxMenuOverlay {
align-items: center;
background: rgba(0, 0, 0, 0.72);
box-sizing: border-box;
display: flex;
font-family: inherit;
inset: 0;
justify-content: center;
opacity: 0;
padding: 10px;
pointer-events: none;
position: fixed;
z-index: 2147483647;
}
html.qolbox-menu-open .qolboxMenuOverlay {
opacity: 1;
pointer-events: auto;
}
.qolboxMenuPanel {
background: rgba(22, 24, 28, 0.98);
border: 2px solid rgb(69, 75, 86);
border-radius: 4px;
box-shadow: 0 8px 28px rgba(0, 0, 0, 0.55);
box-sizing: border-box;
color: #f4f4f4;
display: flex;
flex-direction: column;
max-height: calc(100vh - 20px);
max-width: min(430px, calc(100vw - 20px));
overflow: hidden;
width: 430px;
}
.qolboxMenuBody {
box-sizing: border-box;
display: flex;
flex: 1 1 auto;
flex-direction: column;
gap: 8px;
min-height: 0;
overflow: auto;
padding: 12px;
}
.qolboxMenuTitle {
color: #ffffff;
font-size: 18px;
font-weight: 700;
letter-spacing: 0;
line-height: 22px;
margin: 0;
}
.qolboxMenuHeaderLine {
align-items: center;
display: flex;
gap: 8px;
justify-content: space-between;
}
.qolboxMenuText {
color: #d7dbe1;
font-size: 12px;
line-height: 16px;
margin: 0;
}
.qolboxMenuProgress {
align-items: center;
display: flex;
gap: 4px;
margin-top: 2px;
}
.qolboxMenuDot {
background: rgba(255, 255, 255, 0.25);
border-radius: 999px;
height: 5px;
width: 12px;
}
.qolboxMenuDot.active {
background: #f5c542;
}
.qolboxMenuToggleGroup {
background: rgb(31, 34, 39);
border: 1px solid rgb(72, 78, 89);
border-radius: 3px;
display: grid;
grid-template-columns: 1fr 1fr;
overflow: hidden;
}
.qolboxMenuButton,
.qolboxMenuToggle {
appearance: none;
border: 0;
box-sizing: border-box;
cursor: pointer;
font-family: inherit;
font-size: 12px;
font-weight: 700;
letter-spacing: 0;
line-height: 14px;
min-height: 28px;
}
.qolboxMenuToggle {
background: transparent;
color: #cfd3da;
}
.qolboxMenuToggle + .qolboxMenuToggle {
border-left: 1px solid rgba(255, 255, 255, 0.14);
}
.qolboxMenuToggle.active {
background: #f5c542;
color: #111111;
}
.qolboxMenuActions {
display: flex;
gap: 6px;
justify-content: flex-end;
margin-top: 4px;
}
.qolboxMenuActions.slim {
margin-top: 0;
}
.qolboxMenuButton {
background: rgb(47, 51, 58);
border: 1px solid rgb(92, 98, 108);
border-radius: 3px;
color: #f4f4f4;
padding: 0 12px;
}
.qolboxMenuButton.primary {
background: #f5c542;
color: #111111;
}
.qolboxMenuButton:disabled {
cursor: default;
opacity: 0.45;
}
.qolboxMenuSettingsList {
display: grid;
gap: 6px;
}
.qolboxMenuTabs {
display: grid;
gap: 4px;
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.qolboxMenuTab {
appearance: none;
background: rgb(31, 34, 39);
border: 1px solid rgb(72, 78, 89);
border-radius: 3px;
color: #cfd3da;
cursor: pointer;
font-family: inherit;
font-size: 11px;
font-weight: 700;
letter-spacing: 0;
line-height: 13px;
min-height: 26px;
}
.qolboxMenuTab.active {
background: #f5c542;
border-color: #f5c542;
color: #111111;
}
.qolboxMenuPage {
display: grid;
gap: 8px;
min-height: 172px;
}
.qolboxMenuChoiceGrid {
display: grid;
gap: 6px;
grid-template-columns: 1fr 1fr;
}
.qolboxMenuChoice {
appearance: none;
background: rgb(47, 51, 58);
border: 1px solid rgb(92, 98, 108);
border-radius: 3px;
color: #f4f4f4;
cursor: pointer;
display: grid;
font-family: inherit;
gap: 3px;
min-height: 62px;
padding: 9px;
text-align: left;
}
.qolboxMenuChoice.primary {
border-color: #f5c542;
}
.qolboxMenuChoice span {
color: #ffffff;
font-size: 13px;
font-weight: 700;
line-height: 15px;
}
.qolboxMenuChoice small {
color: #c4c9d1;
font-size: 10px;
line-height: 13px;
}
.qolboxMenuFeatureRow {
align-items: center;
border-bottom: 1px solid rgba(255, 255, 255, 0.09);
display: grid;
gap: 8px;
grid-template-columns: minmax(0, 1fr) 108px;
padding: 0 0 6px;
}
.qolboxMenuFeatureRow.compact {
grid-template-columns: minmax(0, 1fr) 150px;
}
.qolboxMenuFeatureName {
color: #ffffff;
font-size: 12px;
font-weight: 700;
line-height: 15px;
}
.qolboxMenuFeatureSummary {
color: #c4c9d1;
font-size: 10px;
line-height: 13px;
margin-top: 1px;
}
.qolboxMenuFieldControl {
display: grid;
gap: 3px;
}
.qolboxMenuInput {
appearance: none;
background: rgb(31, 34, 39);
border: 1px solid rgb(72, 78, 89);
border-radius: 3px;
box-sizing: border-box;
color: #f4f4f4;
font-family: inherit;
font-size: 12px;
line-height: 16px;
min-height: 26px;
min-width: 0;
padding: 0 6px;
width: 100%;
}
.qolboxMenuInput.invalid {
border-color: #f05f57;
}
.qolboxMenuFieldError {
color: #ffaaa4;
font-size: 10px;
line-height: 12px;
}
.qolboxMenuWarning,
.qolboxMenuInfoBox {
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 3px;
color: #d7dbe1;
font-size: 10px;
line-height: 13px;
padding: 6px;
}
.qolboxMenuWarning {
border-color: rgba(245, 197, 66, 0.45);
}
.qolboxMenuNoteList {
color: #d7dbe1;
font-size: 11px;
line-height: 15px;
margin: 5px 0 0;
padding-left: 16px;
}
.qolboxMenuAboutLinks {
display: grid;
gap: 6px;
}
.qolboxMenuCredit {
align-items: center;
background: rgb(31, 34, 39);
border: 1px solid rgb(72, 78, 89);
border-radius: 3px;
color: #e6e9ee;
display: flex;
gap: 8px;
font-size: 10px;
font-weight: 700;
line-height: 14px;
min-height: 30px;
padding: 0 8px;
text-decoration: none;
}
.qolboxMenuCreditIcon {
background: rgba(255, 255, 255, 0.12);
border-radius: 2px;
display: block;
flex: 0 0 auto;
height: 18px;
object-fit: contain;
padding: 1px;
width: 18px;
}
.qolboxMenuCreditSvg {
fill: currentColor;
height: 16px;
width: 16px;
}
@media (max-height: 620px) {
.qolboxMenuBody {
gap: 6px;
padding: 9px;
}
}
@media (max-width: 420px) {
.qolboxMenuTabs {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.qolboxMenuChoiceGrid,
.qolboxMenuFeatureRow,
.qolboxMenuFeatureRow.compact {
grid-template-columns: 1fr;
}
}
@media (prefers-reduced-motion: reduce) {
.qolboxMenuOverlay {
transition: none !important;
}
}
`;
}
// src/features/global-style-mobile-grab.ts
function getMobileGrabGlobalStyleText(options) {
return `
.buttonArea.qolboxMobileGrabButton {
background-image: url("${options.mobileGrabIconHref}") !important;
background-position: center center !important;
background-repeat: no-repeat !important;
background-size: 68% !important;
box-sizing: border-box !important;
display: none;
transform: none !important;
z-index: 12;
}
`;
}
// src/features/global-style-typing.ts
function getTypingGlobalStyleText() {
return `
.scores .entryContainer .qolboxTypingIndicator {
background-image: url("graphics/ui/typing.svg");
background-position: center center;
background-repeat: no-repeat;
background-size: contain;
display: inline-block;
height: 14px;
margin-left: 5px;
pointer-events: none;
vertical-align: -2px;
width: 14px;
}
@supports ((-webkit-mask-image: url("graphics/ui/typing.svg")) or (mask-image: url("graphics/ui/typing.svg"))) {
.scores .entryContainer .qolboxTypingIndicator {
background-color: currentColor;
background-image: none;
-webkit-mask-image: url("graphics/ui/typing.svg");
mask-image: url("graphics/ui/typing.svg");
-webkit-mask-position: center center;
mask-position: center center;
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size: contain;
mask-size: contain;
}
}
.qolboxWorldTypingLayer {
left: 0;
pointer-events: none;
position: fixed;
top: 0;
z-index: 12;
}
.qolboxWorldTypingIndicator {
background-color: rgba(37, 38, 42, 0.82);
background-image: url("graphics/ui/typing.svg");
background-position: center center;
background-repeat: no-repeat;
background-size: 14px 14px;
border-radius: 3px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
height: 18px;
pointer-events: none;
position: fixed;
transform: translate(-50%, -100%);
width: 22px;
}
`;
}
// src/features/global-style.ts
function getGlobalStyleText(options) {
return `
${getFullscreenGlobalStyleText(options)}
${getTypingGlobalStyleText()}
${getChatGlobalStyleText()}
.qolboxSwitchTeamsButton.qolboxSwitchTeamsButtonBusy {
cursor: not-allowed !important;
opacity: 0.62;
}
html.qolbox-feature-fullscreen #email,
html.qolbox-feature-fullscreen #songcredit,
html.qolbox-feature-fullscreen #betaLink {
display: none !important;
}
${getReserveGlobalStyleText()}
${getQolboxMenuGlobalStyleText()}
${getMobileGrabGlobalStyleText(options)}
`;
}
function createGlobalStyleController(options) {
function ensureGlobalStyle() {
if (document.getElementById(options.styleId)) {
return true;
}
const styleHost = document.head || document.documentElement;
if (!styleHost) {
return false;
}
const style = document.createElement("style");
style.id = options.styleId;
style.textContent = getGlobalStyleText(options);
styleHost.appendChild(style);
return true;
}
return {
ensureGlobalStyle
};
}
// src/features/qolbox-shell-feature-bundle.ts
function createQolboxShellFeatureBundle(options) {
const { ensureGlobalStyle } = createGlobalStyleController({
styleId: "qolbox-style",
fullscreenRenderLayerSelector: FULLSCREEN_RENDER_LAYER_SELECTOR,
fullscreenRenderCanvasSelector: FULLSCREEN_RENDER_CANVAS_SELECTOR,
fullscreenRenderCanvasFocusSelector: FULLSCREEN_RENDER_CANVAS_FOCUS_SELECTOR,
mobileGrabIconHref: MOBILE_GRAB_ICON_HREF
});
const { applyFeatureRootClasses } = createFeatureRootClassController({
featureDefinitions: FEATURE_DEFINITIONS,
isMenuClosed: options.isMenuClosed,
isFeatureActive: options.isFeatureActive,
menuRootClass: QOLBOX_MENU_ROOT_CLASS
});
return {
applyFeatureRootClasses,
ensureGlobalStyle
};
}
// src/hitbox/auto-join-adapter.ts
function getNativeAutoJoin() {
return readNativeProperty(window, "autoJoin");
}
function isNativeAutoJoinMatch(joinId, password) {
const autoJoin = getNativeAutoJoin();
if (!isNativeObject(autoJoin)) {
return false;
}
return joinId === readNativeProperty(autoJoin, "address") && password === readNativeProperty(autoJoin, "passbypass");
}
function isNativeAutoJoinOnePersonRoom() {
const autoJoin = getNativeAutoJoin();
if (!isNativeObject(autoJoin)) {
return false;
}
const maxPlayers = Number(
readNativeProperty(autoJoin, "maxPlayers") || readNativeProperty(autoJoin, "maxplayers") || readNativeProperty(autoJoin, "max")
);
return Number.isFinite(maxPlayers) && maxPlayers === 1;
}
// src/hitbox/reserve-socket-emit-patcher.ts
function isNativeReserveCallable(value) {
return typeof value === "function";
}
function patchReserveSocketEmitTarget(target, options) {
if (!target) {
return false;
}
const nativeEmit = readNativeReflectProperty(target, "emit");
if (readNativeReflectProperty(target, "__qolboxReservePatched") || !isNativeReserveCallable(nativeEmit)) {
return false;
}
const baseEmit = nativeEmit;
function wrappedReserveEmit(eventName, ...args) {
if (options.shouldCaptureJoin(args)) {
options.onJoin(this, eventName, args);
}
return Reflect.apply(baseEmit, this, [eventName, ...args]);
}
setNativeReflectProperty(target, "emit", wrappedReserveEmit);
setNativeReflectProperty(target, "__qolboxReservePatched", true);
setNativeReflectProperty(target, options.originalEmitKey, baseEmit);
return true;
}
// src/hitbox/reserve-socket-adapter.ts
function emitReserveSocketJoinAttempt(attempt, options) {
const emit = readNativeProperty(attempt?.socket, "emit");
if (!attempt || !isNativeReserveCallable(emit)) {
return false;
}
const connect = readNativeProperty(attempt.socket, "connect");
if (!readNativeProperty(attempt.socket, "connected") && isNativeReserveCallable(connect)) {
try {
Reflect.apply(connect, attempt.socket, []);
} catch {
return false;
}
}
try {
options.beforeEmit();
Reflect.apply(emit, attempt.socket, [attempt.eventName, ...attempt.args.map(options.cloneValue)]);
return true;
} catch {
return false;
}
}
function createReserveSocketCaptureHook(options) {
let socketHookInstalled = false;
function patchSocket(socket) {
patchReserveSocketEmitTarget(socket, {
onJoin: options.onJoin,
originalEmitKey: "__qolboxReserveOriginalEmit",
shouldCaptureJoin: options.shouldCaptureJoin
});
return socket;
}
function patchSocketPrototype(ioFactory) {
const prototype = readNativeReflectProperty(readNativeReflectProperty(ioFactory, "Socket"), "prototype");
if (!prototype) {
return;
}
patchReserveSocketEmitTarget(prototype, {
onJoin: options.onJoin,
originalEmitKey: "__qolboxReserveOriginalEmit",
shouldCaptureJoin: options.shouldCaptureJoin
});
}
function patchIo(ioFactory) {
if (!isNativeReserveCallable(ioFactory) || readNativeReflectProperty(ioFactory, "__qolboxReservePatched")) {
patchSocketPrototype(ioFactory);
return ioFactory;
}
const baseIoFactory = ioFactory;
function wrappedReserveIo(...args) {
return patchSocket(Reflect.apply(baseIoFactory, this, args));
}
try {
Object.setPrototypeOf(wrappedReserveIo, Object.getPrototypeOf(baseIoFactory));
} catch {
}
for (const key of Reflect.ownKeys(baseIoFactory)) {
try {
setNativeReflectProperty(wrappedReserveIo, key, readNativeReflectProperty(baseIoFactory, key));
} catch {
}
}
setNativeReflectProperty(wrappedReserveIo, "__qolboxReservePatched", true);
setNativeReflectProperty(wrappedReserveIo, "__qolboxReserveOriginal", baseIoFactory);
patchSocketPrototype(wrappedReserveIo);
return wrappedReserveIo;
}
function installReserveSocketCaptureHook() {
if (socketHookInstalled) {
return;
}
socketHookInstalled = true;
try {
let ioValue = readNativeReflectProperty(window, "io");
Object.defineProperty(window, "io", {
configurable: true,
enumerable: true,
get() {
return ioValue;
},
set(value) {
ioValue = patchIo(value);
}
});
if (ioValue) {
setNativeReflectProperty(window, "io", ioValue);
}
} catch {
const ioValue = readNativeReflectProperty(window, "io");
if (ioValue) {
setNativeReflectProperty(window, "io", patchIo(ioValue));
}
}
}
return {
installReserveSocketCaptureHook
};
}
// src/features/reserve-action-controls.ts
var SELECTED_RESERVE_ROW_SELECTOR = ".roomListContainer .scrollBox tr.SELECTED";
function getText(element) {
return (element.textContent || "").trim();
}
function setDatasetValue(element, key, value) {
if (hasDataset(element)) {
element.dataset[key] = value;
}
}
function createReserveActionControls(options) {
let passwordPromptPending = false;
function clearReservePasswordPromptPending() {
passwordPromptPending = false;
}
function isReservePasswordPromptPending() {
return passwordPromptPending;
}
function setReservePasswordPromptPending(pending) {
passwordPromptPending = Boolean(pending);
}
function syncReserveJoinButtonLabel() {
const button = options.getReserveJoinButton();
if (!(button instanceof Element)) {
return;
}
if (!options.isEnabled()) {
setDatasetValue(button, "qolboxReserveFull", "false");
setDatasetValue(button, "qolboxReserveUnavailable", "false");
button.classList.remove("qolboxReserveUnavailable");
button.removeAttribute("aria-disabled");
if (getText(button) === options.reserveButtonText) {
button.textContent = options.joinButtonText;
}
return;
}
const selectedState = options.getReserveSelectedRoomState();
const shouldReserve = selectedState.full || selectedState.unavailable;
const isUnavailable = selectedState.unavailable;
const nextText = shouldReserve ? options.reserveButtonText : options.joinButtonText;
if (getText(button) !== nextText) {
button.textContent = nextText;
}
setDatasetValue(button, "qolboxReserveFull", shouldReserve ? "true" : "false");
setDatasetValue(button, "qolboxReserveUnavailable", isUnavailable ? "true" : "false");
button.classList.toggle("qolboxReserveUnavailable", isUnavailable);
button.setAttribute("aria-disabled", isUnavailable ? "true" : "false");
}
function syncReservePasswordPrompt() {
if (!options.isEnabled()) {
clearReservePasswordPromptPending();
return;
}
const container = document.querySelector(".passwordWindowContainer");
const joinButton = container?.querySelector(".joinButton") || null;
if (!options.isElementVisible(container) || !joinButton) {
clearReservePasswordPromptPending();
return;
}
if (passwordPromptPending && getText(joinButton) !== options.reserveButtonText) {
joinButton.textContent = options.reserveButtonText;
}
}
function clearReserveVisibleRoomSelection() {
for (const row of document.querySelectorAll(SELECTED_RESERVE_ROW_SELECTOR)) {
row.classList.remove("SELECTED");
}
syncReserveJoinButtonLabel();
}
return {
clearReservePasswordPromptPending,
clearReserveVisibleRoomSelection,
isReservePasswordPromptPending,
setReservePasswordPromptPending,
syncReserveJoinButtonLabel,
syncReservePasswordPrompt
};
}
// src/features/reserve-join-payload.ts
function cloneReserveJoinValue(value) {
try {
const cloned = JSON.parse(JSON.stringify(value));
return cloned;
} catch {
return value;
}
}
function isReserveJoinPayload(value) {
return Boolean(
isReflectableObject(value) && (typeof getReserveJoinPayloadJoinId(value) === "string" || Object.prototype.hasOwnProperty.call(value, "playerName") && Object.prototype.hasOwnProperty.call(value, "peerID") && Object.prototype.hasOwnProperty.call(value, "password"))
);
}
function getReserveJoinPayload(args) {
return args.find(isReserveJoinPayload) || null;
}
function getReserveJoinPayloadJoinId(payload) {
return readObjectProperty(payload, "joinID");
}
function getReserveJoinPayloadPassword(payload) {
return readObjectProperty(payload, "password");
}
// src/features/reserve-captured-join.ts
function isAutoReserveJoin(payload, options) {
if (!payload) {
return false;
}
return options.isAutoJoinMatch(getReserveJoinPayloadJoinId(payload), getReserveJoinPayloadPassword(payload));
}
function createReserveCapturedJoinController(options) {
let capturedJoin = null;
function clearReserveCapturedJoin() {
capturedJoin = null;
}
function getReserveCapturedJoin() {
return capturedJoin;
}
function getRetryCapturedJoin() {
return options.getState()?.capturedJoin || capturedJoin;
}
function captureReserveJoin(socket, eventName, args) {
if (!options.isEnabled()) {
return;
}
const payload = getReserveJoinPayload(args);
if (!payload) {
return;
}
capturedJoin = {
socket,
eventName,
args: args.map(cloneReserveJoinValue),
autoReserve: isAutoReserveJoin(payload, options),
time: Date.now()
};
const state = options.getState();
if (state?.active) {
state.capturedJoin = capturedJoin;
}
options.onCaptured();
}
function shouldWatchRecentReserveCapture() {
return Boolean(
capturedJoin && Date.now() - capturedJoin.time < options.capturedJoinFreshMs && !options.hasSuccessfulJoinLayer()
);
}
function canAutoReserveCapturedJoin() {
return Boolean(options.getState()?.active || capturedJoin?.autoReserve);
}
function emitReserveJoinAttempt() {
return emitReserveSocketJoinAttempt(getRetryCapturedJoin(), {
beforeEmit: options.suppressRetryAudio,
cloneValue: cloneReserveJoinValue
});
}
return {
canAutoReserveCapturedJoin,
captureReserveJoin,
clearReserveCapturedJoin,
emitReserveJoinAttempt,
getReserveCapturedJoin,
shouldWatchRecentReserveCapture
};
}
// src/features/reserve-connecting-state.ts
function createReserveConnectingStateController(options) {
function handleReserveConnectingState() {
if (!options.isEnabled()) {
if (options.getState()) {
options.stopReserveSpot();
}
return;
}
const nativeText = options.getNativeConnectingText();
if (options.isRoomFullSuppressed() && options.hasSuccessfulJoinLayer() && options.roomFullPattern.test(nativeText)) {
options.hideNativeConnectingWindows();
return;
}
if (options.getState()?.active && options.hasSuccessfulJoinLayer()) {
options.stopAfterSuccessfulJoin();
return;
}
if (options.getState()?.active && options.roomClosedPattern.test(nativeText)) {
options.stopReserveSpot();
return;
}
if (options.getState()?.active && options.wrongPasswordPattern.test(nativeText)) {
options.showTerminalMessage("wrong-password", options.getReserveNativeMessage(options.wrongPasswordPattern));
return;
}
const canAutoReserve = options.canAutoReserveCapturedJoin();
if (options.roomFullPattern.test(nativeText) && canAutoReserve) {
if (options.isAutoJoinOnePersonRoom()) {
options.showOnePersonUnavailable();
options.hideNativeConnectingWindows();
return;
}
options.startReserveSpot("room-full");
options.scheduleReserveRetry();
}
}
return {
handleReserveConnectingState
};
}
// src/features/reserve-countdown-timer.ts
function createReserveCountdownTimer(options) {
let countdownTimer = 0;
function clearReserveCountdownTimer() {
if (countdownTimer) {
window.clearTimeout(countdownTimer);
countdownTimer = 0;
}
}
function scheduleReserveCountdownUpdate() {
if (countdownTimer || !options.getState()?.active) {
return;
}
countdownTimer = window.setTimeout(() => {
countdownTimer = 0;
if (!options.getState()?.active) {
return;
}
options.onTick();
scheduleReserveCountdownUpdate();
}, options.intervalMs);
}
return {
clearReserveCountdownTimer,
scheduleReserveCountdownUpdate
};
}
// src/features/reserve-dom-event-hooks.ts
function createReserveDomEventHooks(options) {
let domEventsInstalled = false;
function installReserveDomEventHooks() {
if (domEventsInstalled) {
return;
}
domEventsInstalled = true;
document.addEventListener("click", options.onRoomListClick, true);
document.addEventListener("dblclick", options.onRoomListDoubleClick, true);
document.addEventListener("click", options.onPasswordSubmit, true);
window.addEventListener("keyup", options.onPasswordKey, true);
}
return {
installReserveDomEventHooks
};
}
// src/features/reserve-feature-patch.ts
function createReserveFeaturePatchController(options) {
function shouldContinueReserveStatusWatch() {
if (options.getState()?.active) {
return true;
}
if (options.isRoomFullSuppressed()) {
return true;
}
return options.shouldWatchRecentCapture();
}
function patchReserveSpotFeature() {
if (!options.isEnabled()) {
options.syncJoinButtonLabel();
return;
}
options.installSocketCaptureHook();
options.syncJoinButtonLabel();
options.syncPasswordPrompt();
options.handleConnectingState();
options.installDomEventHooks();
}
return {
patchReserveSpotFeature,
shouldContinueReserveStatusWatch
};
}
// src/features/reserve-interaction-events.ts
function getClosestReserveJoinButton(target) {
return target instanceof Element ? target.closest(".roomListContainer .bottomButton.right") : null;
}
function getReservePasswordSubmitButton(target) {
return target instanceof Element ? target.closest(".passwordWindowContainer .joinButton") : null;
}
function getReserveEventKey(event) {
const key = readObjectProperty(event, "key");
return typeof key === "string" ? key : "";
}
function stopReserveNativeEvent(event) {
event.preventDefault();
event.stopImmediatePropagation();
}
function clickReserveElement(element) {
const click = readObjectProperty(element, "click");
if (typeof click === "function") {
Reflect.apply(click, element, []);
}
}
// src/features/reserve-interaction-handlers.ts
function createReserveInteractionHandlers(options) {
function scheduleJoinButtonSync() {
window.setTimeout(options.syncJoinButtonLabel, 0);
}
function schedulePasswordPromptSync() {
window.setTimeout(options.syncPasswordPrompt, 0);
}
function markPasswordPromptPending() {
options.setPasswordPromptPending(true);
schedulePasswordPromptSync();
}
function showSelectedUnavailable(event) {
stopReserveNativeEvent(event);
options.clearPasswordPromptPending();
options.showOnePersonUnavailable(options.getSelectedRoomRow());
}
function cancelReserveSpot() {
if (options.getState()?.unavailable) {
options.stopReserveSpot({ clearSelection: true });
return;
}
const cancelButton = options.getNativeConnectingWindows().map((windowElement) => windowElement.querySelector(".cancelButton")).find(Boolean);
if (cancelButton) {
clickReserveElement(cancelButton);
}
options.stopReserveSpot();
}
function handleReserveRoomListClick(event) {
if (!options.isEnabled()) {
return;
}
const row = options.getRowFromTarget(event.target);
const joinButton = getClosestReserveJoinButton(event.target);
if (row) {
options.rememberSelectedRoom(row);
scheduleJoinButtonSync();
if (options.isUnavailableRoom(row)) {
options.showOnePersonUnavailable(row);
if (joinButton) {
stopReserveNativeEvent(event);
}
}
}
if (!joinButton) {
return;
}
const selectedState = options.getSelectedRoomState();
const selectedRow = selectedState.row;
if (selectedState.unavailable) {
stopReserveNativeEvent(event);
options.showOnePersonUnavailable(selectedRow);
return;
}
if (!selectedState.full) {
options.clearPasswordPromptPending();
return;
}
if (options.isPasswordRoom(selectedRow)) {
markPasswordPromptPending();
return;
}
options.startReserveSpot("room-list");
}
function handleReserveRoomListDoubleClick(event) {
if (!options.isEnabled()) {
return;
}
const row = options.getRowFromTarget(event.target);
if (!options.isRoomFull(row)) {
return;
}
options.rememberSelectedRoom(row);
if (options.isUnavailableRoom(row)) {
stopReserveNativeEvent(event);
options.showOnePersonUnavailable(row);
return;
}
if (options.isPasswordRoom(row)) {
markPasswordPromptPending();
return;
}
options.startReserveSpot("room-list");
}
function handleReservePasswordSubmit(event) {
if (!options.isEnabled()) {
return;
}
const submitButton = getReservePasswordSubmitButton(event.target);
if (!submitButton || !options.isPasswordPromptPending()) {
return;
}
if (options.isUnavailableRoom(options.getSelectedRoomRow())) {
showSelectedUnavailable(event);
return;
}
options.clearPasswordPromptPending();
options.startReserveSpot("password-room");
}
function handleReservePasswordKey(event) {
if (!options.isEnabled()) {
return;
}
const passwordWindow = document.querySelector(".passwordWindowContainer");
if (getReserveEventKey(event) !== "Enter" || !options.isPasswordPromptPending() || !options.isElementVisible(passwordWindow)) {
return;
}
if (options.isUnavailableRoom(options.getSelectedRoomRow())) {
showSelectedUnavailable(event);
return;
}
options.clearPasswordPromptPending();
options.startReserveSpot("password-room");
}
return {
cancelReserveSpot,
handleReservePasswordKey,
handleReservePasswordSubmit,
handleReserveRoomListClick,
handleReserveRoomListDoubleClick
};
}
// src/features/reserve-lifecycle.ts
function createReserveLifecycleController(options) {
let reserveState = null;
function getReserveState() {
return reserveState;
}
function startReserveSpot(reason) {
if (!options.isEnabled()) {
return;
}
if (!reserveState?.active) {
reserveState = {
active: true,
unavailable: false,
reason,
retryTimer: 0,
nextRetryAt: Date.now() + options.getRetryDelayMs(),
retries: 0,
capturedJoin: options.getCapturedJoin(),
lastStatusText: ""
};
} else {
reserveState.reason = reserveState.reason || reason;
reserveState.capturedJoin = reserveState.capturedJoin || options.getCapturedJoin();
reserveState.nextRetryAt = reserveState.nextRetryAt || Date.now() + options.getRetryDelayMs();
reserveState.unavailable = false;
}
options.updateWaitingWindow();
options.setWaitingVisible(true);
options.scheduleStatusWatch();
options.scheduleCountdownUpdate();
}
function stopReserveSpot({ hideNative = false, clearCaptured = true, clearSelection = false } = {}) {
options.clearRetryTimer(reserveState);
options.clearStatusWatchTimer();
options.clearCountdownTimer();
if (clearCaptured) {
options.clearCapturedJoin();
}
reserveState = null;
options.clearPasswordPromptPending();
options.setWaitingVisible(false);
if (hideNative) {
options.hideNativeConnectingWindows();
}
if (clearSelection) {
options.clearVisibleRoomSelection();
} else {
options.syncJoinButtonLabel();
}
}
function showReserveOnePersonUnavailable(row = null) {
if (!options.isEnabled()) {
return;
}
if (row) {
options.rememberSelectedRoom(row);
}
options.clearRetryTimer(reserveState);
options.clearStatusWatchTimer();
options.clearCountdownTimer();
options.clearCapturedJoin();
options.clearPasswordPromptPending();
reserveState = {
active: false,
unavailable: true,
reason: "one-person-room",
message: options.onePersonText
};
options.updateWaitingWindow();
options.setWaitingVisible(true);
options.syncJoinButtonLabel();
}
function showReserveTerminalMessage(reason, message) {
if (!options.isEnabled()) {
return;
}
options.clearRetryTimer(reserveState);
options.clearStatusWatchTimer();
options.clearCountdownTimer();
options.clearCapturedJoin();
options.clearPasswordPromptPending();
reserveState = {
active: false,
unavailable: false,
terminal: true,
reason,
message: message || options.statusFallbackText
};
options.updateWaitingWindow();
options.setWaitingVisible(true);
options.hideNativeConnectingWindows();
options.syncJoinButtonLabel();
}
function stopReserveAfterSuccessfulJoin() {
options.suppressRoomFullAfterJoin();
stopReserveSpot({ hideNative: true });
options.scheduleStatusWatch();
}
return {
getReserveState,
showReserveOnePersonUnavailable,
showReserveTerminalMessage,
startReserveSpot,
stopReserveAfterSuccessfulJoin,
stopReserveSpot
};
}
// src/features/reserve-room-list.ts
function getCell(row, index) {
const cells = readObjectProperty(row, "cells");
return readObjectProperty(cells, index);
}
function getCellText(row, index) {
const text = readObjectProperty(getCell(row, index), "textContent");
return typeof text === "string" ? text.trim() : "";
}
function hasAtLeastTwoCells(row) {
const cells = readObjectProperty(row, "cells");
const length = Number(readObjectProperty(cells, "length"));
return Number.isFinite(length) && length >= 2;
}
function getReserveRowFromTarget(target) {
return target instanceof Element ? target.closest(".roomListContainer .scrollBox tr") : null;
}
function getReserveRoomSignature(row) {
if (!row || !getCell(row, 0)) {
return "";
}
const roomName = getCellText(row, 0);
const lockState = isReservePasswordRoom(row) ? "locked" : "open";
return `${roomName}
${lockState}`;
}
function findReserveRoomBySignature(signature) {
if (!signature) {
return null;
}
return [...document.querySelectorAll(".roomListContainer .scrollBox tr")].find((row) => {
return row.isConnected && getReserveRoomSignature(row) === signature;
}) || null;
}
function parseReserveRoomPlayers(row) {
if (!hasAtLeastTwoCells(row)) {
return null;
}
const match = getCellText(row, 1).match(/^(\d+)\s*\/\s*(\d+)$/);
if (!match) {
return null;
}
return {
current: Number(match[1]),
max: Number(match[2])
};
}
function isReserveRoomFull(row) {
const players = parseReserveRoomPlayers(row);
return Boolean(players && players.max > 0 && players.current >= players.max);
}
function isReserveOnePersonRoom(row) {
const players = parseReserveRoomPlayers(row);
return Boolean(players && players.max === 1);
}
function isReserveUnavailableRoom(row) {
return Boolean(isReserveRoomFull(row) && isReserveOnePersonRoom(row));
}
function isReservePasswordRoom(row) {
return Boolean(row instanceof Element && row.querySelector('img[src*="lock"]'));
}
function createReserveRoomList(options) {
function getReserveJoinButton() {
const button = document.querySelector(".roomListContainer .bottomButton.right");
return options.isElementVisible(button) ? button : null;
}
return {
getReserveJoinButton
};
}
// src/features/reserve-room-full-suppression.ts
function createReserveRoomFullSuppression(options) {
let suppressUntil = 0;
function isReserveJoinedRoomFullSuppressed() {
return Date.now() < suppressUntil;
}
function suppressReserveRoomFullAfterJoin() {
suppressUntil = Date.now() + options.suppressMs;
}
return {
isReserveJoinedRoomFullSuppressed,
suppressReserveRoomFullAfterJoin
};
}
// src/features/reserve-retry-audio-suppression.ts
function createReserveRetryAudioSuppression(options) {
let suppressUntil = 0;
function isReserveRetryAudioSuppressed() {
return Date.now() < suppressUntil;
}
function suppressReserveRetryAudio() {
suppressUntil = Date.now() + options.suppressMs;
}
return {
isReserveRetryAudioSuppressed,
suppressReserveRetryAudio
};
}
// src/features/reserve-retry-scheduler.ts
function createReserveRetryScheduler(options) {
function clearReserveRetryTimer(state = options.getState()) {
if (state?.retryTimer) {
window.clearTimeout(state.retryTimer);
state.retryTimer = 0;
}
}
function scheduleReserveRetry() {
const state = options.getState();
if (!options.isEnabled() || !state?.active || state.retryTimer) {
return;
}
const retryDelayMs = options.getRetryDelayMs();
state.nextRetryAt = Date.now() + retryDelayMs;
options.updateWaitingWindow();
options.scheduleCountdownUpdate();
state.retryTimer = window.setTimeout(() => {
const currentState = options.getState();
if (!currentState?.active) {
return;
}
currentState.retryTimer = 0;
currentState.nextRetryAt = 0;
options.updateWaitingWindow();
if (options.hasSuccessfulJoinLayer()) {
options.onSuccessfulJoin();
return;
}
if (options.emitJoinAttempt()) {
currentState.retries = (currentState.retries || 0) + 1;
}
scheduleReserveRetry();
}, retryDelayMs);
}
return {
clearReserveRetryTimer,
scheduleReserveRetry
};
}
// src/features/reserve-native-status.ts
function getWindowLines(windowElement) {
const textElement = windowElement.querySelector(".textBox") || windowElement;
return (textElement.textContent || "").split(/\r?\n/);
}
function normalizeLine(line) {
return line.replace(/\s+/g, " ").trim();
}
function setDisplayNone(element) {
if (isStyledElement(element)) {
element.style.display = "none";
}
}
function createReserveNativeStatus(options) {
function getNativeConnectingWindows() {
return [...document.querySelectorAll(".connectingWindowContainer:not(.qolboxReserveWindowContainer)")];
}
function getNativeConnectingText() {
return getNativeConnectingWindows().map((windowElement) => windowElement.textContent || "").join("\n");
}
function hideNativeConnectingWindows() {
for (const windowElement of getNativeConnectingWindows()) {
setDisplayNone(windowElement);
}
}
function getReserveStatusLines() {
return getNativeConnectingWindows().flatMap(getWindowLines).map(normalizeLine).filter((line) => {
return line && !options.roomFullPattern.test(line) && !options.roomClosedPattern.test(line) && !options.wrongPasswordPattern.test(line) && !/^cancel$/i.test(line) && line !== options.reserveWaitText;
});
}
function getReserveNativeMessage(pattern) {
return getNativeConnectingWindows().flatMap(getWindowLines).map(normalizeLine).find((line) => line && pattern.test(line)) || "";
}
return {
getNativeConnectingText,
getNativeConnectingWindows,
getReserveNativeMessage,
getReserveStatusLines,
hideNativeConnectingWindows
};
}
// src/features/reserve-selection-state.ts
var SELECTED_RESERVE_ROW_SELECTOR2 = ".roomListContainer .scrollBox tr.SELECTED";
function createReserveSelectionState() {
let selectedRow = null;
let selectedSignature = "";
let selectedWasFull = false;
let selectedWasUnavailable = false;
function rememberReserveSelectedRoom(row) {
if (!(row instanceof Element) || !row.isConnected) {
return null;
}
selectedRow = row;
selectedSignature = getReserveRoomSignature(row);
selectedWasFull = isReserveRoomFull(row);
selectedWasUnavailable = isReserveUnavailableRoom(row);
return row;
}
function getReserveSelectedRoomRow() {
const selected = document.querySelector(SELECTED_RESERVE_ROW_SELECTOR2);
if (selected?.isConnected) {
return rememberReserveSelectedRoom(selected);
}
if (selectedRow?.isConnected) {
return rememberReserveSelectedRoom(selectedRow);
}
const matchingRow = findReserveRoomBySignature(selectedSignature);
if (matchingRow) {
return rememberReserveSelectedRoom(matchingRow);
}
return null;
}
function getReserveSelectedRoomState() {
const row = getReserveSelectedRoomRow();
if (row) {
return {
row,
full: isReserveRoomFull(row),
unavailable: isReserveUnavailableRoom(row)
};
}
return {
row: null,
full: selectedWasFull,
unavailable: selectedWasUnavailable
};
}
return {
getReserveSelectedRoomRow,
getReserveSelectedRoomState,
rememberReserveSelectedRoom
};
}
// src/features/reserve-status-watch-timer.ts
function createReserveStatusWatchTimer(options) {
let statusWatchTimer = 0;
function clearReserveStatusWatchTimer() {
if (statusWatchTimer) {
window.clearTimeout(statusWatchTimer);
statusWatchTimer = 0;
}
}
function scheduleReserveStatusWatch(delay = options.defaultDelayMs) {
if (statusWatchTimer) {
return;
}
statusWatchTimer = window.setTimeout(() => {
statusWatchTimer = 0;
options.onTick();
if (options.shouldContinue()) {
scheduleReserveStatusWatch(delay);
}
}, delay);
}
return {
clearReserveStatusWatchTimer,
scheduleReserveStatusWatch
};
}
// src/features/reserve-waiting-window.ts
var RESERVE_WINDOW_ID = "qolboxReserveWindow";
function getReserveWindowHost() {
return document.getElementById("appContainer") || document.body || document.documentElement;
}
function createReserveWaitingWindow(options) {
function ensureReserveWaitingWindow() {
const existing = document.getElementById(RESERVE_WINDOW_ID);
if (existing) {
return existing;
}
const container = document.createElement("div");
container.id = RESERVE_WINDOW_ID;
container.className = "connectingWindowContainer qolboxReserveWindowContainer";
container.innerHTML = `
<div class="behindBlocker"></div>
<div class="connectingWindow">
<div class="topBar"></div>
<div class="qolboxReserveContent">
<div class="spinner" aria-hidden="true"></div>
<div class="qolboxReserveStatus"></div>
<div class="qolboxReserveCountdown"></div>
<div class="qolboxReserveMessage"></div>
</div>
<div class="cancelButton">CANCEL</div>
</div>
`;
container.querySelector(".cancelButton")?.addEventListener("click", () => {
options.onCancel();
});
getReserveWindowHost().appendChild(container);
return container;
}
function getReserveStatusText() {
const statusText = options.getReserveStatusLines().slice(-2).join(" - ");
const state = options.getState();
if (statusText) {
if (state) {
state.lastStatusText = statusText;
}
return statusText;
}
return state?.lastStatusText || options.statusFallbackText;
}
function getReserveCountdownText() {
const nextRetryAt = options.getState()?.nextRetryAt;
const remainingMs = nextRetryAt ? Math.max(0, nextRetryAt - Date.now()) : options.getRetryDelayMs();
return `Retrying in ${(remainingMs / 1e3).toFixed(1)} seconds...`;
}
function updateReserveWaitingWindow() {
const container = ensureReserveWaitingWindow();
const state = options.getState();
const title = container.querySelector(".topBar");
const spinner = container.querySelector(".spinner");
const status = container.querySelector(".qolboxReserveStatus");
const countdown = container.querySelector(".qolboxReserveCountdown");
const message = container.querySelector(".qolboxReserveMessage");
const isTerminalMessage = Boolean(state && (state.unavailable || state.terminal));
const isUnavailable = Boolean(state?.unavailable);
if (title) {
title.textContent = isUnavailable ? options.unavailableTitleText : options.waitTitleText;
}
if (spinner) {
spinner.hidden = isTerminalMessage;
}
if (status) {
status.hidden = isTerminalMessage;
status.textContent = isTerminalMessage ? "" : getReserveStatusText();
}
if (countdown) {
countdown.hidden = isTerminalMessage;
countdown.textContent = isTerminalMessage ? "" : getReserveCountdownText();
}
if (message) {
message.hidden = !isTerminalMessage;
message.textContent = isTerminalMessage ? state?.message || options.onePersonText : "";
}
}
function setReserveWaitingVisible(visible) {
document.body?.classList.toggle("qolbox-reserve-active", visible);
ensureReserveWaitingWindow().style.display = visible ? "block" : "none";
}
return {
ensureReserveWaitingWindow,
getReserveCountdownText,
getReserveStatusText,
setReserveWaitingVisible,
updateReserveWaitingWindow
};
}
// src/features/reserve-feature-bundle.ts
function createReserveFeatureBundle(options) {
const {
getReserveSelectedRoomRow,
getReserveSelectedRoomState,
rememberReserveSelectedRoom
} = createReserveSelectionState();
const {
getReserveState,
showReserveOnePersonUnavailable,
showReserveTerminalMessage,
startReserveSpot,
stopReserveAfterSuccessfulJoin,
stopReserveSpot
} = createReserveLifecycleController({
clearCapturedJoin: () => clearReserveCapturedJoin(),
clearCountdownTimer: () => clearReserveCountdownTimer(),
clearPasswordPromptPending: () => clearReservePasswordPromptPending(),
clearRetryTimer: (state) => clearReserveRetryTimer(state),
clearStatusWatchTimer: () => clearReserveStatusWatchTimer(),
clearVisibleRoomSelection: () => clearReserveVisibleRoomSelection(),
getCapturedJoin: () => getReserveCapturedJoin(),
hideNativeConnectingWindows: () => hideNativeConnectingWindows(),
isEnabled: options.isReserveEnabled,
onePersonText: RESERVE_ONE_PERSON_TEXT,
rememberSelectedRoom: (row) => rememberReserveSelectedRoom(row),
getRetryDelayMs: getAdvancedReserveRetryIntervalMs,
scheduleCountdownUpdate: () => scheduleReserveCountdownUpdate(),
scheduleStatusWatch: () => scheduleReserveStatusWatch(),
setWaitingVisible: (visible) => setReserveWaitingVisible(visible),
statusFallbackText: RESERVE_STATUS_FALLBACK_TEXT,
suppressRoomFullAfterJoin: () => suppressReserveRoomFullAfterJoin(),
syncJoinButtonLabel: () => syncReserveJoinButtonLabel(),
updateWaitingWindow: () => updateReserveWaitingWindow()
});
const { getReserveJoinButton } = createReserveRoomList({
isElementVisible
});
const {
clearReservePasswordPromptPending,
clearReserveVisibleRoomSelection,
isReservePasswordPromptPending,
setReservePasswordPromptPending,
syncReserveJoinButtonLabel,
syncReservePasswordPrompt
} = createReserveActionControls({
getReserveJoinButton,
getReserveSelectedRoomState,
isElementVisible,
isEnabled: options.isReserveEnabled,
joinButtonText: JOIN_BUTTON_TEXT,
reserveButtonText: RESERVE_BUTTON_TEXT
});
const {
getNativeConnectingWindows,
getNativeConnectingText,
getReserveNativeMessage,
getReserveStatusLines,
hideNativeConnectingWindows
} = createReserveNativeStatus({
reserveWaitText: RESERVE_WAIT_TEXT,
roomClosedPattern: RESERVE_ROOM_CLOSED_PATTERN,
roomFullPattern: RESERVE_ROOM_FULL_PATTERN,
wrongPasswordPattern: RESERVE_WRONG_PASSWORD_PATTERN
});
const {
isReserveJoinedRoomFullSuppressed,
suppressReserveRoomFullAfterJoin
} = createReserveRoomFullSuppression({
suppressMs: RESERVE_JOINED_ROOM_FULL_SUPPRESS_MS
});
const {
clearReserveStatusWatchTimer,
scheduleReserveStatusWatch
} = createReserveStatusWatchTimer({
defaultDelayMs: 250,
onTick: () => handleReserveConnectingState(),
shouldContinue: () => shouldContinueReserveStatusWatch()
});
const {
isReserveRetryAudioSuppressed,
suppressReserveRetryAudio
} = createReserveRetryAudioSuppression({
suppressMs: RESERVE_RETRY_AUDIO_SUPPRESS_MS
});
const {
canAutoReserveCapturedJoin,
captureReserveJoin,
clearReserveCapturedJoin,
emitReserveJoinAttempt,
getReserveCapturedJoin,
shouldWatchRecentReserveCapture
} = createReserveCapturedJoinController({
capturedJoinFreshMs: 3e4,
getState: () => getReserveState(),
hasSuccessfulJoinLayer: options.hasSuccessfulJoinLayer,
isEnabled: options.isReserveEnabled,
isAutoJoinMatch: isNativeAutoJoinMatch,
onCaptured: () => scheduleReserveStatusWatch(),
suppressRetryAudio: suppressReserveRetryAudio
});
const { installReserveSocketCaptureHook } = createReserveSocketCaptureHook({
onJoin: captureReserveJoin,
shouldCaptureJoin: (args) => Boolean(getReserveJoinPayload(args))
});
const {
setReserveWaitingVisible,
updateReserveWaitingWindow
} = createReserveWaitingWindow({
getReserveStatusLines,
getState: () => getReserveState(),
getRetryDelayMs: getAdvancedReserveRetryIntervalMs,
onCancel: () => cancelReserveSpot(),
onePersonText: RESERVE_ONE_PERSON_TEXT,
statusFallbackText: RESERVE_STATUS_FALLBACK_TEXT,
unavailableTitleText: RESERVE_UNAVAILABLE_TITLE_TEXT,
waitTitleText: RESERVE_WAIT_TITLE_TEXT
});
const {
clearReserveCountdownTimer,
scheduleReserveCountdownUpdate
} = createReserveCountdownTimer({
getState: () => getReserveState(),
intervalMs: RESERVE_COUNTDOWN_UPDATE_MS,
onTick: () => updateReserveWaitingWindow()
});
const { clearReserveRetryTimer, scheduleReserveRetry } = createReserveRetryScheduler({
emitJoinAttempt: emitReserveJoinAttempt,
getState: () => getReserveState(),
hasSuccessfulJoinLayer: options.hasSuccessfulJoinLayer,
isEnabled: options.isReserveEnabled,
onSuccessfulJoin: () => stopReserveAfterSuccessfulJoin(),
getRetryDelayMs: getAdvancedReserveRetryIntervalMs,
scheduleCountdownUpdate: () => scheduleReserveCountdownUpdate(),
updateWaitingWindow: () => updateReserveWaitingWindow()
});
const { handleReserveConnectingState } = createReserveConnectingStateController({
canAutoReserveCapturedJoin,
getNativeConnectingText,
getReserveNativeMessage,
getState: () => getReserveState(),
hasSuccessfulJoinLayer: options.hasSuccessfulJoinLayer,
hideNativeConnectingWindows,
isAutoJoinOnePersonRoom: isNativeAutoJoinOnePersonRoom,
isEnabled: options.isReserveEnabled,
isRoomFullSuppressed: isReserveJoinedRoomFullSuppressed,
roomClosedPattern: RESERVE_ROOM_CLOSED_PATTERN,
roomFullPattern: RESERVE_ROOM_FULL_PATTERN,
scheduleReserveRetry,
showOnePersonUnavailable: () => showReserveOnePersonUnavailable(),
showTerminalMessage: showReserveTerminalMessage,
startReserveSpot,
stopAfterSuccessfulJoin: stopReserveAfterSuccessfulJoin,
stopReserveSpot,
wrongPasswordPattern: RESERVE_WRONG_PASSWORD_PATTERN
});
const {
cancelReserveSpot,
handleReservePasswordKey,
handleReservePasswordSubmit,
handleReserveRoomListClick,
handleReserveRoomListDoubleClick
} = createReserveInteractionHandlers({
clearPasswordPromptPending: clearReservePasswordPromptPending,
getNativeConnectingWindows,
getRowFromTarget: getReserveRowFromTarget,
getSelectedRoomRow: getReserveSelectedRoomRow,
getSelectedRoomState: getReserveSelectedRoomState,
getState: () => getReserveState(),
isElementVisible,
isEnabled: options.isReserveEnabled,
isPasswordPromptPending: isReservePasswordPromptPending,
isPasswordRoom: isReservePasswordRoom,
isRoomFull: isReserveRoomFull,
isUnavailableRoom: isReserveUnavailableRoom,
rememberSelectedRoom: rememberReserveSelectedRoom,
setPasswordPromptPending: setReservePasswordPromptPending,
showOnePersonUnavailable: showReserveOnePersonUnavailable,
startReserveSpot,
stopReserveSpot,
syncJoinButtonLabel: syncReserveJoinButtonLabel,
syncPasswordPrompt: syncReservePasswordPrompt
});
const { installReserveDomEventHooks } = createReserveDomEventHooks({
onPasswordKey: handleReservePasswordKey,
onPasswordSubmit: handleReservePasswordSubmit,
onRoomListClick: handleReserveRoomListClick,
onRoomListDoubleClick: handleReserveRoomListDoubleClick
});
const {
patchReserveSpotFeature,
shouldContinueReserveStatusWatch
} = createReserveFeaturePatchController({
getState: () => getReserveState(),
handleConnectingState: handleReserveConnectingState,
installDomEventHooks: installReserveDomEventHooks,
installSocketCaptureHook: installReserveSocketCaptureHook,
isEnabled: options.isReserveEnabled,
isRoomFullSuppressed: isReserveJoinedRoomFullSuppressed,
shouldWatchRecentCapture: shouldWatchRecentReserveCapture,
syncJoinButtonLabel: syncReserveJoinButtonLabel,
syncPasswordPrompt: syncReservePasswordPrompt
});
return {
clearReservePasswordPromptPending,
getReserveState,
installReserveSocketCaptureHook,
isReserveRetryAudioSuppressed,
patchReserveSpotFeature,
stopReserveSpot,
syncReserveJoinButtonLabel
};
}
// src/hitbox/scoreboard-adapter.ts
function getScorePlayers(session) {
const players = readNativePath(session, ["KR", "uL", "Ho"]);
return Array.isArray(players) ? players.filter(Boolean) : [];
}
// src/features/score-row-color-values.ts
function parseCssRgbColor(value) {
if (typeof value !== "string") {
return null;
}
const match = value.match(
/^rgba?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)(?:\s*,\s*(\d+(?:\.\d+)?))?\s*\)$/i
);
if (!match) {
return null;
}
return {
red: Math.max(0, Math.min(255, Math.round(Number(match[1])))),
green: Math.max(0, Math.min(255, Math.round(Number(match[2])))),
blue: Math.max(0, Math.min(255, Math.round(Number(match[3])))),
alpha: match[4] === void 0 ? 1 : Math.max(0, Math.min(1, Number(match[4])))
};
}
function parseNumericRgbColor(value) {
const color = Number(value);
if (!Number.isFinite(color) || color < 0 || color > 16777215) {
return null;
}
return {
red: color >> 16 & 255,
green: color >> 8 & 255,
blue: color & 255,
alpha: 1
};
}
function parseHexRgbColor(value) {
if (typeof value !== "string") {
return null;
}
const normalized = value.trim().replace(/^#|^0x/i, "");
if (!/^[0-9a-f]{6}$/i.test(normalized)) {
return null;
}
return parseNumericRgbColor(Number.parseInt(normalized, 16));
}
function parsePlayerRgbColor(value) {
return typeof value === "number" ? parseNumericRgbColor(value) : parseCssRgbColor(value) || parseHexRgbColor(value);
}
function colorsMatch(left, right) {
return Boolean(
left && right && left.red === right.red && left.green === right.green && left.blue === right.blue
);
}
function getElementBackgroundColor(element) {
return isStyledElement(element) && typeof element.style.backgroundColor === "string" ? element.style.backgroundColor : "";
}
function blendRgbColors(foreground, background) {
const alpha = foreground.alpha;
return {
red: Math.round(foreground.red * alpha + background.red * (1 - alpha)),
green: Math.round(foreground.green * alpha + background.green * (1 - alpha)),
blue: Math.round(foreground.blue * alpha + background.blue * (1 - alpha)),
alpha: 1
};
}
function getRelativeLuminance(color) {
const channels = [color.red, color.green, color.blue].map((value) => {
const normalized = value / 255;
return normalized <= 0.03928 ? normalized / 12.92 : ((normalized + 0.055) / 1.055) ** 2.4;
});
return channels[0] * 0.2126 + channels[1] * 0.7152 + channels[2] * 0.0722;
}
function getContrastRatio(left, right) {
const leftLuminance = getRelativeLuminance(left);
const rightLuminance = getRelativeLuminance(right);
const lighter = Math.max(leftLuminance, rightLuminance);
const darker = Math.min(leftLuminance, rightLuminance);
return (lighter + 0.05) / (darker + 0.05);
}
function getEffectiveBackgroundColor(element) {
let current = element;
let color = { red: 10, green: 10, blue: 10, alpha: 1 };
const layers = [];
while (current) {
const background = parseCssRgbColor(window.getComputedStyle(current).backgroundColor);
if (background && background.alpha > 0) {
layers.unshift(background);
if (background.alpha >= 1) {
break;
}
}
current = current.parentElement;
}
for (const layer of layers) {
color = layer.alpha >= 1 ? { ...layer, alpha: 1 } : blendRgbColors(layer, color);
}
return color;
}
function getReadableTextColor(background) {
const dark = { red: 0, green: 0, blue: 0, alpha: 1 };
const light = { red: 255, green: 255, blue: 255, alpha: 1 };
return getContrastRatio(dark, background) >= getContrastRatio(light, background) ? dark : light;
}
function toCssRgb(color) {
return `rgb(${color.red}, ${color.green}, ${color.blue})`;
}
// src/features/score-row-colors.ts
var MIN_SCORE_TEXT_CONTRAST = 4.5;
function normalizeScoreName(value) {
return String(value || "").replace(/\s+/g, " ").trim().toLowerCase();
}
function getScoreRowName(row) {
const nameElement = row && row.querySelector ? row.querySelector(".name") : null;
return normalizeScoreName(nameElement ? nameElement.textContent : row && row.textContent);
}
function createScoreRowColorController(options) {
function isFallbackScoreRowColor(color) {
return colorsMatch(color, options.fallbackRgb);
}
function getPlayerScoreColor(player) {
for (const value of getPlayerColorCandidates(player)) {
const parsed = parsePlayerRgbColor(value);
if (parsed) {
return parsed;
}
}
return options.teamScoreColors.get(options.getPlayerTeamState(player)) || null;
}
function syncScoreRowTextContrast(row) {
const background = getEffectiveBackgroundColor(row);
const readableColor = toCssRgb(getReadableTextColor(background));
let changed = false;
const textElements = [row, ...Array.from(row.querySelectorAll(".number, .name"))];
for (const element of textElements) {
if (!(element.textContent || "").trim()) {
continue;
}
const currentColor = parseCssRgbColor(window.getComputedStyle(element).color);
if (currentColor && getContrastRatio(currentColor, background) >= MIN_SCORE_TEXT_CONTRAST) {
continue;
}
options.setImportantStyle(element, "color", readableColor);
changed = true;
}
return changed;
}
function syncScoreRowsFromPlayers(scorePanel) {
const rows = Array.from(scorePanel.querySelectorAll(".entryContainer"));
const players = options.getScorePlayers();
if (!rows.length || !players.length) {
return false;
}
const playersByName = /* @__PURE__ */ new Map();
for (const player of players) {
const name = normalizeScoreName(getPlayerDisplayName(player));
if (name) {
playersByName.set(name, player);
}
}
let changed = false;
rows.forEach((row, index) => {
const inlineColor = parseCssRgbColor(getElementBackgroundColor(row));
const computedColor = parseCssRgbColor(window.getComputedStyle(row).backgroundColor);
const player = playersByName.get(getScoreRowName(row)) || players[index];
const playerColor = getPlayerScoreColor(player);
if (!playerColor || inlineColor && !isFallbackScoreRowColor(inlineColor)) {
changed = syncScoreRowTextContrast(row) || changed;
return;
}
if (!inlineColor && computedColor && !isFallbackScoreRowColor(computedColor)) {
changed = syncScoreRowTextContrast(row) || changed;
return;
}
options.setImportantStyle(row, "background-color", `rgb(${playerColor.red}, ${playerColor.green}, ${playerColor.blue})`);
syncScoreRowTextContrast(row);
changed = true;
});
return changed;
}
function makeScoreRowsOpaque(scorePanel) {
for (const row of scorePanel.querySelectorAll(".entryContainer")) {
const inlineColor = parseCssRgbColor(getElementBackgroundColor(row));
const computedColor = parseCssRgbColor(window.getComputedStyle(row).backgroundColor);
const parsedColor = inlineColor || computedColor;
if (!parsedColor || parsedColor.alpha >= 1) {
syncScoreRowTextContrast(row);
continue;
}
if (!inlineColor && isFallbackScoreRowColor(parsedColor)) {
syncScoreRowTextContrast(row);
continue;
}
options.setImportantStyle(
row,
"background-color",
`rgb(${parsedColor.red}, ${parsedColor.green}, ${parsedColor.blue})`
);
syncScoreRowTextContrast(row);
}
}
return {
makeScoreRowsOpaque,
syncScoreRowsFromPlayers
};
}
// src/hitbox/typing-pulse-adapter.ts
function isNativeCallable9(value) {
return typeof value === "function";
}
function getNativeLobbyUi(session) {
const lobbyUi = readNativeProperty(session, "TJ");
return isNativeObject(lobbyUi) ? lobbyUi : null;
}
function isNativeTypingPulseHookInstalled(session) {
return Boolean(readNativeProperty(getNativeLobbyUi(session), "__qolboxTypingIndicatorPatched"));
}
function installNativeTypingPulseHook(session, onTypingPulse) {
const lobbyUi = getNativeLobbyUi(session);
if (!lobbyUi || isNativeTypingPulseHookInstalled(session)) {
return Boolean(lobbyUi);
}
const nativeTypingPulse = readNativeProperty(lobbyUi, "$W");
if (!isNativeCallable9(nativeTypingPulse)) {
return false;
}
const wrappedTypingPulse = function wrappedTypingPulse2(playerId, ...rest) {
onTypingPulse(playerId);
return Reflect.apply(nativeTypingPulse, this, [playerId, ...rest]);
};
setNativeReflectProperty(lobbyUi, "$W", wrappedTypingPulse);
setNativeReflectProperty(lobbyUi, "__qolboxTypingIndicatorOriginal", nativeTypingPulse);
setNativeReflectProperty(lobbyUi, "__qolboxTypingIndicatorPatched", true);
return true;
}
// src/features/typing-expiration-tracker.ts
function createTypingExpirationTracker(options) {
const timers = /* @__PURE__ */ new Map();
const expirations = /* @__PURE__ */ new Map();
function clear() {
for (const timer of timers.values()) {
window.clearTimeout(timer);
}
timers.clear();
expirations.clear();
}
function isTyping(playerId) {
const id = String(playerId);
const expiresAt = expirations.get(id);
if (!expiresAt) {
return false;
}
if (expiresAt <= Date.now()) {
expirations.delete(id);
return false;
}
return true;
}
function note(playerId) {
if (playerId === null || playerId === void 0) {
return false;
}
const id = String(playerId);
const timeoutMs = options.getTimeoutMs();
const expiresAt = Date.now() + timeoutMs;
const existingTimer = timers.get(id);
if (existingTimer) {
window.clearTimeout(existingTimer);
}
expirations.set(id, expiresAt);
timers.set(
id,
window.setTimeout(() => {
if ((expirations.get(id) || 0) <= Date.now()) {
expirations.delete(id);
timers.delete(id);
options.onExpire();
}
}, timeoutMs + 50)
);
return true;
}
return {
clear,
isTyping,
note
};
}
// src/features/typing-score-indicators.ts
function clearScoreTypingIndicators() {
for (const indicator of document.querySelectorAll(".qolboxTypingIndicator")) {
indicator.remove();
}
}
function syncScoreTypingIndicators(scorePanel, typingPlayers) {
const panels = scorePanel ? [scorePanel] : Array.from(document.querySelectorAll(".scores"));
let changed = false;
for (const panel of panels) {
for (const row of panel.querySelectorAll(".entryContainer")) {
const nameElement = row.querySelector(".name") || row;
const rowName = getScoreRowName(row);
const rowText = normalizeScoreName(row.textContent);
const isTyping = typingPlayers.some(
(entry) => entry.name && (entry.name === rowName || rowText.includes(entry.name))
);
const indicator = nameElement.querySelector(".qolboxTypingIndicator");
if (isTyping && !indicator) {
const newIndicator = document.createElement("span");
newIndicator.className = "qolboxTypingIndicator";
newIndicator.setAttribute("aria-label", "typing");
nameElement.appendChild(newIndicator);
changed = true;
} else if (!isTyping && indicator) {
indicator.remove();
changed = true;
}
}
}
return changed;
}
// src/features/typing-world-indicators.ts
function ensureWorldTypingLayer() {
if (!document.body) {
return null;
}
const existingLayer = document.querySelector(".qolboxWorldTypingLayer");
if (existingLayer) {
return existingLayer;
}
const layer = document.createElement("div");
layer.className = "qolboxWorldTypingLayer";
layer.setAttribute("aria-hidden", "true");
document.body.appendChild(layer);
return layer;
}
function createWorldTypingIndicatorController(options) {
let typingIndicatorPositionRaf = 0;
function stopTypingIndicatorPositionLoop() {
if (!typingIndicatorPositionRaf) {
return;
}
if (typeof window.cancelAnimationFrame === "function") {
window.cancelAnimationFrame(typingIndicatorPositionRaf);
} else {
window.clearTimeout(typingIndicatorPositionRaf);
}
typingIndicatorPositionRaf = 0;
}
function syncWorldTypingIndicators(typingPlayers, session = options.getSession()) {
const shouldShowWorldIndicators = options.isSessionMatchActive(session) && typingPlayers.length > 0;
const existingLayer = document.querySelector(".qolboxWorldTypingLayer");
if (!shouldShowWorldIndicators) {
if (existingLayer) {
existingLayer.remove();
return true;
}
return false;
}
const layer = ensureWorldTypingLayer();
if (!layer) {
return false;
}
const activeIds = /* @__PURE__ */ new Set();
let changed = false;
for (const player of typingPlayers) {
const position = options.getWorldTypingPosition(player.id, session);
if (!position) {
continue;
}
const id = String(player.id);
activeIds.add(id);
let indicator = Array.from(layer.querySelectorAll(".qolboxWorldTypingIndicator")).find(
(element) => element.dataset.playerId === id
);
if (!indicator) {
indicator = document.createElement("span");
indicator.className = "qolboxWorldTypingIndicator";
indicator.dataset.playerId = id;
indicator.setAttribute("aria-label", "typing");
layer.appendChild(indicator);
changed = true;
}
const left = `${Math.round(position.left)}px`;
const top = `${Math.round(position.top)}px`;
if (indicator.style.left !== left) {
indicator.style.left = left;
}
if (indicator.style.top !== top) {
indicator.style.top = top;
}
}
for (const indicator of Array.from(layer.querySelectorAll(".qolboxWorldTypingIndicator"))) {
if (!activeIds.has(indicator.dataset.playerId || "")) {
indicator.remove();
changed = true;
}
}
if (!activeIds.size && layer.children.length === 0) {
layer.remove();
changed = true;
}
return changed;
}
function scheduleTypingIndicatorPositionLoop(session = options.getSession()) {
if (typingIndicatorPositionRaf || !options.isSessionMatchActive(session)) {
return;
}
const updateTypingIndicatorPositions = () => {
typingIndicatorPositionRaf = 0;
if (!options.isChatFeatureEnabled() || !options.isSessionMatchActive()) {
syncWorldTypingIndicators([], options.getSession());
return;
}
const typingPlayers = options.getTypingPlayers();
syncWorldTypingIndicators(typingPlayers);
if (typingPlayers.length > 0) {
scheduleTypingIndicatorPositionLoop();
}
};
typingIndicatorPositionRaf = typeof window.requestAnimationFrame === "function" ? window.requestAnimationFrame(updateTypingIndicatorPositions) : window.setTimeout(updateTypingIndicatorPositions, options.fallbackUpdateDelayMs);
}
function clearWorldTypingIndicators() {
stopTypingIndicatorPositionLoop();
const worldLayer = document.querySelector(".qolboxWorldTypingLayer");
if (worldLayer) {
worldLayer.remove();
}
}
return {
clearWorldTypingIndicators,
scheduleTypingIndicatorPositionLoop,
stopTypingIndicatorPositionLoop,
syncWorldTypingIndicators
};
}
// src/features/typing-indicators.ts
function createTypingIndicatorController(options) {
let typingIndicatorSession = null;
const typingExpirations = createTypingExpirationTracker({
getTimeoutMs: options.getTimeoutMs,
onExpire: () => syncTypingIndicators()
});
const worldTypingIndicators = createWorldTypingIndicatorController({
fallbackUpdateDelayMs: 100,
getSession: options.getSession,
getTypingPlayers,
getWorldTypingPosition: options.getWorldTypingPosition,
isChatFeatureEnabled: options.isChatFeatureEnabled,
isSessionMatchActive: options.isSessionMatchActive
});
function clearTypingIndicators() {
worldTypingIndicators.clearWorldTypingIndicators();
typingExpirations.clear();
clearScoreTypingIndicators();
}
function isPlayerTypingNow(playerId) {
return typingExpirations.isTyping(playerId);
}
function getTypingPlayers(session = options.getSession()) {
const localPlayerId = options.getLocalPlayerId(session);
return options.getSessionPlayers(session).filter(({ id }) => !options.isSamePlayerId(id, localPlayerId) && isPlayerTypingNow(id)).map(({ id, player }) => ({
id,
name: normalizeScoreName(getPlayerDisplayName(player))
}));
}
function syncWorldTypingIndicators(typingPlayers, session = options.getSession()) {
return worldTypingIndicators.syncWorldTypingIndicators(typingPlayers, session);
}
function scheduleTypingIndicatorPositionLoop(session = options.getSession()) {
worldTypingIndicators.scheduleTypingIndicatorPositionLoop(session);
}
function syncTypingIndicators(scorePanel = null) {
const session = options.getSession();
if (!session) {
worldTypingIndicators.stopTypingIndicatorPositionLoop();
return false;
}
const typingPlayers = getTypingPlayers(session);
let changed = syncScoreTypingIndicators(scorePanel, typingPlayers);
changed = syncWorldTypingIndicators(typingPlayers, session) || changed;
if (typingPlayers.length > 0 && options.isSessionMatchActive(session)) {
scheduleTypingIndicatorPositionLoop(session);
} else {
worldTypingIndicators.stopTypingIndicatorPositionLoop();
}
return changed;
}
function notePlayerTyping(playerId) {
if (playerId === null || playerId === void 0) {
return false;
}
if (options.isSamePlayerId(playerId, options.getLocalPlayerId())) {
return false;
}
typingExpirations.note(playerId);
syncTypingIndicators();
return true;
}
function patchTypingIndicatorHooks() {
const session = options.getSession();
if (isNativeTypingPulseHookInstalled(session)) {
return true;
}
if (typingIndicatorSession && typingIndicatorSession !== session) {
clearTypingIndicators();
}
const installed = installNativeTypingPulseHook(session, notePlayerTyping);
if (installed) {
typingIndicatorSession = session;
}
return installed;
}
return {
clearTypingIndicators,
notePlayerTyping,
patchTypingIndicatorHooks,
syncTypingIndicators,
syncWorldTypingIndicators
};
}
// src/hitbox/world-state-adapter.ts
function hasForEach(value) {
return isNativeObject(value) && typeof readNativeProperty(value, "forEach") === "function";
}
function getCollectionEntries(collection) {
if (!collection) {
return [];
}
if (Array.isArray(collection)) {
return collection.map((value, key) => ({ key, value })).filter((entry) => entry.value);
}
if (collection instanceof Map) {
const entries = [];
collection.forEach((value, key) => {
if (value) {
entries.push({ key, value });
}
});
return entries;
}
if (hasForEach(collection)) {
const entries = [];
collection.forEach((value, key) => {
if (value) {
entries.push({ key, value });
}
});
return entries;
}
if (!isNativeObject(collection)) {
return [];
}
return Object.keys(collection).map((key) => ({ key, value: readNativeProperty(collection, key) })).filter((entry) => entry.value);
}
function readFiniteNumber(source, property) {
const value = Number(readNativeProperty(source, property));
return Number.isFinite(value) ? value : null;
}
function getPlayerWorldEntityPosition(playerId, session) {
const sources = [
// Live match entities observed during gameplay.
readNativePath(session, ["KR", "uL", "Ho"]),
// Alternate player collection observed around match/lobby transitions.
readNativePath(session, ["KR", "mL", "Pi"])
];
for (const source of sources) {
for (const { key, value } of getCollectionEntries(source)) {
const id = readNativeProperty(value, "id") !== void 0 ? readNativeProperty(value, "id") : key;
const x = readFiniteNumber(value, "x");
const y = readFiniteNumber(value, "y");
if (isSamePlayerId(id, playerId) && x !== null && y !== null) {
return { x, y };
}
}
}
return null;
}
function getWorldCameraState(session) {
const camera = readNativePath(session, ["KR", "ed"]) || readNativePath(session, ["KR", "hb", "Bc"]);
return {
width: Number(readNativeProperty(camera, "fc")),
height: Number(readNativeProperty(camera, "gc")),
left: Number(readNativeProperty(camera, "yc")),
top: Number(readNativeProperty(camera, "vc"))
};
}
// src/features/world-typing-position.ts
function createWorldTypingPositioner(options) {
function getPlayerWorldEntity(playerId, session = options.getSession()) {
return getPlayerWorldEntityPosition(playerId, session);
}
function getWorldTypingViewport(session = options.getSession()) {
const canvas = options.getActiveGameplayCanvas();
if (!canvas || !isElementVisible(canvas)) {
return null;
}
const rect = canvas.getBoundingClientRect();
const camera = getWorldCameraState(session);
const baseGameSize = options.getBaseGameSize();
return {
rect,
worldLeft: Number.isFinite(camera.left) ? camera.left : 0,
worldTop: Number.isFinite(camera.top) && camera.top >= 0 ? camera.top : 0,
worldWidth: Number.isFinite(camera.width) && camera.width > 0 ? camera.width : baseGameSize.width,
worldHeight: Number.isFinite(camera.height) && camera.height > 0 ? camera.height : (Number.isFinite(camera.width) && camera.width > 0 ? camera.width : baseGameSize.width) * (rect.height / rect.width)
};
}
function getWorldTypingPosition(playerId, session = options.getSession()) {
const entity = getPlayerWorldEntity(playerId, session);
const viewport = getWorldTypingViewport(session);
if (!entity || !viewport) {
return null;
}
const { rect, worldLeft, worldTop, worldWidth, worldHeight } = viewport;
const rectRight = Number.isFinite(rect.right) ? rect.right : rect.left + rect.width;
const rectBottom = Number.isFinite(rect.bottom) ? rect.bottom : rect.top + rect.height;
const x = rect.left + (entity.x - worldLeft) / worldWidth * rect.width;
const y = rect.top + (entity.y - worldTop) / worldHeight * rect.height - 42;
return {
left: Math.max(rect.left + 11, Math.min(rectRight - 11, x)),
top: Math.max(rect.top + 18, Math.min(rectBottom - 6, y))
};
}
return {
getPlayerWorldEntity,
getWorldTypingPosition,
getWorldTypingViewport
};
}
// src/features/typing-feature-bundle.ts
function createTypingFeatureBundle(options) {
const scoreRows = createScoreRowColorController({
fallbackRgb: SCORE_ROW_FALLBACK_RGB,
teamScoreColors: TEAM_SCORE_COLORS,
getPlayerTeamState,
getScorePlayers: () => getScorePlayers(getMultiplayerSession()),
setImportantStyle: options.setImportantStyle
});
const { getWorldTypingPosition } = createWorldTypingPositioner({
getActiveGameplayCanvas: () => options.getActiveRenderCanvas("gameplay"),
getBaseGameSize: options.getBaseGameSize,
getSession: getMultiplayerSession
});
const typingIndicators = createTypingIndicatorController({
getTimeoutMs: getAdvancedTypingIndicatorDurationMs,
getLocalPlayerId,
getSession: getMultiplayerSession,
getSessionPlayers,
getWorldTypingPosition,
isChatFeatureEnabled: options.isChatFeatureEnabled,
isSamePlayerId,
isSessionMatchActive
});
return {
...scoreRows,
...typingIndicators,
getWorldTypingPosition
};
}
// src/app/qolbox-app.ts
(function() {
"use strict";
if (!shouldRunGamePageBootstrap()) {
return;
}
function scheduleAppUiWork(request) {
scheduleUiWork(request);
}
const { isFeatureEnabled, setAllFeatureSettings, setFeatureEnabled, shouldRunFeature } = createFeatureSettingsController({
isOnboardingComplete: () => qolboxMenuController.isOnboardingComplete(),
onApplyFeatureRootClasses: () => applyFeatureRootClasses(),
onApplyPersistentFeatures: () => applyPersistentFeatures(),
onDisableFeatureSideEffects: (featureKey) => disableFeatureSideEffects(featureKey),
onRenderMenu: () => renderQolboxMenu(),
onScheduleUiWork: scheduleAppUiWork,
resizeSettlePasses: RESIZE_SETTLE_PASSES
});
const featureGates = createFeatureGateSet(shouldRunFeature);
const { patchInGameChatScroll } = createInGameChatScrollController();
const {
getAdvancedSettings,
setAdvancedSettings
} = createAdvancedSettingsController({
onApplyPersistentFeatures: () => applyPersistentFeatures(),
onRenderMenu: () => renderQolboxMenu(),
onScheduleLayoutRefresh: () => scheduleAppUiWork({ force: true, features: true, passes: FULLSCREEN_SETTLE_PASSES })
});
const {
applyPersistentFeatures,
disableFeatureSideEffects
} = createFeatureSideEffectsController({
applyFeatureRootClasses: () => applyFeatureRootClasses(),
applyGameVolume: () => applyGameVolume(),
applyJukeboxState: () => applyJukeboxState(),
clearFullscreenLayoutStyles: () => clearFullscreenLayoutStyles(),
clearReservePasswordPromptPending: () => clearReservePasswordPromptPending(),
clearTypingIndicators: () => clearTypingIndicators(),
disableGameStartAlerts: () => disableGameStartAlerts(),
getReserveState: () => getReserveState(),
hideMobileGrabButton: () => hideMobileGrabButton(),
hookHowlPrototype: () => hookHowlPrototype(),
hookYouTubePlayer: () => hookYouTubePlayer(),
installGameStartIndicatorHooks: () => installGameStartIndicatorHooks(),
installPlayerPopupDismissal: () => installPlayerPopupDismissal2(),
installTabFocusHooks: () => installTabFocusHooks(),
installYouTubeReadyCallbackHook: () => installYouTubeReadyCallbackHook(),
patchChatTabOrder: () => patchChatTabOrder(),
patchInGameChatScroll,
patchGameVolumeMenu: () => patchGameVolumeMenu(),
patchJukeboxKnob: () => patchJukeboxKnob(),
patchJukeboxMenu: () => patchJukeboxMenu(),
patchLobbyMusicController: () => patchLobbyMusicController(),
patchMobileGrabButton: () => patchMobileGrabButton(),
patchMobileQolboxHamburgerEntry: () => patchMobileQolboxHamburgerEntry(),
patchReserveSpotFeature: () => patchReserveSpotFeature(),
patchSlashCommands: () => patchSlashCommands(),
patchSwitchTeamsButton: () => patchSwitchTeamsButton(),
patchTypingIndicatorHooks: () => patchTypingIndicatorHooks(),
removeJukeboxMenuItem: () => removeJukeboxMenuItem(),
removeSwitchTeamsButton: () => removeSwitchTeamsButton(),
featureGates,
stopReserveSpot: (options) => stopReserveSpot(options),
syncReserveJoinButtonLabel: () => syncReserveJoinButtonLabel(),
syncTypingIndicators: () => syncTypingIndicators(),
updateGameStartIndicator: () => updateGameStartIndicator()
});
const {
clearReservePasswordPromptPending,
getReserveState,
installReserveSocketCaptureHook,
isReserveRetryAudioSuppressed,
patchReserveSpotFeature,
stopReserveSpot,
syncReserveJoinButtonLabel
} = createReserveFeatureBundle({
hasSuccessfulJoinLayer: () => hasReserveSuccessfulJoinLayer(),
isReserveEnabled: featureGates.isReserveEnabled
});
const {
handleMobileGrabPointerStart,
hideMobileGrabButton,
isMobileGameMode,
isMobileQolboxMenuContext,
layoutMobileGrabButton,
patchMobileGrabButton,
setMobileGrabPressed,
shouldShowMobileGrabButton,
syncMobileGrabButton,
patchMobileQolboxHamburgerEntry
} = createMobileFeatureBundle({
isMobileGrabEnabled: featureGates.isMobileGrabEnabled,
openMenu: () => openQolboxMenu(qolboxMenuController.isOnboardingComplete() ? "settings" : "onboarding")
});
const {
clearGameStartIndicator,
disableGameStartAlerts,
handleGameStartInteractionFocus,
hasPendingLocalPlayTransition,
hasReserveSuccessfulJoinLayer,
installGameStartIndicatorHooks,
isCurrentPlayerSpectating,
isMenuGameplayOverlap,
isPageFocused,
isPlayableLobby,
isPlayingMatch,
noteLocallyInitiatedPlayTransition,
patchMultiplayerSessionGameStartHooks,
setGameStartPageFocused,
setGameStartWasInLobbyWhenUnfocused,
setGameStartWasPlayingWhenUnfocused,
updateGameStartIndicator
} = createGameplayAlertFeatureBundle({
isGameStartAlertEnabled: featureGates.isGameStartAlertEnabled
});
const { applyFeatureRootClasses, ensureGlobalStyle } = createQolboxShellFeatureBundle({
isMenuClosed: () => qolboxMenuController.isClosed(),
isFeatureActive: featureGates.shouldRunFeature
});
const {
buildFullscreenSignature,
clearFullscreenStyleSnapshots,
getActiveRenderCanvas,
getActiveRenderMode,
getBaseGameSize,
getFullscreenDimensions,
getLayoutProbe,
getNativeUiZoom,
getRelativeContainerBounds,
installNativeFullscreenPatch,
isEditorCanvas,
isEditorLayer,
isNativeProbeAligned,
isRenderProbeAligned,
restoreFullscreenStyles,
restoreNativeFullscreenPatch,
restoreNativeLayoutSizeFallback,
runNativeResize,
setImportantStyle,
setNativeFullscreenSize,
shouldWaitForNativeLayoutSeed
} = createFullscreenFoundationBundle();
const {
captureGameplayInputFocus,
focusActiveRenderCanvas,
handleGameplayBackgroundFocus,
installChatCommandAliasHooks,
installChatEscapeHooks,
installGameplayBackgroundFocusHooks,
isChatInput,
patchChatTabOrder,
resetBrowserScroll,
restoreLobbyChatPrompt,
shouldCaptureGameplayBackgroundFocus
} = createInputFocusFeatureBundle({
getActiveRenderCanvas,
isChatFeatureEnabled: featureGates.isChatEnabled,
areLobbyCommandsEnabled: featureGates.isLobbyCommandsEnabled,
isPlayingMatch,
isQolboxMenuClosed: () => qolboxMenuController.isClosed()
});
const {
clearTypingIndicators,
getWorldTypingPosition,
makeScoreRowsOpaque,
notePlayerTyping,
patchTypingIndicatorHooks,
syncScoreRowsFromPlayers,
syncTypingIndicators,
syncWorldTypingIndicators
} = createTypingFeatureBundle({
getActiveRenderCanvas,
getBaseGameSize,
isChatFeatureEnabled: featureGates.isChatEnabled,
setImportantStyle
});
const {
clearFullscreenLayoutStyles,
enforceFullscreenLayout,
fitEditorCanvasToNative,
fitEditorLayerToFrame,
getScaledEditorFrame,
installGameReadyHook,
layoutRelativeHud,
refreshObservedResizeTargets,
resizeKnownFullscreenRenderers: resizeKnownFullscreenRenderers2,
setFullscreenResizeObserver,
syncSpectateControlsBottomWithJukebox
} = createFullscreenLayoutFeatureBundle({
clearFullscreenSignature: () => clearFullscreenSignature(),
clearFullscreenStyleSnapshots,
ensureGlobalStyle,
getFullscreenDimensions,
getNativeUiZoom,
getRelativeContainerBounds,
isEditorCanvas,
isEditorLayer,
isFullscreenEnabled: featureGates.isFullscreenEnabled,
makeScoreRowsOpaque,
restoreFullscreenStyles,
restoreNativeFullscreenPatch,
restoreNativeLayoutSizeFallback,
scheduleUiWork: scheduleAppUiWork,
setImportantStyle,
syncScoreRowsFromPlayers,
syncTypingIndicators
});
const qolboxMenuController = createQolboxMenuFeatureBundle({
applyFeatureRootClasses,
applyPersistentFeatures,
ensureGlobalStyle,
getAdvancedSettings,
isFeatureEnabled,
scheduleUiWork: scheduleAppUiWork,
setAdvancedSettings,
setAllFeatureSettings,
setFeatureEnabled
});
const { getOnboardingSteps, installQolboxMenuHooks, openQolboxMenu, renderQolboxMenu, scheduleFirstBootOnboarding } = qolboxMenuController;
const {
applyGameVolume,
applyJukeboxState,
getEffectiveJukeboxPercent,
hookHowlPrototype,
hookYouTubePlayer,
installTabFocusHooks,
installYouTubeReadyCallbackHook,
patchGameVolumeMenu,
patchJukeboxKnob,
patchJukeboxMenu,
patchLobbyMusicController,
removeJukeboxMenuItem,
setJukeboxState,
stopLobbyMusicIfNeeded
} = createAudioFeatureBundle({
focusActiveRenderCanvas,
getActiveRenderCanvas,
getActiveRenderMode,
isAudioEnabled: featureGates.isAudioEnabled,
isChatInput,
isReserveRetryAudioSuppressed: () => Boolean(
featureGates.isReserveEnabled() && getReserveState()?.active && isReserveRetryAudioSuppressed()
),
resetBrowserScroll,
scheduleUiWork: scheduleAppUiWork
});
const {
endCurrentGame,
findPlayerByName: findPlayerByName2,
installPlayerPopupDismissal: installPlayerPopupDismissal2,
handleJoinSlashCommand,
handleQolboxSlashCommand,
handleSpecSlashCommand,
patchSlashCommands,
patchSwitchTeamsButton,
requestBulkTeamState,
requestTeamState,
restartCurrentGame,
removeSwitchTeamsButton,
showAllHostSettings,
switchTeamPlayers
} = createLobbyCommandsFeatureBundle({
areGameStartAlertsEnabled: featureGates.isGameStartAlertEnabled,
areLobbyCommandsEnabled: featureGates.isLobbyCommandsEnabled,
installStartAlertHooks: (session) => patchMultiplayerSessionGameStartHooks(session),
isCurrentPlayerSpectating,
noteLocallyInitiatedPlayTransition
});
const {
clearFullscreenSignature,
installFullscreenHooks,
scheduleUiWork
} = createFullscreenOrchestrationBundle({
applyFeatureRootClasses,
applyPersistentFeatures,
buildFullscreenSignature,
clearFullscreenLayoutStyles,
enforceFullscreenLayout,
ensureGlobalStyle,
getFullscreenDimensions,
getLayoutProbe,
installChatCommandAliasHooks,
installChatEscapeHooks,
installGameReadyHook,
installGameStartIndicatorHooks,
installGameplayBackgroundFocusHooks,
installQolboxMenuHooks,
installReserveSocketCaptureHook,
installTabFocusHooks,
installNativeFullscreenPatch,
isAudioEnabled: featureGates.isAudioEnabled,
isFullscreenEnabled: featureGates.isFullscreenEnabled,
isGameStartAlertEnabled: featureGates.isGameStartAlertEnabled,
isMenuGameplayOverlap,
isNativeProbeAligned,
isRenderProbeAligned,
isReserveEnabled: featureGates.isReserveEnabled,
patchLobbyMusicController,
refreshObservedResizeTargets,
resizeKnownFullscreenRenderers: resizeKnownFullscreenRenderers2,
runNativeResize,
setFullscreenResizeObserver,
setNativeFullscreenSize,
shouldWaitForNativeLayoutSeed,
stopLobbyMusicIfNeeded,
syncSpectateControlsBottomWithJukebox,
updateGameStartIndicator
});
runQolboxStartupSequence({
applyFeatureRootClasses,
ensureGlobalStyle,
installFullscreenHooks,
installQolboxMenuHooks,
installReserveSocketCaptureHook,
installYouTubeReadyCallbackHook,
isAudioEnabled: featureGates.isAudioEnabled,
isReserveEnabled: featureGates.isReserveEnabled,
scheduleFirstBootOnboarding,
scheduleUiWork
});
})();
})();