Bomb Party Suggester

Suggests words containing the required syllable for JKLM.fun Bomb Party game with customizable dictionaries and sorting options

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Bomb Party Suggester
// @namespace    http://tampermonkey.net/
// @version      0.1.2
// @description  Suggests words containing the required syllable for JKLM.fun Bomb Party game with customizable dictionaries and sorting options
// @author       Doomsy1
// @match        *.jklm.fun/games/bombparty*
// @grant        none
// @run-at       document-start
// @supportURL   https://github.com/Doomsy1/Bomb-Party-Suggester/issues
// @icon         https://www.google.com/s2/favicons?sz=64&domain=jklm.fun
// @license      MIT
// ==/UserScript==
(() => {
  // src/core/typer.js
  window.BPS = window.BPS || {};
  (function() {
    let KEYBOARD_LAYOUT = {
      layout: {
        q: [0, 0],
        w: [0, 1],
        e: [0, 2],
        r: [0, 3],
        t: [0, 4],
        y: [0, 5],
        u: [0, 6],
        i: [0, 7],
        o: [0, 8],
        p: [0, 9],
        a: [1, 0],
        s: [1, 1],
        d: [1, 2],
        f: [1, 3],
        g: [1, 4],
        h: [1, 5],
        j: [1, 6],
        k: [1, 7],
        l: [1, 8],
        z: [2, 0],
        x: [2, 1],
        c: [2, 2],
        v: [2, 3],
        b: [2, 4],
        n: [2, 5],
        m: [2, 6]
      },
      adjacent: {}
    };
    Object.entries(KEYBOARD_LAYOUT.layout).forEach(([key, [row, col]]) => {
      KEYBOARD_LAYOUT.adjacent[key] = Object.entries(KEYBOARD_LAYOUT.layout).filter(([k, [r, c]]) => {
        if (k === key) return !1;
        let rowDiff = Math.abs(r - row), colDiff = Math.abs(c - col);
        return rowDiff <= 1 && colDiff <= 1;
      }).map(([k]) => k);
    });
    let TYPER_CONFIG = {
      baseDelay: 60,
      distanceMultiplier: 12.5,
      minDelay: 15,
      delayVariation: 0.2,
      typoChance: 2,
      typoNoticeDelay: { mean: 250, stdDev: 60 },
      typoBackspaceDelay: { mean: 100, stdDev: 40 },
      typoRecoveryDelay: { mean: 200, stdDev: 50 }
    };
    function loadSavedSettings() {
      let saved = localStorage.getItem("bombPartyTyperSettings");
      if (saved)
        try {
          let parsed = JSON.parse(saved);
          Object.assign(TYPER_CONFIG, parsed);
        } catch {
        }
    }
    function saveSettings() {
      try {
        localStorage.setItem("bombPartyTyperSettings", JSON.stringify(TYPER_CONFIG));
      } catch {
      }
    }
    function normalRandom(mean, stdDev) {
      let u = 0, v = 0;
      for (; u === 0; ) u = Math.random();
      for (; v === 0; ) v = Math.random();
      let num = Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
      return Math.floor(num * stdDev + mean);
    }
    function calculateTypingDelay(fromKey, toKey) {
      if (!fromKey) return TYPER_CONFIG.baseDelay;
      fromKey = fromKey.toLowerCase(), toKey = toKey.toLowerCase();
      let fromPos = KEYBOARD_LAYOUT.layout[fromKey], toPos = KEYBOARD_LAYOUT.layout[toKey];
      if (!fromPos || !toPos) return TYPER_CONFIG.baseDelay;
      let distance = Math.sqrt(
        Math.pow(fromPos[0] - toPos[0], 2) + Math.pow(fromPos[1] - toPos[1], 2)
      ), meanDelay = TYPER_CONFIG.baseDelay + distance * TYPER_CONFIG.distanceMultiplier, stdDev = meanDelay * TYPER_CONFIG.delayVariation;
      return Math.max(TYPER_CONFIG.minDelay, normalRandom(meanDelay, stdDev));
    }
    async function simulateTypo(inputField, correctChar) {
      let c = correctChar.toLowerCase();
      if (!KEYBOARD_LAYOUT.adjacent[c] || Math.random() > TYPER_CONFIG.typoChance / 100) return !1;
      let neighbors = KEYBOARD_LAYOUT.adjacent[c], typoChar = neighbors[Math.floor(Math.random() * neighbors.length)];
      return inputField.value += typoChar, inputField.dispatchEvent(new Event("input", { bubbles: !0 })), await new Promise((resolve) => setTimeout(resolve, calculateTypingDelay(null, typoChar))), await new Promise((resolve) => setTimeout(
        resolve,
        normalRandom(TYPER_CONFIG.typoNoticeDelay.mean, TYPER_CONFIG.typoNoticeDelay.stdDev)
      )), inputField.value = inputField.value.slice(0, -1), inputField.dispatchEvent(new Event("input", { bubbles: !0 })), await new Promise((resolve) => setTimeout(
        resolve,
        normalRandom(TYPER_CONFIG.typoBackspaceDelay.mean, TYPER_CONFIG.typoBackspaceDelay.stdDev)
      )), inputField.value += correctChar, inputField.dispatchEvent(new Event("input", { bubbles: !0 })), await new Promise((resolve) => setTimeout(
        resolve,
        normalRandom(TYPER_CONFIG.typoRecoveryDelay.mean, TYPER_CONFIG.typoRecoveryDelay.stdDev)
      )), !0;
    }
    async function simulateTyping(word) {
      let selfTurn = document.querySelector(".selfTurn"), form = document.querySelector(".selfTurn form"), inputField = document.querySelector(".selfTurn input");
      if (!inputField || !form || selfTurn.hidden)
        return;
      inputField.value = "", inputField.focus();
      let lastChar = null;
      for (let i = 0; i < word.length; i++)
        await simulateTypo(inputField, word[i]) || (inputField.value += word[i], inputField.dispatchEvent(new Event("input", { bubbles: !0 })), await new Promise((resolve) => setTimeout(resolve, calculateTypingDelay(lastChar, word[i]))), lastChar = word[i]);
      form.dispatchEvent(new Event("submit", { bubbles: !0, cancelable: !0 }));
    }
    function isPlayerTurn() {
      let selfTurn = document.querySelector(".selfTurn");
      return selfTurn && !selfTurn.hidden;
    }
    loadSavedSettings(), window.BPS.KEYBOARD_LAYOUT = KEYBOARD_LAYOUT, window.BPS.TYPER_CONFIG = TYPER_CONFIG, window.BPS.loadSavedSettings = loadSavedSettings, window.BPS.saveSettings = saveSettings, window.BPS.normalRandom = normalRandom, window.BPS.calculateTypingDelay = calculateTypingDelay, window.BPS.simulateTyping = simulateTyping, window.BPS.isPlayerTurn = isPlayerTurn;
  })();

  // src/core/dictionaryLoader.js
  window.BPS = window.BPS || {};
  (function() {
    let dictionaries = {
      "5k": {
        url: "https://raw.githubusercontent.com/filiph/english_words/master/data/word-freq-top5000.csv",
        words: [],
        hasFrequency: !0
      },
      "20k": {
        url: "https://raw.githubusercontent.com/first20hours/google-10000-english/master/google-10000-english-usa.txt",
        words: [],
        hasFrequency: !0
      },
      "273k": {
        url: "https://raw.githubusercontent.com/kli512/bombparty-assist/refs/heads/main/bombparty/dictionaries/en.txt",
        words: [],
        hasFrequency: !1
      }
    };
    async function loadDictionary(size) {
      let dictionary = dictionaries[size], lines = (await (await fetch(dictionary.url)).text()).split(`
`);
      switch (size) {
        case "5k":
          let dataLines = lines.slice(1);
          dictionary.words = dataLines.map((line) => {
            let trimmed = line.trim();
            if (!trimmed) return { word: "", freq: 0 };
            let parts = trimmed.split(",");
            if (parts.length < 4) return { word: "", freq: 0 };
            let word = parts[1] || "", freq = parseInt(parts[3], 10) || 0;
            return { word, freq };
          });
          break;
        case "20k":
          dictionary.words = lines.map((line, idx) => ({
            word: line.trim(),
            freq: lines.length - idx
            // higher index = less frequent
          }));
          break;
        case "273k":
          dictionary.words = lines.filter((line) => line.trim().length > 0).map((line) => ({
            word: line.trim().toLowerCase(),
            freq: 1
            // treat all words equally
          }));
          break;
        default:
          return;
      }
      dictionary.words = dictionary.words.filter((entry) => entry.word);
    }
    async function loadAllDictionaries() {
      try {
        await Promise.all([
          loadDictionary("5k"),
          loadDictionary("20k"),
          loadDictionary("273k")
        ]);
      } catch {
      }
    }
    window.BPS.dictionaries = dictionaries, window.BPS.loadAllDictionaries = loadAllDictionaries;
  })();

  // src/ui/styles.js
  window.BPS = window.BPS || {};
  (function() {
    let styles = {
      colors: {
        primary: "#61dafb",
        background: "#282c34",
        text: "#ffffff",
        highlight: "#2EFF2E",
        special: "#FF8C00"
      },
      panel: {
        position: "fixed",
        top: "10px",
        right: "10px",
        backgroundColor: "rgba(40, 44, 52, 0.5)",
        border: "2px solid #61dafb",
        borderRadius: "8px",
        padding: "10px",
        zIndex: "2147483647",
        maxWidth: "500px",
        minWidth: "200px",
        minHeight: "150px",
        maxHeight: "800px",
        width: "300px",
        height: "400px",
        fontFamily: "sans-serif",
        fontSize: "14px",
        color: "#fff",
        boxShadow: "0px 4px 12px rgba(0,0,0,0.5)",
        cursor: "move",
        resize: "none",
        overflow: "hidden"
      },
      resizeHandle: {
        position: "absolute",
        width: "20px",
        height: "20px",
        background: "transparent",
        zIndex: "2147483647",
        cursor: "nw-resize"
      },
      resizeDot: {
        position: "absolute",
        width: "8px",
        height: "8px",
        background: "#61dafb",
        borderRadius: "50%",
        left: "50%",
        top: "50%",
        transform: "translate(-50%, -50%)"
      },
      resizeEdge: {
        position: "absolute",
        background: "transparent",
        zIndex: "2147483647"
      },
      sizeSelector: {
        marginBottom: "4px",
        display: "flex",
        gap: "8px",
        justifyContent: "center"
      },
      sortControls: {
        marginBottom: "8px",
        display: "flex",
        gap: "8px",
        justifyContent: "center",
        flexWrap: "wrap"
      },
      sortButton: {
        padding: "4px 8px",
        border: "1px solid #61dafb",
        borderRadius: "4px",
        background: "transparent",
        color: "#fff",
        cursor: "pointer",
        fontSize: "12px",
        display: "flex",
        alignItems: "center",
        gap: "4px"
      },
      activeSortButton: {
        background: "#61dafb",
        color: "#282c34"
      },
      button: {
        padding: "4px 8px",
        border: "1px solid #61dafb",
        borderRadius: "4px",
        background: "transparent",
        color: "#fff",
        cursor: "pointer"
      },
      activeButton: {
        background: "#61dafb",
        color: "#282c34"
      },
      resultsList: {
        listStyle: "none",
        padding: "0",
        margin: "0"
      },
      resultsItem: {
        padding: "4px 0",
        textAlign: "center",
        fontSize: "14px",
        cursor: "pointer",
        transition: "background-color 0.2s",
        borderRadius: "4px"
      },
      resultsItemHover: {
        backgroundColor: "rgba(97, 218, 251, 0.2)"
      },
      resultsItemDisabled: {
        backgroundColor: "rgba(220, 53, 69, 0.2)"
      },
      resultsDiv: {
        height: "auto",
        overflowY: "visible",
        marginTop: "8px"
      },
      settingsButton: {
        position: "absolute",
        top: "10px",
        right: "10px",
        padding: "4px 8px",
        border: "1px solid #61dafb",
        borderRadius: "4px",
        background: "transparent",
        color: "#fff",
        cursor: "pointer",
        fontSize: "12px"
      },
      settingsPanel: {
        position: "fixed",
        top: "10px",
        left: "10px",
        backgroundColor: "rgba(40, 44, 52, 0.9)",
        border: "2px solid #61dafb",
        borderRadius: "8px",
        padding: "12px",
        zIndex: "2147483647",
        width: "220px",
        color: "#fff",
        fontFamily: "sans-serif",
        fontSize: "12px",
        cursor: "move",
        boxShadow: "0px 4px 12px rgba(0,0,0,0.5)"
      },
      settingsGroup: {
        marginBottom: "8px",
        display: "flex",
        flexDirection: "column"
      },
      settingsLabel: {
        display: "block",
        marginBottom: "2px",
        color: "#61dafb",
        fontSize: "11px"
      },
      settingsInputGroup: {
        display: "flex",
        gap: "8px",
        alignItems: "center"
      },
      settingsInput: {
        width: "50px",
        padding: "2px 4px",
        backgroundColor: "rgba(255, 255, 255, 0.1)",
        border: "1px solid #61dafb",
        borderRadius: "4px",
        color: "#fff",
        fontSize: "11px"
      },
      settingsSlider: {
        flex: 1,
        height: "4px",
        WebkitAppearance: "none",
        background: "rgba(97, 218, 251, 0.2)",
        borderRadius: "2px",
        outline: "none"
      }
    };
    function applyStyles(element, styleObj) {
      Object.assign(element.style, styleObj);
    }
    window.BPS.styles = styles, window.BPS.applyStyles = applyStyles;
  })();

  // src/ui/dragResize.js
  window.BPS = window.BPS || {};
  (function() {
    "use strict";
    let styles = window.BPS.styles, applyStyles = window.BPS.applyStyles;
    function makeDraggable(element) {
      let isDragging = !1, offsetX = 0, offsetY = 0;
      element.addEventListener("mousedown", (e) => {
        let tag = e.target.tagName.toLowerCase();
        tag === "button" || tag === "input" || (isDragging = !0, offsetX = e.clientX - element.offsetLeft, offsetY = e.clientY - element.offsetTop, e.preventDefault());
      }), document.addEventListener("mousemove", (e) => {
        if (!isDragging) return;
        e.preventDefault();
        let x = e.clientX - offsetX, y = e.clientY - offsetY;
        element.style.left = Math.max(0, Math.min(window.innerWidth - element.offsetWidth, x)) + "px", element.style.top = Math.max(0, Math.min(window.innerHeight - element.offsetHeight, y)) + "px";
      }), document.addEventListener("mouseup", () => {
        isDragging = !1;
      });
    }
    function setupDraggableResize(panel) {
      [
        { corner: "nw", top: "-10px", left: "-10px", cursor: "nw-resize" },
        { corner: "ne", top: "-10px", right: "-10px", cursor: "ne-resize" },
        { corner: "se", bottom: "-10px", right: "-10px", cursor: "se-resize" },
        { corner: "sw", bottom: "-10px", left: "-10px", cursor: "sw-resize" }
      ].forEach((pos) => {
        let handle = document.createElement("div");
        handle.className = `resize-handle ${pos.corner}`, applyStyles(handle, { ...styles.resizeHandle, ...pos });
        let dot = document.createElement("div");
        applyStyles(dot, styles.resizeDot), handle.appendChild(dot), panel.appendChild(handle);
      }), [
        { edge: "n", top: "-5px", left: "20px", right: "20px", height: "10px", cursor: "ns-resize" },
        { edge: "s", bottom: "-5px", left: "20px", right: "20px", height: "10px", cursor: "ns-resize" },
        { edge: "e", top: "20px", right: "-5px", bottom: "20px", width: "10px", cursor: "ew-resize" },
        { edge: "w", top: "20px", left: "-5px", bottom: "20px", width: "10px", cursor: "ew-resize" }
      ].forEach((pos) => {
        let edge = document.createElement("div");
        edge.className = `resize-edge ${pos.edge}`, applyStyles(edge, { ...styles.resizeEdge, ...pos }), panel.appendChild(edge);
      });
      let draggingPanel = !1, offsetX = 0, offsetY = 0;
      panel.addEventListener("mousedown", (e) => {
        e.target.classList.contains("resize-handle") || e.target.classList.contains("resize-edge") || (draggingPanel = !0, offsetX = e.clientX - panel.getBoundingClientRect().left, offsetY = e.clientY - panel.getBoundingClientRect().top, e.preventDefault());
      }), panel.addEventListener("mousemove", (e) => {
        if (!draggingPanel) return;
        let newLeft = e.clientX - offsetX, newTop = e.clientY - offsetY;
        panel.style.left = Math.max(0, Math.min(window.innerWidth - panel.offsetWidth, newLeft)) + "px", panel.style.top = Math.max(0, Math.min(window.innerHeight - panel.offsetHeight, newTop)) + "px";
      }), panel.addEventListener("mouseup", () => {
        draggingPanel = !1;
      }), panel.addEventListener("mouseleave", () => {
        draggingPanel = !1;
      });
      let resizing = !1, currentResizer = null, startX, startY, startWidth, startHeight, panelLeft, panelTop;
      [
        ...panel.querySelectorAll(".resize-handle"),
        ...panel.querySelectorAll(".resize-edge")
      ].forEach((r) => {
        r.addEventListener("mousedown", (e) => {
          resizing = !0, currentResizer = r, startX = e.clientX, startY = e.clientY;
          let rect = panel.getBoundingClientRect();
          startWidth = rect.width, startHeight = rect.height, panelLeft = rect.left, panelTop = rect.top, e.preventDefault(), e.stopPropagation();
        });
      }), document.addEventListener("mousemove", (e) => {
        if (!resizing || !currentResizer) return;
        let dx = e.clientX - startX, dy = e.clientY - startY, maxW = parseInt(styles.panel.maxWidth, 10) || 500, minW = parseInt(styles.panel.minWidth, 10) || 200, maxH = parseInt(styles.panel.maxHeight, 10) || 800, minH = parseInt(styles.panel.minHeight, 10) || 150, newW = startWidth, newH = startHeight, newL = panelLeft, newT = panelTop, direction = currentResizer.classList[1];
        if (currentResizer.classList.contains("resize-handle"))
          switch (direction) {
            case "nw":
              newW = startWidth - dx, newH = startHeight - dy, newL = panelLeft + (startWidth - newW), newT = panelTop + (startHeight - newH);
              break;
            case "ne":
              newW = startWidth + dx, newH = startHeight - dy, newT = panelTop + (startHeight - newH);
              break;
            case "se":
              newW = startWidth + dx, newH = startHeight + dy;
              break;
            case "sw":
              newW = startWidth - dx, newH = startHeight + dy, newL = panelLeft + (startWidth - newW);
              break;
          }
        else
          switch (direction) {
            case "n":
              newH = startHeight - dy, newT = panelTop + (startHeight - newH);
              break;
            case "s":
              newH = startHeight + dy;
              break;
            case "e":
              newW = startWidth + dx;
              break;
            case "w":
              newW = startWidth - dx, newL = panelLeft + (startWidth - newW);
              break;
          }
        newW = Math.min(maxW, Math.max(minW, newW)), newH = Math.min(maxH, Math.max(minH, newH)), newL = Math.min(window.innerWidth - newW, Math.max(0, newL)), newT = Math.min(window.innerHeight - newH, Math.max(0, newT)), panel.style.width = newW + "px", panel.style.height = newH + "px", panel.style.left = newL + "px", panel.style.top = newT + "px";
      }), document.addEventListener("mouseup", () => {
        resizing = !1, currentResizer = null;
      });
    }
    window.BPS.makeDraggable = makeDraggable, window.BPS.setupDraggableResize = setupDraggableResize;
  })();

  // src/ui/settings.js
  window.BPS = window.BPS || {};
  (function() {
    "use strict";
    let styles = window.BPS.styles, applyStyles = window.BPS.applyStyles, TYPER_CONFIG = window.BPS.TYPER_CONFIG, saveSettings = window.BPS.saveSettings, loadSavedSettings = window.BPS.loadSavedSettings, makeDraggable = window.BPS.makeDraggable;
    function createSettingsPanel() {
      let panel = document.createElement("div");
      panel.id = "typerSettingsPanel", applyStyles(panel, styles.settingsPanel), panel.style.display = "none", makeDraggable(panel);
      let header = document.createElement("div");
      applyStyles(header, {
        display: "flex",
        justifyContent: "space-between",
        alignItems: "center",
        marginBottom: "10px"
      });
      let title = document.createElement("h3");
      title.textContent = "Typer Settings", title.style.margin = "0", title.style.color = "#61dafb", title.style.fontSize = "14px", header.appendChild(title);
      let resetBtn = document.createElement("button");
      return resetBtn.textContent = "\u21BA", resetBtn.title = "Reset to defaults", applyStyles(resetBtn, {
        ...styles.button,
        padding: "2px 6px",
        fontSize: "14px",
        marginLeft: "8px",
        backgroundColor: "transparent"
      }), resetBtn.onmouseenter = () => {
        resetBtn.style.backgroundColor = "rgba(97, 218, 251, 0.2)";
      }, resetBtn.onmouseleave = () => {
        resetBtn.style.backgroundColor = "transparent";
      }, resetBtn.onclick = () => {
        Object.assign(TYPER_CONFIG, JSON.parse(JSON.stringify({
          baseDelay: 60,
          distanceMultiplier: 12.5,
          minDelay: 15,
          delayVariation: 0.2,
          typoChance: 2,
          typoNoticeDelay: { mean: 250, stdDev: 60 },
          typoBackspaceDelay: { mean: 100, stdDev: 40 },
          typoRecoveryDelay: { mean: 200, stdDev: 50 }
        }))), saveSettings(), refreshSettingsPanel(panel);
      }, header.appendChild(resetBtn), panel.appendChild(header), panel.appendChild(createSettingInput("Base Delay (ms)", "baseDelay", TYPER_CONFIG.baseDelay, 0, 100, 1)), panel.appendChild(createSettingInput("Distance Multiplier", "distanceMultiplier", TYPER_CONFIG.distanceMultiplier, 0, 20, 0.1)), panel.appendChild(createSettingInput("Minimum Delay (ms)", "minDelay", TYPER_CONFIG.minDelay, 0, 50, 1)), panel.appendChild(createSettingInput("Delay Variation", "delayVariation", TYPER_CONFIG.delayVariation, 0, 1, 0.01)), panel.appendChild(createSettingInput("Typo Chance (%)", "typoChance", TYPER_CONFIG.typoChance, 0, 10, 0.1)), panel.appendChild(createSettingInput("Notice Delay (ms)", "typoNoticeDelay.mean", TYPER_CONFIG.typoNoticeDelay.mean, 0, 1e3, 10)), panel.appendChild(createSettingInput("Notice Variation", "typoNoticeDelay.stdDev", TYPER_CONFIG.typoNoticeDelay.stdDev, 0, 200, 5)), panel.appendChild(createSettingInput("Backspace Delay (ms)", "typoBackspaceDelay.mean", TYPER_CONFIG.typoBackspaceDelay.mean, 0, 500, 10)), panel.appendChild(createSettingInput("Backspace Variation", "typoBackspaceDelay.stdDev", TYPER_CONFIG.typoBackspaceDelay.stdDev, 0, 100, 5)), panel.appendChild(createSettingInput("Recovery Delay (ms)", "typoRecoveryDelay.mean", TYPER_CONFIG.typoRecoveryDelay.mean, 0, 500, 10)), panel.appendChild(createSettingInput("Recovery Variation", "typoRecoveryDelay.stdDev", TYPER_CONFIG.typoRecoveryDelay.stdDev, 0, 100, 5)), document.body.appendChild(panel), panel;
    }
    function createSettingInput(labelText, configPath, initialValue, min, max, step) {
      let group = document.createElement("div");
      group.className = "settingsGroup", applyStyles(group, styles.settingsGroup);
      let labelEl = document.createElement("label");
      labelEl.textContent = labelText, applyStyles(labelEl, styles.settingsLabel), group.appendChild(labelEl);
      let inputGroup = document.createElement("div");
      applyStyles(inputGroup, styles.settingsInputGroup);
      let slider = document.createElement("input");
      slider.type = "range", slider.min = min, slider.max = max, slider.step = step, slider.value = initialValue, applyStyles(slider, styles.settingsSlider);
      let numericInput = document.createElement("input");
      numericInput.type = "number", numericInput.value = initialValue, numericInput.min = min, numericInput.max = max, numericInput.step = step, applyStyles(numericInput, styles.settingsInput);
      let updateValue = (val) => {
        let keys = configPath.split("."), target = TYPER_CONFIG;
        for (let i = 0; i < keys.length - 1; i++)
          target = target[keys[i]];
        target[keys[keys.length - 1]] = parseFloat(val), slider.value = val, numericInput.value = val, saveSettings();
      };
      return slider.addEventListener("input", () => updateValue(slider.value)), numericInput.addEventListener("change", () => updateValue(numericInput.value)), inputGroup.appendChild(slider), inputGroup.appendChild(numericInput), group.appendChild(inputGroup), group;
    }
    function refreshSettingsPanel(panel) {
      panel.querySelectorAll(".settingsGroup").forEach((group) => {
        let label = group.querySelector("label").textContent, slider = group.querySelector('input[type="range"]'), numericInput = group.querySelector('input[type="number"]'), path = {
          "Base Delay (ms)": "baseDelay",
          "Distance Multiplier": "distanceMultiplier",
          "Minimum Delay (ms)": "minDelay",
          "Delay Variation": "delayVariation",
          "Typo Chance (%)": "typoChance",
          "Notice Delay (ms)": "typoNoticeDelay.mean",
          "Notice Variation": "typoNoticeDelay.stdDev",
          "Backspace Delay (ms)": "typoBackspaceDelay.mean",
          "Backspace Variation": "typoBackspaceDelay.stdDev",
          "Recovery Delay (ms)": "typoRecoveryDelay.mean",
          "Recovery Variation": "typoRecoveryDelay.stdDev"
        }[label];
        if (!path) return;
        let parts = path.split("."), val = TYPER_CONFIG;
        for (let p of parts)
          val = val[p];
        slider.value = val, numericInput.value = val;
      });
    }
    window.BPS.createSettingsPanel = createSettingsPanel, window.BPS.refreshSettingsPanel = refreshSettingsPanel;
  })();

  // src/ui/suggester.js
  window.BPS = window.BPS || {};
  (function() {
    "use strict";
    let styles = window.BPS.styles, applyStyles = window.BPS.applyStyles, dictionaries = window.BPS.dictionaries, simulateTyping = window.BPS.simulateTyping, isPlayerTurn = window.BPS.isPlayerTurn, currentDictionary = "20k", currentSort = { method: "frequency", direction: "desc" }, letterScores = {
      e: 1,
      t: 2,
      a: 3,
      o: 4,
      i: 5,
      n: 6,
      s: 7,
      r: 8,
      h: 9,
      d: 10,
      l: 11,
      u: 12,
      c: 13,
      m: 14,
      f: 15,
      y: 16,
      w: 17,
      g: 18,
      p: 19,
      b: 20,
      v: 21,
      k: 22,
      x: 23,
      q: 24,
      j: 25,
      z: 26
    };
    function calculateRarityScore(word) {
      return word.toLowerCase().split("").reduce((score, letter) => score + (letterScores[letter] || 13), 0);
    }
    function sortMatches(matches) {
      let { method, direction } = currentSort;
      method === "frequency" && !dictionaries[currentDictionary].hasFrequency && (method = "length");
      let sortFns = {
        frequency: (a, b) => b.freq - a.freq,
        length: (a, b) => b.word.length - a.word.length,
        rarity: (a, b) => calculateRarityScore(b.word) - calculateRarityScore(a.word)
      }, sortFn = sortFns[method] || sortFns.length;
      return matches.sort(direction === "desc" ? sortFn : (a, b) => -sortFn(a, b)), matches;
    }
    function suggestWords(syllable) {
      let resultsDiv = document.getElementById("bombPartyWordSuggesterResults");
      if (!resultsDiv) return;
      if (!syllable) {
        resultsDiv.textContent = "(Waiting for syllable...)";
        return;
      }
      let dictObj = dictionaries[currentDictionary];
      if (!dictObj.words.length) {
        resultsDiv.textContent = "Dictionary not ready yet...";
        return;
      }
      let lower = syllable.toLowerCase(), matches = dictObj.words.filter((e) => e.word.toLowerCase().includes(lower));
      if (!matches.length) {
        resultsDiv.textContent = "No suggestions found.";
        return;
      }
      sortMatches(matches);
      let ul = document.createElement("ul");
      applyStyles(ul, styles.resultsList), matches.slice(0, 15).forEach(({ word }) => {
        let li = document.createElement("li");
        applyStyles(li, styles.resultsItem), li.onmouseenter = () => {
          isPlayerTurn() ? applyStyles(li, styles.resultsItemHover) : applyStyles(li, styles.resultsItemDisabled);
        }, li.onmouseleave = () => {
          applyStyles(li, { backgroundColor: "transparent" });
        }, li.onclick = () => {
          isPlayerTurn() && simulateTyping(word);
        };
        let idx = word.toLowerCase().indexOf(lower);
        if (idx >= 0) {
          let before = word.slice(0, idx), match = word.slice(idx, idx + lower.length), after = word.slice(idx + lower.length);
          li.innerHTML = `${before}<span style="color:${styles.colors.highlight}">${match}</span>${after}`;
        } else
          li.textContent = word;
        ul.appendChild(li);
      }), resultsDiv.innerHTML = "", resultsDiv.appendChild(ul);
    }
    function createDictionarySizeSelector() {
      let container = document.createElement("div");
      return applyStyles(container, styles.sizeSelector), ["5k", "20k", "273k"].forEach((dictSize) => {
        let btn = document.createElement("button");
        btn.textContent = dictSize, applyStyles(btn, styles.button), btn.onclick = () => {
          if (!dictionaries[dictSize].words.length) return;
          currentDictionary = dictSize, [...container.querySelectorAll("button")].forEach((b) => {
            applyStyles(b, styles.button);
          }), applyStyles(btn, { ...styles.button, ...styles.activeButton }), currentSort.method === "frequency" && !dictionaries[dictSize].hasFrequency && (currentSort.method = "length", currentSort.direction = "desc");
          let sEl = document.querySelector(".syllable");
          sEl && suggestWords(sEl.textContent.trim());
        }, btn.onmousedown = (e) => e.stopPropagation(), container.appendChild(btn);
      }), container;
    }
    function createSortControls() {
      let sortControls = document.createElement("div");
      return applyStyles(sortControls, styles.sortControls), Object.entries({
        frequency: "Freq",
        length: "Len",
        rarity: "Rare"
      }).forEach(([method, label]) => {
        let btn = document.createElement("button");
        btn.textContent = label + " \u2191", applyStyles(btn, styles.sortButton);
        let isAscending = !0;
        btn.onclick = () => {
          if (method === "frequency" && !dictionaries[currentDictionary].hasFrequency)
            return;
          currentSort.method === method ? isAscending = !isAscending : isAscending = !0, currentSort.method = method, currentSort.direction = isAscending ? "desc" : "asc", [...sortControls.querySelectorAll("button")].forEach((b) => {
            applyStyles(b, styles.sortButton), b.textContent = b.textContent.replace(/[↑↓]/, "\u2191");
          }), applyStyles(btn, { ...styles.sortButton, ...styles.activeSortButton }), btn.textContent = `${label} ${isAscending ? "\u2193" : "\u2191"}`;
          let sEl = document.querySelector(".syllable");
          sEl && suggestWords(sEl.textContent.trim());
        }, btn.onmousedown = (e) => e.stopPropagation(), sortControls.appendChild(btn);
      }), sortControls;
    }
    function getCurrentDictionary() {
      return currentDictionary;
    }
    function getCurrentSort() {
      return currentSort;
    }
    window.BPS.suggestWords = suggestWords, window.BPS.createDictionarySizeSelector = createDictionarySizeSelector, window.BPS.createSortControls = createSortControls, window.BPS.getCurrentDictionary = getCurrentDictionary, window.BPS.getCurrentSort = getCurrentSort;
  })();

  // src/ui/main.js
  window.BPS = window.BPS || {};
  (function() {
    "use strict";
    let styles = window.BPS.styles, applyStyles = window.BPS.applyStyles, setupDraggableResize = window.BPS.setupDraggableResize, createSettingsPanel = window.BPS.createSettingsPanel, createDictionarySizeSelector = window.BPS.createDictionarySizeSelector, createSortControls = window.BPS.createSortControls;
    function createUI() {
      let panel = document.createElement("div");
      panel.id = "bombPartyWordSuggesterPanel", applyStyles(panel, styles.panel), setupDraggableResize(panel);
      let content = document.createElement("div");
      content.id = "bombPartyWordSuggesterContent", panel.appendChild(content);
      let sizeSelector = createDictionarySizeSelector();
      content.appendChild(sizeSelector);
      let sortControls = createSortControls();
      content.appendChild(sortControls);
      let resultsDiv = document.createElement("div");
      resultsDiv.id = "bombPartyWordSuggesterResults", applyStyles(resultsDiv, styles.resultsDiv), resultsDiv.textContent = "(Waiting for syllable...)", content.appendChild(resultsDiv);
      let settingsPanel = createSettingsPanel(), settingsButton = document.createElement("button");
      settingsButton.textContent = "\u2699\uFE0F", applyStyles(settingsButton, styles.settingsButton), settingsButton.onclick = () => {
        settingsPanel.style.display = settingsPanel.style.display === "none" ? "block" : "none";
      }, settingsButton.onmousedown = (e) => e.stopPropagation(), panel.appendChild(settingsButton), document.body.appendChild(panel);
      let dictButtons = sizeSelector.querySelectorAll("button");
      dictButtons[1] && applyStyles(dictButtons[1], { ...styles.button, ...styles.activeButton });
      let sortButtons = sortControls.querySelectorAll("button");
      sortButtons[0] && (applyStyles(sortButtons[0], { ...styles.sortButton, ...styles.activeSortButton }), sortButtons[0].textContent = "Freq \u2193");
    }
    function initScript() {
      createUI(), window.BPS.setupSyllableObserver();
    }
    window.BPS.initScript = initScript;
  })();

  // src/ui/observer.js
  window.BPS = window.BPS || {};
  (function() {
    "use strict";
    let syllableObserver = null, suggestWords = window.BPS.suggestWords;
    function setupSyllableObserver() {
      if (syllableObserver) return;
      syllableObserver = new MutationObserver((mutations) => {
        for (let m of mutations)
          if (m.type === "childList" || m.type === "characterData") {
            let text = m.target.textContent.trim();
            text && suggestWords(text);
          }
      });
      function waitForSyllable() {
        let el = document.querySelector(".syllable");
        el ? (syllableObserver.observe(el, { childList: !0, characterData: !0, subtree: !0 }), el.textContent.trim() && suggestWords(el.textContent.trim())) : setTimeout(waitForSyllable, 1e3);
      }
      waitForSyllable();
    }
    window.BPS.setupSyllableObserver = setupSyllableObserver;
  })();

  // src/index.js
  (function() {
    "use strict";
    typeof window.BPS < "u" && window.BPS.loadAllDictionaries().then(() => {
      window.BPS.initScript();
    });
  })();
})();