QOLBox

Fullscreen hitbox.io, reserve spots, away-tab alerts, audio controls, mobile Grab, readable chat, lobby commands, and first-start setup for hitbox.io.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey, το Greasemonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

You will need to install an extension such as Tampermonkey to install this script.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Userscripts για να εγκαταστήσετε αυτόν τον κώδικα.

You will need to install an extension such as Tampermonkey to install this script.

Θα χρειαστεί να εγκαταστήσετε μια επέκταση διαχείρισης κώδικα χρήστη για να εγκαταστήσετε αυτόν τον κώδικα.

(Έχω ήδη έναν διαχειριστή κώδικα χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

Advertisement:

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Έχω ήδη έναν διαχειριστή στυλ χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

Advertisement:

// ==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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
  }
  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
    });
  })();
})();