Trapi's Client

A Bonk.io client for hosting, stats, player database, and gameplay utilities

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==UserScript==
// @name         Trapi's Client
// @version      9.0.12
// @author       Salama
// @description  A Bonk.io client for hosting, stats, player database, and gameplay utilities
// @match        https://bonk.io/gameframe-release.html
// @match        https://bonkisback.io/gameframe-release.html
// @run-at       document-start
// @grant        none
// @supportURL   https://discord.gg/Dj6usq7ww3
// @namespace    https://greasyfork.org/users/824888
// ==/UserScript==

// v6.7: Built-in injector plus diagnostics, version info, settings import/export, and player cards.
// v6.8: Built-in Bonk code injector bundled and hardened.
// v6.9: Added Go To Lobby button and refined Main Menu/Restart visibility.
// v7.0: Added local saved Player Card history: wins, games, winrate, joins, and last seen.
// v7.1: Made Go To Lobby context-aware: hosts return to room lobby, non-hosts leave to custom game menu.
// v7.2: Added Global Player History so Player Card stats track games even when you are not host.
// v7.3: Added packet-aware career stats, Classic Quickplay/custom stat splits, cleaner Return To Lobby packet handling, and reliability cleanup.
// v7.4: Added Player Database client foundation, My Profile, player lookup, account linking, XP/level progress, and profile side panel.
// v7.7: Stability rollback from v7.5/v7.6 experimental patch, rebranded title, and safe close/reopen client button.
// v7.8: Refined stat categories: Custom Rooms renamed to Free For All, Team stats only count team mode, 1v1 only counts two active players, and FFA only counts 3+ active non-team players.
// v7.9: Auto-saves and refreshes your public Bonk level/XP from visible game/profile data so My Profile no longer needs repeated manual XP input.
// v8.0: Added exact local XP progress tracking so XP keeps saved round-win progress above the public level floor instead of resetting to base level XP.
// v8.1: Added XP cap awareness: XP CAPPED Yes/No, cooldown timer, local 20-minute/daily cap tracking, and XP gain blocking while capped.
// v8.2: Added Saved Room Watch, Bonk Commands-compatible private chat foundation, Social menu, and cleaner client menu layout/formatting.
// v8.3: Reorganized client categories, cleaned menu formatting, added command shortcut buttons, and added player-menu private chat.
// v8.4: Expanded Player Database with tags, notes, favorites, rival/team chemistry tools, room-title tracking, fairer team shuffle, styled info popups, and fixed client reopen.
// v8.5: Docked popup-only display, removed duplicated chat/top-panel info, added accordion cleanup, corrected stat total display, improved close/reopen behavior, and tightened UI spacing.
// v8.6: Converted category dropdowns into docked popup menus, reorganized/merged UI categories, reduced redundancy, and kept Main Menu as the only dropdown.
// v8.9: Rebuilt Match/Room into one Match Settings popup, refined team shuffle, added cleaner toggles, relabeled legacy buttons, and hardened popup routing.
// v9.0: Removed Room category, merged room controls into Lobby Controls, hid old dropdown routes, standardized popup card styling, fixed popup width, and cleaned legacy menu labels.
// v9.0.1: Kept the stable v9.0 base, renamed visible Match to Lobby Controls, and removed the visible Room category without touching legacy internals.
// v9.0.2: Updated Maps / Playlists with Quickplay ON/OFF, map-only Smart Shuffle with info, current playlist selector, and playlist create/add/remove buttons.
// v9.0.3: Added version authority lock to stop old layered patches from flickering the displayed version.
// v9.0.4: Expanded the lock into a label authority guard so updated category names like Maps / Playlists stop flickering.
// v9.0.5: Replaced the broad label lock with a focused Maps / Playlists label-only guard to avoid touching working UI routes.
// v9.0.7: Focused only the Room Controls Go To Lobby button into Return To Bonk Menu; Team Setup buttons and duplicate headers untouched.
// v9.0.10: Removed the broken old Profile route and rebuilt it as a standalone My Profile self-card.
// v9.0.12: Polished My Profile formatting, reset displayed best streak, renamed XP rows, and reverted the v9.0.11 Lobby Controls rewrite.
// v9.0.3: Added version authority lock so older layered patches cannot overwrite the displayed client version.
// v8.7: Polished the client into profile/database/social/room/match/settings popup pages, added cleaner cards/progress bars, moved rare tools into Advanced, and reduced redundant button clutter.
// v8.8: Fixed popup action routing, made legacy/player-stat actions render reliably inside docked popups, and restyled the full client shell to match the popup UI.
// v8.7: Polished the client into profile/database/social/room/match/settings popup pages, added cleaner cards/progress bars, moved rare tools into Advanced, and reduced redundant button clutter.
// No separate Code Injector userscript required. This waits for document.head
// before hooking alpha2s.js so Host Mod works correctly from document-start.

(function () {
    if (window.bonkHostBuiltInCodeInjectorLoaded) return;
    window.bonkHostBuiltInCodeInjectorLoaded = true;
    if (!window.bonkCodeInjectors) window.bonkCodeInjectors = [];

    const log = (msg) => console.log(`%c[Host Mod Injector] ${msg}`, "color: #06c26d");

    const installHostModInjector = () => {
        if (!document.head) {
            setTimeout(installHostModInjector, 10);
            return;
        }
        if (document.head.__bonkHostInjectorHooked) return;
        document.head.__bonkHostInjectorHooked = true;

        const _appendChild = document.head.appendChild;

        document.head.appendChild = function (...args) {
            if (args[0] && args[0]?.tagName === "SCRIPT" && args[0]?.src?.includes("alpha2s.js")) {
                const code_url = args[0].src + "?";
                args[0].removeAttribute("src");

                (async function () {
                    log("Fetching alpha2s.js...");
                    const request = await fetch(code_url).catch(error => console.error(error));

                    if (!request || !request.ok) {
                        log("Failed to fetch alpha2s.js");
                        alert("An error occurred whilst fetching game code");
                        args[0].src = code_url;
                        return _appendChild.apply(document.head, args);
                    }

                    let code = await request.text();
                    log("Patching alpha2s.js...");
                    const start_time = performance.now();

                    // Let the rest of Host Mod finish registering its injectors if alpha2s.js
                    // was requested extremely early.
                    await new Promise(resolve => setTimeout(resolve, 0));

                    let error_notified = false;
                    for (const injector of (window.bonkCodeInjectors || [])) {
                        try {
                            if (typeof injector === "function") code = injector(code);
                            else {
                                log("Injector was not a function");
                                console.log(injector);
                            }
                        } catch (error) {
                            if (!error_notified) {
                                alert("One of your Bonk.io userscripts was unable to be loaded");
                                error_notified = true;
                            }
                            console.error(error);
                        }
                    }

                    const end_time = performance.now();
                    log(`Patched alpha2s.js successfully (${(end_time - start_time).toFixed(0)}ms)`);
                    args[0].textContent = code;
                    args[0].dispatchEvent(new Event("load"));
                    return _appendChild.apply(document.head, args);
                })().catch(error => {
                    log("Failed to inject code into alpha2s.js");
                    console.error(error);
                    alert("An error occurred whilst patching game code");
                    args[0].src = code_url;
                    return _appendChild.apply(document.head, args);
                });
            } else {
                return _appendChild.apply(document.head, args);
            }
        };

        log("Built-in injector ready");
    };

    installHostModInjector();
})();

let injector = (str) => {
	let newStr = str;
window.bonkHost = {};
window.bonkHost.playerManagement = {};
window.bonkHost.freejoin = false;
window.bonkHost.bans = [];
window.bonkHost.inGame = false;
window.bonkHost.playerManagement.canBeVisible = false;
window.bonkHost.bonkCallbacks = {};
window.bonkHost.playerHistory = {};
window.bonkHost.playerSus = [];
window.bonkHost.fig = 0;
window.bonkHost.cheatDetection = false;
window.bonkHost.autoKickGuests = false;
let mapHistory = [];
let mapHistoryIndex = 0;

window.bonkCommands = window.bonkCommands.concat(["/kick", "/ak", "/mute", "/unmute", "/lock", "/unlock", "/balance", "/fav", "/unfav", "/curate", "/curateyes", "/curateno", "/roomname", "/roompass", "/clearroompass", "/hhelp", "/balanceall", "/start", "/freejoin", "/host", "/ban", "/bans", "/unban", "/scoreboard", "/resetpos"]);

if(!localStorage.getItem("bonkHost")) {
	localStorage.setItem("bonkHost", "{}");
}

let hostPlayerMenuCSS = document.createElement('style');
hostPlayerMenuCSS.innerHTML = `
#hostPlayerMenu {
    background-color: #cfd8cd;
    width: calc(35.2vw - 400px);
    min-width: 154px;
    max-width: 200px;
    height: auto;
    position: absolute;
    left: 10px;
    top: 60px;
    bottom: unset;
    border-radius: 7px;
    display: none;
    transition: ease-in-out 100ms;
    z-index: 100;
    overflow: visible;
}

#hostPlayerMenuBox {
    position: relative !important;
    top: 0;
    height: auto;
    max-height: calc(47px * 8);
    overflow-y: auto;
    overflow-x: hidden;
}

#hostPlayerMenuCollapse {
    position: absolute;
    left: 3px;
    top: 3px;
    width: 26px;
    height: 26px;
    border-radius: 2px;
    visibility: visible;
}

#hostPlayerMenuGrab {
    position: absolute;
    right: 3px;
    top: 3px;
    width: 26px;
    height: 26px;
    border-radius: 2px;
    visibility: visible;
    cursor: grab;
}

#hostPlayerMenuControls {
    position: relative;
    bottom: auto;
    width: 100%;
}

#hostPlayerCheatDetection {
    position: absolute;
    left: 0;
    top: 0;
    z-index: -1;
    background-color: #cfd8cd;
    width: inherit;
    min-width: inherit;
    max-width: inherit;
    height: inherit;
    border-radius: 7px;
    transition: ease-in-out 100ms;
    opacity: 70%;
    visibility: hidden;
}

#hostPlayerCheatDetection canvas {
    background-color: rgb(58, 58, 58);
    margin-left: 5%;
    height: 45px;
    width: 95%;
    margin-top: 1px;
    margin-bottom: 1px;
}
#selectionWheel {
    width: 150px;
    height: 150px;
    position: absolute;
    left: 588px;
    top: 181px;
    pointer-events: none;
    display: none;
    z-index: 150;
}
#selectionWheelTeams {
    width: 150px;
    height: 150px;
    position: absolute;
    left: 1061px;
    top: 169px;
    pointer-events: none;
    display: none;
    z-index: 150;
}

#hostPlayerMenuRestartButton,
#hostPlayerMenuGoToLobbyButton,
#hostPlayerMenuCollapseMenuButton {
    width: 100%;
    border-width: 0 !important;
}

#hostPlayerMenuTeamToolsToggle,
#hostPlayerMenuHostToolsToggle,
#hostPlayerMenuShuffleRBButton,
#hostPlayerMenuAutoBalanceButton,
#hostPlayerMenuSwapTeamsButton,
#hostPlayerMenuAllSpecButton,
#hostPlayerMenuTeamlock,
#hostPlayerMenuFreejoin,
#hostPlayerMenuKeepScores,
#hostPlayerMenuCheatDetectionCheckbox,
#hostPlayerMenuKeepPositions,
#hostPlayerMenuAkToggleButton,
#hostPlayerMenuPlaylistsToggle,
#hostPlayerMenuOpenPlaylistsButton,
#hostPlayerMenuQuickplayButton,
#hostPlayerMenuQpLeaderStatus,
#hostPlayerMenuQpYourWinsStatus,
#hostPlayerMenuQpRoundsButton,
#hostPlayerMenuQpShuffleButton,
#hostPlayerMenuQpPrevButton,
#hostPlayerMenuQpNextButton,
#hostPlayerMenuMapHistoryStatus,
#hostPlayerMenuSmartShuffleButton,
#hostPlayerMenuStatsToggle,
#hostPlayerMenuStatsOverview,
#hostPlayerMenuRedBlueWinrate,
#hostPlayerMenuSmartBalanceButton,
#hostPlayerMenuCaptainsButton,
#hostPlayerMenuPlayersToggle,
#hostPlayerMenuAutoHostButton,
#hostPlayerMenuSetAutoHostButton,
#hostPlayerMenuRecentJoinsStatus,
#hostPlayerMenuRejoinProtectionButton,
#hostPlayerMenuPlayerNotesButton,
#hostPlayerMenuPlayerCardButton,
#hostPlayerMenuMyCareerStatsButton,
#hostPlayerMenuHelpToggle,
#hostPlayerMenuHelpWelcome,
#hostPlayerMenuHelpTeamTools,
#hostPlayerMenuHelpHostTools,
#hostPlayerMenuHelpPlaylists,
#hostPlayerMenuHelpStats,
#hostPlayerMenuHelpPlayers,
#hostPlayerMenuHelpSettings,
#hostPlayerMenuPatchNotesToggle,
#hostPlayerMenuPatchNotesSummary,
#hostPlayerMenuPatchNotesV55,
#hostPlayerMenuPatchNotesV54,
#hostPlayerMenuPatchNotesV53,
#hostPlayerMenuPatchNotesQuickplay,
#hostPlayerMenuPatchNotesTools,
#hostPlayerMenuPatchNotesStats,
#hostPlayerMenuPatchNotesPlayers {

    width: 100%;
    border-width: 0 !important;
}

#hostPlayerMenuControls .newbonklobby_settings_button {
    min-height: 29px;
    height: 29px;
    line-height: 29px;
}

#hostPlayerMenuTeamToolsDropdown,
#hostPlayerMenuHostToolsDropdown,
#hostPlayerMenuPlaylistsDropdown,
#hostPlayerMenuStatsDropdown,
#hostPlayerMenuPlayersDropdown,
#hostPlayerMenuPatchNotesDropdown,
#hostPlayerMenuHelpDropdown {
    display: none;
    width: 100%;
}

#hostPlayerMenuTeamToolsDropdown .hostPlayerMenuDropdownItem,
#hostPlayerMenuHostToolsDropdown .hostPlayerMenuDropdownItem,
#hostPlayerMenuPlaylistsDropdown .hostPlayerMenuDropdownItem,
#hostPlayerMenuStatsDropdown .hostPlayerMenuDropdownItem,
#hostPlayerMenuPlayersDropdown .hostPlayerMenuDropdownItem,
#hostPlayerMenuSettingsDropdown .hostPlayerMenuDropdownItem,
#hostPlayerMenuPatchNotesDropdown .hostPlayerMenuDropdownItem,
#hostPlayerMenuHelpDropdown .hostPlayerMenuDropdownItem {
    background-color: #8b6f61 !important;
    color: #ffffff !important;
    box-sizing: border-box;
    text-align: left !important;
    justify-content: flex-start;
    padding-left: 10px;
    font-size: 14px !important;
    font-weight: 600 !important;
    font-family: futurept_b1, Arial, sans-serif;
    text-transform: uppercase;
}

#hostPlayerMenuSmartBalanceButton {
    display: none !important;
}

#hostPlayerMenuTeamToolsToggle,
#hostPlayerMenuHostToolsToggle,
#hostPlayerMenuPlaylistsToggle,
#hostPlayerMenuStatsToggle,
#hostPlayerMenuPlayersToggle,
#hostPlayerMenuPatchNotesToggle,
#hostPlayerMenuHelpToggle,
#hostPlayerMenuRestartButton,
#hostPlayerMenuGoToLobbyButton,
#hostPlayerMenuCollapseMenuButton {
    background-color: #6f4f42 !important;
    color: #ffffff !important;
    text-align: center !important;
    font-size: 17px !important;
    font-weight: 700 !important;
    text-transform: uppercase;
}

#hostPlayerMenuRespawningRequiredWarning {
    display: none;
}
#hostPlayerMenuTeamToolsLabel {
    width: 100%;
    text-align: center;
    font-size: 11px;
    opacity: 0.8;
    padding: 2px 0;
}

#newbonklobby_hostprevmap {
    width: 27px;
    height: 27px;
    position: absolute;
    top: 106px;
    left: 15px;
}

#newbonklobby_hostnextmap {
    width: 27px;
    height: 27px;
    position: absolute;
    top: 106px;
    left: 48px;
}

#newbonklobby_roundsinput {
    height: 50px !important;
    text-align: center !important;
}

#hostPlayerMenuStatsDropdown {
    overflow-x: auto;
    overflow-y: hidden;
    white-space: nowrap;
}

#hostPlayerMenuStatsDropdown .hostPlayerMenuDropdownItem {
    min-width: max-content;
    padding-right: 16px;
}

#hostPlayerMenuPatchNotesDropdown .hostPlayerMenuDropdownItem {
    height: auto !important;
    min-height: 42px !important;
    line-height: 16px !important;
    white-space: normal !important;
    text-transform: none !important;
    padding-top: 6px;
    padding-bottom: 6px;
    align-items: flex-start !important;
}
/* v5.5 cleanup/settings additions */
#hostPlayerMenuSettingsToggle,
#hostPlayerMenuAutoTeamJoinButton,
#hostPlayerMenuAutoJoinNextButton,
#hostPlayerMenuGlobalHistoryButton,
#hostPlayerMenuCareerStatsButton,
#hostPlayerMenuStatusButton,
#hostPlayerMenuVersionInfoButton,
#hostPlayerMenuExportSettingsButton,
#hostPlayerMenuImportSettingsButton,
#hostPlayerMenuSettingsSmartShuffleButton,
#hostPlayerMenuSettingsRejoinProtectionButton,
#hostPlayerMenuPatchNotesCompactButton,
#hostPlayerMenuRoadmapButton,
#hostPlayerMenuShowFlagsButton,
#hostPlayerMenuHelpToggle,
#hostPlayerMenuHelpWelcome,
#hostPlayerMenuHelpTeamTools,
#hostPlayerMenuHelpHostTools,
#hostPlayerMenuHelpPlaylists,
#hostPlayerMenuHelpStats,
#hostPlayerMenuHelpPlayers,
#hostPlayerMenuHelpSettings {
    width: 100%;
    border-width: 0 !important;
}

#hostPlayerMenuSettingsDropdown {
    display: none;
    width: 100%;
}

#hostPlayerMenuSettingsDropdown .hostPlayerMenuDropdownItem {
    background-color: #8b6f61 !important;
    color: #ffffff !important;
    box-sizing: border-box;
    text-align: left !important;
    justify-content: flex-start;
    padding-left: 10px;
    font-size: 14px !important;
    font-weight: 600 !important;
    font-family: futurept_b1, Arial, sans-serif;
    text-transform: uppercase;
}

#hostPlayerMenuSettingsToggle {
    background-color: #6f4f42 !important;
    color: #ffffff !important;
    text-align: center !important;
    font-size: 17px !important;
    font-weight: 700 !important;
    text-transform: uppercase;
}

#hostPlayerMenuPatchNotesDropdown.compact .olderPatchNote {
    display: none !important;
}

#hostPlayerMenuRoadmapButton {
    height: auto !important;
    min-height: 42px !important;
    line-height: 16px !important;
    white-space: normal !important;
    text-transform: none !important;
    padding-top: 6px;
    padding-bottom: 6px;
    align-items: flex-start !important;
}

#hostPlayerMenuHelpDropdown .hostPlayerMenuDropdownItem {
    height: auto !important;
    min-height: 34px !important;
    line-height: 16px !important;
    white-space: normal !important;
    text-transform: none !important;
    padding-top: 6px;
    padding-bottom: 6px;
    align-items: flex-start !important;
}


.bonkHostFlagName[data-bonk-host-flag]::before {
    content: attr(data-bonk-host-flag) " ";
}


/* v6.0 main menu + room presets */
#hostPlayerMenuMainToggle,
#hostPlayerMenuRoomPresetsToggle,
#hostPlayerMenuPresetCurrent,
#hostPlayerMenuPresetClassic,
#hostPlayerMenuPresetWDB,
#hostPlayerMenuPresetClassicQuickplay,
#hostPlayerMenuPresetClassicFreejoin,
#hostPlayerMenuPresetSmartShuffle,
#hostPlayerMenuPresetAutoTeamJoin,
#hostPlayerMenuPresetWinsMode,
#hostPlayerMenuPresetReset {
    width: 100%;
    border-width: 0 !important;
}

#hostPlayerMenuMainDropdown,
#hostPlayerMenuRoomPresetsDropdown {
    display: none;
    width: 100%;
}

#hostPlayerMenuMainToggle,
#hostPlayerMenuRoomPresetsToggle {
    background-color: #6f4f42 !important;
    color: #ffffff !important;
    text-align: center !important;
    font-size: 17px !important;
    font-weight: 700 !important;
    text-transform: uppercase;
}

#hostPlayerMenuRoomPresetsDropdown .hostPlayerMenuDropdownItem {
    background-color: #8b6f61 !important;
    color: #ffffff !important;
    box-sizing: border-box;
    text-align: left !important;
    justify-content: flex-start;
    padding-left: 10px;
    font-size: 14px !important;
    font-weight: 600 !important;
    font-family: futurept_b1, Arial, sans-serif;
    text-transform: uppercase;
}



/* v7.7 stable client title + close/reopen */
#hostPlayerMenu .newbonklobby_boxtop {
    position: relative;
    text-align: center;
}
#hostPlayerMenuClientTitle {
    display: block;
    font-size: 16px;
    font-weight: 800;
    line-height: 18px;
    padding-top: 1px;
}
#hostPlayerMenuClientVersion {
    position: absolute;
    left: 0;
    right: 0;
    top: 22px;
    text-align: center;
    font-size: 9px;
    line-height: 9px;
    opacity: 0.85;
    pointer-events: none;
}
#hostPlayerMenuCloseButton {
    position: absolute;
    right: 31px;
    top: 3px;
    width: 26px;
    height: 26px;
    border-radius: 2px;
    visibility: visible;
    cursor: pointer;
    font-size: 16px !important;
    line-height: 26px !important;
    z-index: 3;
}
#hostPlayerMenuLauncher {
    position: absolute;
    left: 10px;
    top: 60px;
    width: 34px;
    height: 34px;
    border-radius: 7px;
    z-index: 101;
    display: none;
    text-align: center;
    line-height: 34px;
    font-family: futurept_b1, Arial, sans-serif;
    font-size: 18px;
    font-weight: 800;
}

/* v7.4 Player Database / Client Profile */
#hostPlayerMenuDatabaseToggle,
#hostPlayerMenuMyProfileButton,
#hostPlayerMenuPlayerLookupButton,
#hostPlayerMenuRecentPlayersButton,
#hostPlayerMenuTopPlayedWithButton,
#hostPlayerMenuTopWinrateButton,
#hostPlayerMenuLinkAccountsButton,
#hostPlayerMenuAccountLinksButton,
#hostPlayerMenuDatabaseHealthButton,
#hostPlayerMenuSetExactXpButton,
#hostPlayerMenuXpCapButton {
    width: 100%;
    border-width: 0 !important;
}

#hostPlayerMenuDatabaseDropdown {
    display: none;
    width: 100%;
}

#hostPlayerMenuDatabaseToggle {
    background-color: #6f4f42 !important;
    color: #ffffff !important;
    text-align: center !important;
    font-size: 17px !important;
    font-weight: 700 !important;
    text-transform: uppercase;
}

#hostPlayerMenuDatabaseDropdown .hostPlayerMenuDropdownItem {
    background-color: #8b6f61 !important;
    color: #ffffff !important;
    box-sizing: border-box;
    text-align: left !important;
    justify-content: flex-start;
    padding-left: 10px;
    font-size: 14px !important;
    font-weight: 600 !important;
    font-family: futurept_b1, Arial, sans-serif;
    text-transform: uppercase;
}

#hostPlayerMenuDatabasePanel {
    display: none;
    background-color: #2f2927;
    color: #fff;
    font-family: futurept_b1, Arial, sans-serif;
    font-size: 12px;
    line-height: 15px;
    max-height: 260px;
    overflow-y: auto;
    overflow-x: hidden;
    padding: 8px;
    box-sizing: border-box;
    border-top: 2px solid #6f4f42;
    white-space: pre-wrap;
}

#hostPlayerMenuDatabasePanelTitle {
    font-size: 15px;
    font-weight: 800;
    margin-bottom: 6px;
    text-transform: uppercase;
}

#hostPlayerMenuDatabasePanelBody {
    font-size: 12px;
    font-weight: 600;
    text-transform: none;
}

`;
document.getElementsByTagName('head')[0].appendChild(hostPlayerMenuCSS);

let hostPlayerMenu = document.createElement('div');
document.getElementById('pagecontainer').appendChild(hostPlayerMenu);
hostPlayerMenu.outerHTML = `
<div class="windowShadow newbonklobby_elementcontainer" id="hostPlayerMenu">
	<div class="newbonklobby_boxtop newbonklobby_boxtop_classic" style="background-color: #009688;">
		<div onclick="window.bonkHost.playerManagement.collapse();" id="hostPlayerMenuCollapse" class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow">-</div>
		<span id="hostPlayerMenuClientTitle">Trapi's Client</span><div id="hostPlayerMenuClientVersion">v9.0.12</div>
		<div onclick="window.bonkHost.closeClient && window.bonkHost.closeClient();" id="hostPlayerMenuCloseButton" class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow">×</div>
		<div id="hostPlayerMenuGrab" class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow">:::</div>
	</div>
	<div id="hostPlayerMenuBox" class="newbonklobby_elementcontainer"></div>
	<div id="hostPlayerMenuDatabasePanel">
		<div id="hostPlayerMenuDatabasePanelTitle">PLAYER DATABASE</div>
		<div id="hostPlayerMenuDatabasePanelBody">Open My Profile or Player Lookup to view stats.</div>
	</div>
	<div id="hostPlayerMenuControls">
		<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow" id="hostPlayerMenuTeamToolsToggle">
			TEAM TOOLS ▼
		</div>
		<div id="hostPlayerMenuTeamToolsDropdown">
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuShuffleRBButton">
				SHUFFLE R/B
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuAutoBalanceButton">
				AUTO BALANCE
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuSwapTeamsButton">
				SWAP RED/BLUE
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuAllSpecButton">
				ALL SPEC
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuCaptainsButton">
				CAPTAINS MODE: OFF
			</div>
		</div>
		<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow" id="hostPlayerMenuHostToolsToggle">
			HOST TOOLS ▼
		</div>
		<div id="hostPlayerMenuHostToolsDropdown">
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuTeamlock">
				TEAMLOCK: OFF
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuFreejoin">
				FREEJOIN: OFF
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuKeepScores">
				KEEP SCORES: OFF
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuCheatDetectionCheckbox">
				CHEAT DETECTION: OFF
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuKeepPositions">
				KEEP POSITIONS: OFF
				<span id="hostPlayerMenuRespawningRequiredWarning">RESPAWNING REQUIRED</span>
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuAkToggleButton">
				GUEST AK: OFF
			</div>
		</div>
		<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow" id="hostPlayerMenuPlaylistsToggle">
			PLAYLISTS ▼
		</div>
		<div id="hostPlayerMenuPlaylistsDropdown">
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuOpenPlaylistsButton">
				OPEN PLAYLISTS
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuQuickplayButton">
				QUICKPLAY: OFF
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuQpLeaderStatus">
				LEADER: NONE (0/3)
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuQpYourWinsStatus">
				YOU: 0/3
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuQpRoundsButton">
				WINS/QP: 3
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuQpShuffleButton">
				SHUFFLE QUEUE
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuMapHistoryStatus">
				MAP HISTORY: EMPTY
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuQpPrevButton">
				PREVIOUS MAP
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuQpNextButton">
				NEXT MAP
			</div>
		</div>
		<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow" id="hostPlayerMenuStatsToggle">
			MATCH STATS ▼
		</div>
		<div id="hostPlayerMenuStatsDropdown">
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuStatsOverview">
				STATS: 0 GAMES
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuRedBlueWinrate">
				RED WR: 0% | BLUE WR: 0%
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuSmartBalanceButton">
				BALANCE BY WINRATE
			</div>
		</div>
		<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow" id="hostPlayerMenuPlayersToggle">
			PLAYER TOOLS ▼
		</div>
		<div id="hostPlayerMenuPlayersDropdown">
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuAutoHostButton">
				AUTO HOST: OFF
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuSetAutoHostButton">
				SET AUTO HOST
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuRecentJoinsStatus">
				RECENT JOINS: NONE
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuPlayerNotesButton">
				PLAYER NOTES
			</div>

		</div>
		<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow" id="hostPlayerMenuDatabaseToggle">
			PLAYER DATABASE ▼
		</div>
		<div id="hostPlayerMenuDatabaseDropdown">
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuMyProfileButton">
				MY PROFILE
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuSetExactXpButton">
				SET EXACT XP
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuXpCapButton">
				XP CAP STATUS
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuPlayerLookupButton">
				PLAYER LOOKUP
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuRecentPlayersButton">
				RECENT PLAYERS
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuTopPlayedWithButton">
				TOP PLAYED WITH
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuTopWinrateButton">
				TOP WINRATE
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuLinkAccountsButton">
				LINK ACCOUNTS
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuAccountLinksButton">
				ACCOUNT LINKS
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuDatabaseHealthButton">
				DATABASE HEALTH
			</div>
		</div>
		<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow" id="hostPlayerMenuSettingsToggle">
			SETTINGS ▼
		</div>
		<div id="hostPlayerMenuSettingsDropdown">
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuAutoTeamJoinButton">
				AUTO TEAM JOIN: ON
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuAutoJoinNextButton">
				AUTO JOIN NEXT: ON
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuGlobalHistoryButton">
				GLOBAL PLAYER HISTORY: ON
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuCareerStatsButton">
				CAREER STATS: ON
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuStatusButton">
				HOST MOD STATUS
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuVersionInfoButton">
				VERSION INFO
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuExportSettingsButton">
				EXPORT SETTINGS
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuImportSettingsButton">
				IMPORT SETTINGS
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuShowFlagsButton">
				COUNTRY FLAGS: AUTO
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuSettingsSmartShuffleButton">
				SMART SHUFFLE: OFF
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuSettingsRejoinProtectionButton">
				REJOIN PROTECTION: OFF
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuPatchNotesCompactButton">
				PATCH NOTES: COMPACT
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuRoadmapButton">
				NEXT PORTS: QUEUE SYSTEM, POLLS, SAVED ROOMS, SESSION LEADERBOARD, TRUSTED PLAYERS
			</div>
		</div>
		<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow" id="hostPlayerMenuHelpToggle">
			HELP ▼
		</div>
		<div id="hostPlayerMenuHelpDropdown">
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuHelpWelcome">
				WELCOME / BASIC HELP
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuHelpTeamTools">
				TEAM TOOLS HELP
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuHelpHostTools">
				HOST TOOLS HELP
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuHelpPlaylists">
				PLAYLISTS HELP
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuHelpStats">
				MATCH STATS HELP
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuHelpPlayers">
				PLAYER TOOLS HELP
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuHelpSettings">
				SETTINGS HELP
			</div>
		</div>
		<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow" id="hostPlayerMenuPatchNotesToggle">
			PATCH NOTES ▼
		</div>
		<div id="hostPlayerMenuPatchNotesDropdown" class="compact">
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuPatchNotesSummary">
				v8.5: Popups now dock beside the client and act as the only info display, duplicated chat/top-panel output was removed, categories auto-collapse for space, reopen was hardened, and stat totals display with corrected bucket math. v8.4 expanded Player Database.
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuPatchNotesV59">
				v5.9: Country flags now display automatically when Bonk provides country data. v5.8 added flag badge support.
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuPatchNotesV57">
				v5.7: Fixed dropdown menus not opening by repairing the menu item CSS and dropdown open/close detection.
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuPatchNotesV56">
				v5.6: Added Help menu. Click any help row to show simple system messages explaining what each Host Mod section does.
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem" id="hostPlayerMenuPatchNotesV55">
				v5.5: Added Settings menu, moved Rejoin Protection and Smart Shuffle into Settings, moved Captains Mode into Team Tools, and added future Bonk Commands port reminders.
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem olderPatchNote" id="hostPlayerMenuPatchNotesV54">
				v5.4: Added hidden Team Join Balance. In team modes, new players are automatically placed on Red or Blue based on which side needs a player.
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem olderPatchNote" id="hostPlayerMenuPatchNotesV53">
				v5.3: Team shuffle now uses session winrate, match stats can scroll sideways, and rejoin protection announces reserved players when the room fills.
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem olderPatchNote" id="hostPlayerMenuPatchNotesQuickplay">
				v5.1-v5.2: Quickplay gained leader tracking, your win count, smart shuffle, map history, player tools, and match stats.
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem olderPatchNote" id="hostPlayerMenuPatchNotesTools">
				v4.9-v5.0: Quickplay started tracking actual map wins instead of raw completed rounds and added a cleaner leader display.
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem olderPatchNote" id="hostPlayerMenuPatchNotesStats">
				v4.4-v4.8: Playlists were added, Quickplay became smoother, and the menu text/header styling was polished.
			</div>
			<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem olderPatchNote" id="hostPlayerMenuPatchNotesPlayers">
				v4.3 and earlier: Host Tools and Team Tools were reorganized into consistent dropdown rows with ON/OFF status labels.
			</div>
		</div>
		<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow" id="hostPlayerMenuRestartButton">
			RESTART GAME
		</div>
	</div>
	<div class="windowShadow" id="hostPlayerCheatDetection">
	<div class="newbonklobby_boxtop newbonklobby_boxtop_classic" style="background-color: #009688;"></div>
		<div id="hostPlayerMenuCheatBox" class="newbonklobby_elementcontainer">
			 <canvas width="1" height="45" class="mapeditor_rightbox_table"></canvas>
			 <canvas width="1" height="45" class="mapeditor_rightbox_table"></canvas>
			 <canvas width="1" height="45" class="mapeditor_rightbox_table"></canvas>
			 <canvas width="1" height="45" class="mapeditor_rightbox_table"></canvas>
			 <canvas width="1" height="45" class="mapeditor_rightbox_table"></canvas>
			 <canvas width="1" height="45" class="mapeditor_rightbox_table"></canvas>
			 <canvas width="1" height="45" class="mapeditor_rightbox_table"></canvas>
			 <canvas width="1" height="45" class="mapeditor_rightbox_table"></canvas>
		</div>
		<div class="mapeditor_rightbox_table" style="position:absolute;bottom:0;width:95%;margin-left:5%;text-align:justify;font-size:15;height:170px;overflow-y:scroll;">
INSTRUCTIONS: The graph shows how laggy someone is compared to how laggy they actually should be. This can be used to detect lag cheats. Being over the first red line is OK. Consistently being under it can happen but it's suspicious. Anything consistent under that is usually just cheats. Random lag spikes can happen. ⚠️ indicates that the player is using a mod that is known to have related cheats.
		</div>
	</div>
</div>
<div id="selectionWheel">
	<svg width="150" height="150" viewBox="0 0 26.458 26.458"><g><path d="M22.009-13.227a8.78 8.78 0 0 1-13.17 7.605 8.78 8.78 0 0 1-4.39-7.605" transform="rotate(90)" stroke="#fff" style="stroke-linejoin: round; stroke-width: 9; fill: none; opacity: 0.5; stroke: rgb(121, 85, 72);"></path><text class="brownButton_classic" style="
		text-anchor: middle;
		font-family: 'futurept_b1';
		font-size: 3.17496;
		transform: scale(1, 1);
		fill: #fff;
	"><tspan x="4.5" y="14.4">FFA</tspan></text>
	</g><g style="transform: translateX(50%);transform-origin: 100% 0px;"><path d="M22.009-13.227a8.78 8.78 0 0 1-13.17 7.605 8.78 8.78 0 0 1-4.39-7.605" transform="rotate(90)" stroke="#fff" style="stroke-linejoin: round; stroke-width: 9; fill: none; opacity: 0.5; transform: rotate(-90deg); transform-origin: 75% 25%; stroke: rgb(121, 85, 72);"></path><text style="
		text-anchor: middle;
		font-family: 'futurept_b1';
		font-size: 3.17496;
		transform: scale(1, 1);
		fill: #fff;
	"><tspan x="8.8" y="14.4">SPEC</tspan></text></g></svg>
</div>

<div id="selectionWheelTeams">
	<svg width="150" height="150" viewBox="0 0 26.458 26.458" style="
"><g stroke-linejoin="round" stroke-width="9" stroke="#fff" style="transform: rotate(180deg);transform-origin: 50% 0%;"><path d="M6.126-8.066c-2.236-3.078-2.236-7.246 0-10.323" style="opacity: 0.5; stroke: rgb(121, 85, 72);"></path><path d="M6.126-18.39a8.78 8.78 0 0 1 9.816-3.19" style="stroke: rgb(255, 235, 59); opacity: 1;"></path><path d="M15.942-21.58a8.78 8.78 0 0 1 6.067 8.352" style="stroke: rgb(76, 175, 80); opacity: 0.5;"></path><path d="M22.009-13.228a8.78 8.78 0 0 1-6.067 8.352" style="stroke: rgb(33, 150, 243); opacity: 0.5;"></path><path d="M15.942-4.876a8.78 8.78 0 0 1-9.816-3.19" style="stroke: rgb(244, 67, 54); opacity: 0.5;"></path></g><text font-size="6" fill="#000" style="
		text-anchor: middle;
		font-family: 'futurept_b1';
		font-size: 3.17496;
		fill: #fff;
	"><tspan x="22" y="14.4">SPEC</tspan></text><text font-size="6" fill="#000" style="
		text-anchor: middle;
		font-family: 'futurept_b1';
		font-size: 3.17496;
		fill: #fff;
	"><tspan x="16.75" y="22.5">YELLOW</tspan></text><text font-size="6" fill="#000" style="
		text-anchor: middle;
		font-family: 'futurept_b1';
		font-size: 3.17496;
		fill: #fff;
	"><tspan x="16.5" y="6">RED</tspan></text><text font-size="6" fill="#000" style="
		text-anchor: middle;
		font-family: 'futurept_b1';
		font-size: 3.17496;
		fill: #fff;
	"><tspan x="6" y="9.5">BLUE</tspan></text><text font-size="6" fill="#000" style="
		text-anchor: middle;
		font-family: 'futurept_b1';
		font-size: 3.17496;
		fill: #fff;
	"><tspan x="6" y="19.5">GREEN</tspan></text></svg>
</div>
<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow" id="newbonklobby_hostprevmap">&lt;</div>
<div class="newbonklobby_settings_button brownButton brownButton_classic buttonShadow" id="newbonklobby_hostnextmap">&gt;</div>
<!--
<div id="teamshufflebutton" class="brownButton brownButton_classic buttonShadow" style="
    width: 16px;
    height: 35px;
    border-radius: 2px;
    position: absolute;
    top: calc(17% - 40px);
    right: calc(33.5% - 8px);
    font-family: monospace;
	">
	<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16" style="height: 100%;transform:scaleX(-0.9);">
		<path fill-rule="evenodd" d="M0 3.5A.5.5 0 0 1 .5 3H1c2.202 0 3.827 1.24 4.874 2.418.49.552.865 1.102 1.126 1.532.26-.43.636-.98 1.126-1.532C9.173 4.24 10.798 3 13 3v1c-1.798 0-3.173 1.01-4.126 2.082A9.624 9.624 0 0 0 7.556 8a9.624 9.624 0 0 0 1.317 1.918C9.828 10.99 11.204 12 13 12v1c-2.202 0-3.827-1.24-4.874-2.418A10.595 10.595 0 0 1 7 9.05c-.26.43-.636.98-1.126 1.532C4.827 11.76 3.202 13 1 13H.5a.5.5 0 0 1 0-1H1c1.798 0 3.173-1.01 4.126-2.082A9.624 9.624 0 0 0 6.444 8a9.624 9.624 0 0 0-1.317-1.918C4.172 5.01 2.796 4 1 4H.5a.5.5 0 0 1-.5-.5z"></path>
		<path d="M13 5.466V1.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384l-2.36 1.966a.25.25 0 0 1-.41-.192zm0 9v-3.932a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384l-2.36 1.966a.25.25 0 0 1-.41-.192z"></path>
	</svg>
</div>
<div id="teamshufflebox" class="newbonklobby_elementcontainer" style="
    position: absolute;
    top: calc(17% - 42px);
    right: calc(33.5% - 10px);
    width: auto;
    height: auto;
    bottom: unset;
    outline: 3000px solid rgba(0,0,0,0.30);
	visibility: hidden;
">
	<div class="newbonklobby_boxtop newbonklobby_boxtop_classic" style="
	    height: 39px;
	    line-height: 39px;
	">
		Shuffle Teams
		<div onclick=window.bonkHost.shuffleTeams class="newbonklobby_votewindow_votebutton brownButton brownButton_classic buttonShadow" id="newbonklobby_votewindow_votebutton_up" style="
    		margin: 0;
    		height: 35px;
    		width: 25px;
    		position: absolute;
    		left: calc(100% - 27px);
    		top: 2px;
		"></div>
		<div onclick="()=>{document.getElementById('teamshufflebox').style.visibility='hidden';}" class="newbonklobby_votewindow_votebutton brownButton brownButton_classic buttonShadow" id="newbonklobby_votewindow_votebutton_down" style="
    		margin: 0;
    		height: 35px;
    		width: 25px;
    		position: absolute;
    		top: 2px;
    		left: calc(100% - 55px);
		"></div>
	</div>
	<table id="mapeditor_rightbox_table_simple" class="mapeditor_rightbox_table">
		<tbody>
		<tr>
			<td class="mapeditor_rightbox_table_leftcell">Players per team</td>
			<td class="mapeditor_rightbox_table_rightcell">
					<div class="mapeditor_field_button fieldShadow" style="width:auto;margin:0 3;" onclick="() => {
						document.getElementById('teamshufflebox').value = 'AUTO';
					}">AUTO</div>
				<div class="mapeditor_field_button fieldShadow" style="margin:0 1;">-</div>
				<input id="teamshuffleppt" class="mapeditor_field fieldShadow" value="1" autocomplete="off" style="width:44px;">
					<div class="mapeditor_field_button fieldShadow" style="margin 0 1;" onclick="() => {
						document.getElementById('teamshuffleppt').value = parseInt(document.getElementById('teamshuffleppt').value) + 1;
					}">+</div>
			</td>
		</tr>
		<tr>
			<td class="mapeditor_rightbox_table_leftcell">Teams</td>
			<td class="mapeditor_rightbox_table_rightcell" style="
				display: grid;
				align-content: center;
				justify-content: center;
				align-items: center;
				grid-template-columns: 20px 25px 20px 35px;
			">
				<input type="checkbox" id="team_shuffle_blue"><label>Blue</label>
				<input type="checkbox" id="team_shuffle_green"><label>Green</label>
				<input type="checkbox" id="team_shuffle_red"><label>Red</label>
				<input type="checkbox" id="team_shuffle_yellow"><label>Yellow</label>
			</td>
		</tr>
			<tr>
				<td class="mapeditor_rightbox_table_leftcell">Autoshuffle</td>
				<td class="mapeditor_rightbox_table_rightcell">
					<input type="checkbox" id="mapeditor_rightbox_table_bullet">
				</td>
			</tr>
		</tbody>
	</table>
</div>
`;

// v7.7: Safe close/reopen control for the client panel.
window.bonkHost.clientClosed = localStorage.getItem("trapisClientClosed") === "true";
window.bonkHost.ensureClientLauncher = () => {
    let launcher = document.getElementById("hostPlayerMenuLauncher");
    if(!launcher) {
        launcher = document.createElement("div");
        launcher.id = "hostPlayerMenuLauncher";
        launcher.className = "newbonklobby_settings_button brownButton brownButton_classic buttonShadow";
        launcher.textContent = "T";
        launcher.title = "Open Trapi's Client";
        launcher.addEventListener("click", () => window.bonkHost.openClient && window.bonkHost.openClient());
        let parent = document.getElementById("pagecontainer") || document.body || document.documentElement;
        if(parent) parent.appendChild(launcher);
    }
    return launcher;
};
window.bonkHost.closeClient = () => {
    try {
        window.bonkHost.clientClosed = true;
        localStorage.setItem("trapisClientClosed", "true");
        let menu = document.getElementById("hostPlayerMenu");
        let launcher = window.bonkHost.ensureClientLauncher();
        if(menu) menu.style.display = "none";
        if(launcher) launcher.style.display = "block";
    } catch(e) { console.warn("Trapi's Client close failed", e); }
};
window.bonkHost.openClient = () => {
    try {
        window.bonkHost.clientClosed = false;
        localStorage.setItem("trapisClientClosed", "false");
        let menu = document.getElementById("hostPlayerMenu");
        let launcher = window.bonkHost.ensureClientLauncher();
        if(menu) menu.style.display = "";
        if(launcher) launcher.style.display = "none";
    } catch(e) { console.warn("Trapi's Client open failed", e); }
};
window.bonkHost.ensureClientLauncher();
if(window.bonkHost.clientClosed) {
    let menu = document.getElementById("hostPlayerMenu");
    let launcher = document.getElementById("hostPlayerMenuLauncher");
    if(menu) menu.style.display = "none";
    if(launcher) launcher.style.display = "block";
}

document.getElementById("hostPlayerMenuBox").addEventListener("click", (e) => {
	if(e.target === document.getElementById("hostPlayerMenuBox")) {
		document.getElementById('newbonklobby').click();
	}
});

["hostPlayerMenuTeamlock", "hostPlayerMenuFreejoin", "hostPlayerMenuKeepScores", "hostPlayerMenuCheatDetectionCheckbox", "hostPlayerMenuKeepPositions"].forEach(id => {
	let el = document.getElementById(id);
	if(el) el.checked = false;
});


document.getElementById("hostPlayerMenuRestartButton").addEventListener("click", () => {
	window.bonkHost.startGame();
});


// v6.3: Main Menu wrapper only. Room Presets removed for a cleaner one-feature-at-a-time workflow.
window.bonkHost.initMainMenu = () => {
	let controls = document.getElementById("hostPlayerMenuControls");
	if(!controls || document.getElementById("hostPlayerMenuMainToggle")) return;

	let restartButton = document.getElementById("hostPlayerMenuRestartButton");

	let mainToggle = document.createElement("div");
	mainToggle.className = "newbonklobby_settings_button brownButton brownButton_classic buttonShadow";
	mainToggle.id = "hostPlayerMenuMainToggle";
	mainToggle.textContent = "MAIN MENU ▼";

	let mainDropdown = document.createElement("div");
	mainDropdown.id = "hostPlayerMenuMainDropdown";

	while(controls.firstChild) mainDropdown.appendChild(controls.firstChild);
	controls.appendChild(mainToggle);
	controls.appendChild(mainDropdown);

	// v6.5/v6.9: Restart Game stays outside Main Menu, but hides while Main Menu is open.
	if(restartButton) controls.appendChild(restartButton);

	let goToLobbyButton = document.createElement("div");
	goToLobbyButton.className = "newbonklobby_settings_button brownButton brownButton_classic buttonShadow";
	goToLobbyButton.id = "hostPlayerMenuGoToLobbyButton";
	goToLobbyButton.textContent = "GO TO LOBBY";
	goToLobbyButton.style.display = "none";
	controls.appendChild(goToLobbyButton);

	let collapseMenuButton = document.createElement("div");
	collapseMenuButton.className = "newbonklobby_settings_button brownButton brownButton_classic buttonShadow";
	collapseMenuButton.id = "hostPlayerMenuCollapseMenuButton";
	collapseMenuButton.textContent = "COLLAPSE MENU";
	collapseMenuButton.style.display = "none";
	controls.appendChild(collapseMenuButton);

	window.bonkHost.updateMainMenuActionVisibility = () => {
		let collapseBtn = document.getElementById("hostPlayerMenuCollapseMenuButton");
		let restartBtn = document.getElementById("hostPlayerMenuRestartButton");
		let lobbyBtn = document.getElementById("hostPlayerMenuGoToLobbyButton");
		let main = document.getElementById("hostPlayerMenuMainDropdown");
		let mainOpen = !!main && window.getComputedStyle(main).display !== "none";
		let gameVisible = !!document.getElementById("gamerenderer") && document.getElementById("gamerenderer").style.visibility !== "hidden";

		if(collapseBtn) collapseBtn.style.display = mainOpen ? "block" : "none";
		if(restartBtn) restartBtn.style.display = mainOpen ? "none" : "block";
		if(lobbyBtn) lobbyBtn.style.display = (!mainOpen && gameVisible) ? "block" : "none";
	};

	// Backwards-compatible alias used by older menu code.
	window.bonkHost.updateCollapseMenuButtonVisibility = window.bonkHost.updateMainMenuActionVisibility;

	window.bonkHost.collapseAllHostMenuTabs = () => {
		const pairs = [
			["hostPlayerMenuTeamToolsDropdown", "hostPlayerMenuTeamToolsToggle", "TEAM TOOLS ▼"],
			["hostPlayerMenuHostToolsDropdown", "hostPlayerMenuHostToolsToggle", "HOST TOOLS ▼"],
			["hostPlayerMenuPlaylistsDropdown", "hostPlayerMenuPlaylistsToggle", "PLAYLISTS ▼"],
			["hostPlayerMenuStatsDropdown", "hostPlayerMenuStatsToggle", "MATCH STATS ▼"],
			["hostPlayerMenuPlayersDropdown", "hostPlayerMenuPlayersToggle", "PLAYER TOOLS ▼"],
			["hostPlayerMenuDatabaseDropdown", "hostPlayerMenuDatabaseToggle", "PLAYER DATABASE ▼"],
			["hostPlayerMenuSettingsDropdown", "hostPlayerMenuSettingsToggle", "SETTINGS ▼"],
			["hostPlayerMenuHelpDropdown", "hostPlayerMenuHelpToggle", "HELP ▼"],
			["hostPlayerMenuPatchNotesDropdown", "hostPlayerMenuPatchNotesToggle", "PATCH NOTES ▼"]
		];
		for(let [dropdownId, toggleId, closedText] of pairs) {
			let dropdown = document.getElementById(dropdownId);
			let toggle = document.getElementById(toggleId);
			if(dropdown) dropdown.style.display = "none";
			if(toggle) toggle.textContent = closedText;
		}
		let main = document.getElementById("hostPlayerMenuMainDropdown");
		let mainToggleEl = document.getElementById("hostPlayerMenuMainToggle");
		if(main) main.style.display = "none";
		if(mainToggleEl) mainToggleEl.textContent = "MAIN MENU ▼";
		window.bonkHost.updateCollapseMenuButtonVisibility && window.bonkHost.updateCollapseMenuButtonVisibility();
		window.bonkHost.updateHostPlayerMenuDropdownHeight && window.bonkHost.updateHostPlayerMenuDropdownHeight();
	};

	collapseMenuButton.addEventListener("click", () => {
		window.bonkHost.collapseAllHostMenuTabs && window.bonkHost.collapseAllHostMenuTabs();
	});

	goToLobbyButton.addEventListener("click", () => {
		window.bonkHost.goToLobby && window.bonkHost.goToLobby();
	});

	mainToggle.addEventListener("click", () => {
		let open = window.getComputedStyle(mainDropdown).display !== "none";
		mainDropdown.style.display = open ? "none" : "block";
		mainToggle.textContent = open ? "MAIN MENU ▼" : "MAIN MENU ▲";
		window.bonkHost.updateCollapseMenuButtonVisibility && window.bonkHost.updateCollapseMenuButtonVisibility();
		window.bonkHost.updateHostPlayerMenuDropdownHeight && window.bonkHost.updateHostPlayerMenuDropdownHeight();
	});
	window.bonkHost.updateCollapseMenuButtonVisibility && window.bonkHost.updateCollapseMenuButtonVisibility();
};
window.bonkHost.initMainMenu();

// v7.1: Context-aware Go To Lobby.
// Host: returns to your room lobby.
// Non-host: leaves the room and returns to the custom game menu.
window.bonkHost.isCurrentHost = () => {
	try {
		return !!(window.bonkHost.toolFunctions &&
			window.bonkHost.toolFunctions.networkEngine &&
			window.bonkHost.toolFunctions.networkEngine.getLSID &&
			window.bonkHost.toolFunctions.networkEngine.getLSID() === window.bonkHost.toolFunctions.networkEngine.hostID);
	}
	catch(e) {
		return false;
	}
};

window.bonkHost.clickVisibleHostModButton = (selectors, textMatches = []) => {
	let candidates = [];
	for(let selector of selectors) {
		try { candidates = candidates.concat([...document.querySelectorAll(selector)]); }
		catch(e) {}
	}
	for(let el of candidates) {
		if(!el) continue;
		let style = window.getComputedStyle(el);
		let visible = style.display !== "none" && style.visibility !== "hidden" && el.offsetParent !== null;
		if(!visible) continue;
		let txt = (el.textContent || "").trim().toLowerCase();
		if(!textMatches.length || textMatches.some(t => txt === t || txt.includes(t))) {
			try { el.click(); return true; } catch(e) {}
		}
	}
	return false;
};

window.bonkHost.confirmVisibleLeaveWindow = () => {
	let windows = ["leaveconfirmwindow", "hostleaveconfirmwindow", "prettywindow", "genericdialog"];
	for(let id of windows) {
		let win = document.getElementById(id);
		if(!win) continue;
		let style = window.getComputedStyle(win);
		let visible = style.display !== "none" && style.visibility !== "hidden";
		if(!visible) continue;
		let buttons = [...win.querySelectorAll(".brownButton, .newbonklobby_settings_button, div, button")];
		let confirmBtn = buttons.find(b => {
			let txt = (b.textContent || "").trim().toLowerCase();
			return ["yes", "ok", "leave", "exit", "confirm"].includes(txt) || txt.includes("leave") || txt.includes("exit");
		});
		if(confirmBtn) {
			try { confirmBtn.click(); return true; } catch(e) {}
		}
	}
	return false;
};

window.bonkHost.leaveRoomToCustomMenu = () => {
	// Try common lobby leave/exit buttons first. Bonk's IDs change sometimes, so this is intentionally broad.
	let clicked = window.bonkHost.clickVisibleHostModButton(
		[
			"#newbonklobby .brownButton",
			"#newbonklobby .newbonklobby_settings_button",
			"#newbonklobby div",
			".newbonklobby_elementcontainer .brownButton",
			".newbonklobby_elementcontainer .newbonklobby_settings_button"
		],
		["leave", "exit", "back", "custom games", "custom game menu", "return"]
	);
	if(clicked) {
		setTimeout(() => window.bonkHost.confirmVisibleLeaveWindow && window.bonkHost.confirmVisibleLeaveWindow(), 150);
		return true;
	}

	// Fallback: click top exit again if Bonk exposes it after returning to lobby.
	let exitBtn = document.getElementById("pretty_top_exit");
	if(exitBtn) {
		try {
			exitBtn.click();
			setTimeout(() => window.bonkHost.confirmVisibleLeaveWindow && window.bonkHost.confirmVisibleLeaveWindow(), 150);
			return true;
		} catch(e) {}
	}
	return false;
};

window.bonkHost.sendReturnToLobbyPacket = () => {
	try {
		let sock = window.bonkHost.extraFeatures && window.bonkHost.extraFeatures.activeSocket;
		if(sock && sock.readyState === WebSocket.OPEN) { sock.send("42[14]"); return true; }
	}
	catch(e) {}
	return false;
};

window.bonkHost.goToLobby = () => {
	try {
		let amHost = window.bonkHost.isCurrentHost && window.bonkHost.isCurrentHost();
		let sentPacket = window.bonkHost.sendReturnToLobbyPacket && window.bonkHost.sendReturnToLobbyPacket();
		if(!sentPacket) {
			let exitBtn = document.getElementById("pretty_top_exit");
			if(exitBtn) exitBtn.click();
			setTimeout(() => { window.bonkHost.confirmVisibleLeaveWindow && window.bonkHost.confirmVisibleLeaveWindow(); }, 120);
		}
		if(!amHost) {
			setTimeout(() => { window.bonkHost.leaveRoomToCustomMenu && window.bonkHost.leaveRoomToCustomMenu(); }, sentPacket ? 500 : 650);
			setTimeout(() => { window.bonkHost.leaveRoomToCustomMenu && window.bonkHost.leaveRoomToCustomMenu(); }, sentPacket ? 1250 : 1400);
		}
		if(window.bonkHost.menuFunctions) window.bonkHost.menuFunctions.showStatusMessage(amHost ? "* Returning to room lobby" : "* Leaving to custom game menu", "#b53030", false);
	}
	catch(e) {
		console.log("Host Mod: Go To Lobby failed", e);
	}
};

if(!window.bonkHost.goToLobbyVisibilityWatcherStarted) {
	window.bonkHost.goToLobbyVisibilityWatcherStarted = true;
	setInterval(() => {
		try { window.bonkHost.updateMainMenuActionVisibility && window.bonkHost.updateMainMenuActionVisibility(); }
		catch(e) {}
	}, 500);
}

window.bonkHost.updateHostPlayerMenuDropdownHeight = () => {
	let isOpen = (id) => {
		let el = document.getElementById(id);
		return !!el && window.getComputedStyle(el).display !== "none";
	};
	let anyOpen = isOpen("hostPlayerMenuMainDropdown") || isOpen("hostPlayerMenuTeamToolsDropdown") || isOpen("hostPlayerMenuHostToolsDropdown") || isOpen("hostPlayerMenuPlaylistsDropdown") || isOpen("hostPlayerMenuStatsDropdown") || isOpen("hostPlayerMenuPlayersDropdown") || isOpen("hostPlayerMenuDatabaseDropdown") || isOpen("hostPlayerMenuSocialDropdown") || isOpen("hostPlayerMenuSettingsDropdown") || isOpen("hostPlayerMenuHelpDropdown") || isOpen("hostPlayerMenuPatchNotesDropdown");
	document.getElementById("hostPlayerMenuBox").style.maxHeight = anyOpen ? "calc(47px * 4)" : "calc(47px * 8)";
};

document.getElementById("hostPlayerMenuTeamToolsToggle").addEventListener("click", () => {
	let dropdown = document.getElementById("hostPlayerMenuTeamToolsDropdown");
	let toggle = document.getElementById("hostPlayerMenuTeamToolsToggle");
	let open = window.getComputedStyle(dropdown).display !== "none";

	dropdown.style.display = open ? "none" : "block";
	toggle.textContent = open ? "TEAM TOOLS ▼" : "TEAM TOOLS ▲";
	window.bonkHost.updateHostPlayerMenuDropdownHeight();
});

document.getElementById("hostPlayerMenuHostToolsToggle").addEventListener("click", () => {
	let dropdown = document.getElementById("hostPlayerMenuHostToolsDropdown");
	let toggle = document.getElementById("hostPlayerMenuHostToolsToggle");
	let open = window.getComputedStyle(dropdown).display !== "none";

	dropdown.style.display = open ? "none" : "block";
	toggle.textContent = open ? "HOST TOOLS ▼" : "HOST TOOLS ▲";
	window.bonkHost.updateHostPlayerMenuDropdownHeight();
});

document.getElementById("hostPlayerMenuPlaylistsToggle").addEventListener("click", () => {
	let dropdown = document.getElementById("hostPlayerMenuPlaylistsDropdown");
	let toggle = document.getElementById("hostPlayerMenuPlaylistsToggle");
	let open = window.getComputedStyle(dropdown).display !== "none";

	dropdown.style.display = open ? "none" : "block";
	toggle.textContent = open ? "PLAYLISTS ▼" : "PLAYLISTS ▲";
	window.bonkHost.updateHostPlayerMenuDropdownHeight();
});

document.getElementById("hostPlayerMenuStatsToggle").addEventListener("click", () => {
	let dropdown = document.getElementById("hostPlayerMenuStatsDropdown");
	let toggle = document.getElementById("hostPlayerMenuStatsToggle");
	let open = window.getComputedStyle(dropdown).display !== "none";
	dropdown.style.display = open ? "none" : "block";
	toggle.textContent = open ? "MATCH STATS ▼" : "MATCH STATS ▲";
	window.bonkHost.updateHostPlayerMenuDropdownHeight();
});

document.getElementById("hostPlayerMenuPlayersToggle").addEventListener("click", () => {
	let dropdown = document.getElementById("hostPlayerMenuPlayersDropdown");
	let toggle = document.getElementById("hostPlayerMenuPlayersToggle");
	let open = window.getComputedStyle(dropdown).display !== "none";
	dropdown.style.display = open ? "none" : "block";
	toggle.textContent = open ? "PLAYER TOOLS ▼" : "PLAYER TOOLS ▲";
	window.bonkHost.updateHostPlayerMenuDropdownHeight();
});

document.getElementById("hostPlayerMenuDatabaseToggle").addEventListener("click", () => {
	let dropdown = document.getElementById("hostPlayerMenuDatabaseDropdown");
	let toggle = document.getElementById("hostPlayerMenuDatabaseToggle");
	let open = window.getComputedStyle(dropdown).display !== "none";
	dropdown.style.display = open ? "none" : "block";
	toggle.textContent = open ? "PLAYER DATABASE ▼" : "PLAYER DATABASE ▲";
	window.bonkHost.updateHostPlayerMenuDropdownHeight();
});

document.getElementById("hostPlayerMenuSettingsToggle").addEventListener("click", () => {
	let dropdown = document.getElementById("hostPlayerMenuSettingsDropdown");
	let toggle = document.getElementById("hostPlayerMenuSettingsToggle");
	let open = window.getComputedStyle(dropdown).display !== "none";
	dropdown.style.display = open ? "none" : "block";
	toggle.textContent = open ? "SETTINGS ▼" : "SETTINGS ▲";
	window.bonkHost.updateHostPlayerMenuDropdownHeight();
});

document.getElementById("hostPlayerMenuHelpToggle").addEventListener("click", () => {
	let dropdown = document.getElementById("hostPlayerMenuHelpDropdown");
	let toggle = document.getElementById("hostPlayerMenuHelpToggle");
	let open = window.getComputedStyle(dropdown).display !== "none";
	dropdown.style.display = open ? "none" : "block";
	toggle.textContent = open ? "HELP ▼" : "HELP ▲";
	window.bonkHost.updateHostPlayerMenuDropdownHeight();
});

document.getElementById("hostPlayerMenuPatchNotesToggle").addEventListener("click", () => {
	let dropdown = document.getElementById("hostPlayerMenuPatchNotesDropdown");
	let toggle = document.getElementById("hostPlayerMenuPatchNotesToggle");
	let open = window.getComputedStyle(dropdown).display !== "none";
	dropdown.style.display = open ? "none" : "block";
	toggle.textContent = open ? "PATCH NOTES ▼" : "PATCH NOTES ▲";
	window.bonkHost.updateHostPlayerMenuDropdownHeight();
});

document.getElementById("hostPlayerMenuOpenPlaylistsButton").addEventListener("click", () => {
	let mapButton = document.getElementById("newbonklobby_mapbutton");
	let playlistsOption = document.getElementById("maploadtypedropdownoptionplaylists");
	if(mapButton) mapButton.click();
	setTimeout(() => {
		playlistsOption = document.getElementById("maploadtypedropdownoptionplaylists");
		if(playlistsOption) playlistsOption.click();
	}, 100);
});

window.bonkHost.quickplay = {
	state: "off",
	roundsPer: 3,
	roundCounter: 0,
	queue: [],
	index: -1,
	busy: false,
	startedGame: false,
	lastRoundPacketTime: 0,
	mapWins: {},
	lastScores: null
};


window.bonkHost.extraFeatures = {
	stats: { games: 0, wins: {}, streakName: null, streak: 0, redWins: 0, blueWins: 0 },
	mapHistoryNames: [],
	smartShuffle: false,
	autoHost: false,
	autoHostTargetName: null,
	recentJoins: [],
	rejoinProtection: false,
	reservedNames: {},
	lastPlayerNames: [],
	autoNewJoinBalance: true,
	playerNotes: JSON.parse(localStorage.getItem("bonkHostPlayerNotes") || "{}"),
	playerHistory: JSON.parse(localStorage.getItem("bonkHostPlayerHistory") || "{}"),
	captainsMode: false,
	patchNotesCompact: true,
	showFlags: true,
	autoJoinNext: true,
	autoJoinNextLastAttempt: 0,
	globalPlayerHistory: true,
	careerStatsEnabled: true,
	careerStats: JSON.parse(localStorage.getItem("bonkHostCareerStats") || "{}"),
	packetMonitor: { inGame: false, lastStart: 0, lastEnd: 0, currentMode: "custom", lastPacketType: null },
	activeSocket: null,
	globalHistoryLastScores: null,
	globalHistoryLastRecordTime: 0,
	settingsVersion: "8.2"
};

window.bonkHost.getPlayerNameById = (id) => {
	return window.bonkHost.players && window.bonkHost.players[id] && window.bonkHost.players[id].userName ? window.bonkHost.players[id].userName : null;
};

window.bonkHost.updateExtraFeatureButtons = () => {
	let ex = window.bonkHost.extraFeatures;
	let statsBtn = document.getElementById("hostPlayerMenuStatsOverview");
	let rbBtn = document.getElementById("hostPlayerMenuRedBlueWinrate");
	let autoHostBtn = document.getElementById("hostPlayerMenuAutoHostButton");
	let recentBtn = document.getElementById("hostPlayerMenuRecentJoinsStatus");
	let rejoinBtn = document.getElementById("hostPlayerMenuSettingsRejoinProtectionButton");
	let captainsBtn = document.getElementById("hostPlayerMenuCaptainsButton");
	let smartBtn = document.getElementById("hostPlayerMenuSettingsSmartShuffleButton");
	let historyBtn = document.getElementById("hostPlayerMenuMapHistoryStatus");

	let winsEntries = Object.entries(ex.stats.wins || {}).sort((a,b)=>b[1]-a[1]);
	let top = winsEntries.length ? winsEntries[0][0] + " (" + winsEntries[0][1] + ")" : "NONE";
	if(statsBtn) statsBtn.textContent = "MOST WINS: " + top + " | STREAK: " + (ex.stats.streakName ? ex.stats.streakName + " (" + ex.stats.streak + ")" : "NONE") + " | GAMES: " + ex.stats.games;

	let totalTeams = ex.stats.redWins + ex.stats.blueWins;
	let redWr = totalTeams ? Math.round((ex.stats.redWins / totalTeams) * 100) : 0;
	let blueWr = totalTeams ? Math.round((ex.stats.blueWins / totalTeams) * 100) : 0;
	if(rbBtn) rbBtn.textContent = "RED WR: " + redWr + "% | BLUE WR: " + blueWr + "%";

	if(autoHostBtn) autoHostBtn.textContent = "AUTO HOST: " + (ex.autoHost ? "ON" : "OFF") + (ex.autoHostTargetName ? " (" + ex.autoHostTargetName + ")" : "");
	if(recentBtn) recentBtn.textContent = "RECENT JOINS: " + (ex.recentJoins.length ? ex.recentJoins.slice(0, 3).join(", ") : "NONE");
	if(rejoinBtn) rejoinBtn.textContent = "REJOIN PROTECTION: " + (ex.rejoinProtection ? "ON" : "OFF");
	let smartBalanceBtn = document.getElementById("hostPlayerMenuSmartBalanceButton");
	if(smartBalanceBtn) smartBalanceBtn.textContent = "BALANCE NEW JOINS: AUTO";
	if(captainsBtn) captainsBtn.textContent = "CAPTAINS MODE: " + (ex.captainsMode ? "ON" : "OFF");
	if(smartBtn) smartBtn.textContent = "SMART SHUFFLE: " + (ex.smartShuffle ? "ON" : "OFF");
	if(historyBtn) historyBtn.textContent = "MAP HISTORY: " + (ex.mapHistoryNames.length ? ex.mapHistoryNames.slice(0, 3).join(" > ") : "EMPTY");
	let autoTeamJoinBtn = document.getElementById("hostPlayerMenuAutoTeamJoinButton");
	if(autoTeamJoinBtn) autoTeamJoinBtn.textContent = "AUTO TEAM JOIN: " + (ex.autoNewJoinBalance ? "ON" : "OFF");
	let autoJoinNextBtn = document.getElementById("hostPlayerMenuAutoJoinNextButton");
	if(autoJoinNextBtn) autoJoinNextBtn.textContent = "AUTO JOIN NEXT: " + (ex.autoJoinNext ? "ON" : "OFF");
	let globalHistoryBtn = document.getElementById("hostPlayerMenuGlobalHistoryButton");
	if(globalHistoryBtn) globalHistoryBtn.textContent = "GLOBAL PLAYER HISTORY: " + (ex.globalPlayerHistory ? "ON" : "OFF");
	let careerStatsBtn = document.getElementById("hostPlayerMenuCareerStatsButton");
	if(careerStatsBtn) careerStatsBtn.textContent = "CAREER STATS: " + (ex.careerStatsEnabled !== false ? "ON" : "OFF");
	let patchCompactBtn = document.getElementById("hostPlayerMenuPatchNotesCompactButton");
	if(patchCompactBtn) patchCompactBtn.textContent = "PATCH NOTES: " + (ex.patchNotesCompact ? "COMPACT" : "FULL");
	let patchDropdown = document.getElementById("hostPlayerMenuPatchNotesDropdown");
	if(patchDropdown) patchDropdown.classList.toggle("compact", !!ex.patchNotesCompact);
	let showFlagsBtn = document.getElementById("hostPlayerMenuShowFlagsButton");
	if(showFlagsBtn) showFlagsBtn.textContent = "COUNTRY FLAGS: AUTO";
	if(window.bonkHost.applyCountryFlagsToLobby) window.bonkHost.applyCountryFlagsToLobby();

};



// v6.4: Personal Auto Join Next.
// When enabled, if you are spectating, the mod waits for the start of a round
// and moves only you into FFA or the Red/Blue team that needs a player.
window.bonkHost.getMyPlayerId = () => {
	try {
		if(window.bonkHost.toolFunctions && window.bonkHost.toolFunctions.networkEngine) {
			return window.bonkHost.toolFunctions.networkEngine.getLSID();
		}
	}
	catch(e) {}
	return -1;
};

window.bonkHost.getAutoJoinNextTargetTeam = () => {
	let gs = null;
	try { gs = window.bonkHost.toolFunctions.getGameSettings(); } catch(e) {}
	if(!gs || !gs.tea) return 1; // FFA

	let myId = window.bonkHost.getMyPlayerId ? window.bonkHost.getMyPlayerId() : -1;
	let counts = {2: 0, 3: 0};
	for(let i = 0; i < (window.bonkHost.players || []).length; i++) {
		if(i === myId) continue;
		let p = window.bonkHost.players[i];
		if(!p) continue;
		if(p.team === 2) counts[2]++;
		if(p.team === 3) counts[3]++;
	}
	return counts[2] <= counts[3] ? 2 : 3;
};

window.bonkHost.tryAutoJoinNext = (reason = "timer") => {
	let ex = window.bonkHost.extraFeatures;
	if(!ex || !ex.autoJoinNext) return false;
	if(!window.bonkHost.players || !window.bonkHost.toolFunctions || !window.bonkHost.toolFunctions.networkEngine) return false;

	let myId = window.bonkHost.getMyPlayerId ? window.bonkHost.getMyPlayerId() : -1;
	let me = window.bonkHost.players[myId];
	if(!me || me.team !== 0) return false; // only when spectating

	// Avoid spam-clicking team changes.
	let now = Date.now();
	if(ex.autoJoinNextLastAttempt && now - ex.autoJoinNextLastAttempt < 1500) return false;

	// Only trigger at the beginning of a round/game where possible.
	// fig is patched from Bonk's frame counter and normally resets near round start.
	let fig = typeof window.bonkHost.fig === "number" ? window.bonkHost.fig : 0;
	let roundLooksFresh = !window.bonkHost.inGame || fig < 120 || reason === "manual";
	if(!roundLooksFresh) return false;

	let targetTeam = window.bonkHost.getAutoJoinNextTargetTeam();
	ex.autoJoinNextLastAttempt = now;

	try {
		window.bonkHost.toolFunctions.networkEngine.changeOwnTeam(targetTeam);
		if(window.bonkHost.menuFunctions) {
			let label = targetTeam === 1 ? "FFA" : targetTeam === 2 ? "RED" : targetTeam === 3 ? "BLUE" : "GAME";
			window.bonkHost.menuFunctions.showStatusMessage("* Auto Join Next: joined " + label, "#b53030", false);
		}
		return true;
	}
	catch(e) {
		console.log("Host Mod: Auto Join Next failed", e);
	}
	return false;
};

window.bonkHost.startAutoJoinNextWatcher = () => {
	if(window.bonkHost.autoJoinNextWatcherStarted) return;
	window.bonkHost.autoJoinNextWatcherStarted = true;
	setInterval(() => {
		try { window.bonkHost.tryAutoJoinNext && window.bonkHost.tryAutoJoinNext(); }
		catch(e) {}
	}, 750);
};
window.bonkHost.startAutoJoinNextWatcher();



// v6.7: Diagnostics, version info, settings import/export, and player cards.
window.bonkHost.showHostStatus = () => {
	let lines = [];
	let add = (ok, label) => lines.push((ok ? "✓ " : "✗ ") + label);

	add(!!window.bonkHostBuiltInCodeInjectorLoaded, "Built-in injector loaded");
	add(!!window.bonkCodeInjectors, "Injector queue available");
	add(!!window.bonkHost.toolFunctions, "Game tool functions loaded");
	add(!!window.bonkHost.menuFunctions, "Host Mod UI helpers loaded");
	add(!!document.getElementById("hostPlayerMenu"), "Host menu loaded");
	add(!!window.bonkHost.quickplay, "Quickplay loaded");
	add(!!window.bonkHost.extraFeatures, "Extra features loaded");
	add(!!window.bonkHost.applyCountryFlagsToLobby, "Country flags loaded");
	add(!!window.bonkHost.tryAutoJoinNext, "Auto Join Next loaded");
	add(!!window.bonkHost.handleGlobalPlayerHistoryScoreTick, "Global Player History loaded");
	add(!!window.bonkHost.handleObservedPacket, "Packet monitor loaded");
	add(!!window.bonkHost.showMyCareerStats, "Career stats loaded");
	add(!!window.bonkHost.showMyProfile, "Player Database loaded");
	add(!!window.bonkHost.patchPacketMonitorSend, "Outgoing packet monitor loaded");
	add(!!window.bonkHost.sendReturnToLobbyPacket, "Packet Return To Lobby loaded");

	if(window.bonkHost.showHelpMessages) window.bonkHost.showHelpMessages("STATUS", lines);
	else alert("HOST MOD STATUS\n\n" + lines.join("\n"));
};

window.bonkHost.showVersionInfo = () => {
	let lines = [
		"Current Version: Trapi's Client v8.2",
		"Latest Update: Client UI cleanup, saved room watch, and private chat.",
		"Built-in Injector: " + (window.bonkHostBuiltInCodeInjectorLoaded ? "Loaded" : "Not detected"),
		"Install Note: Separate Code Injector userscript should no longer be required."
	];
	if(window.bonkHost.showHelpMessages) window.bonkHost.showHelpMessages("VERSION", lines);
	else alert(lines.join("\n"));
};

window.bonkHost.getExportableSettings = () => {
	let ex = window.bonkHost.extraFeatures || {};
	return {
		version: "8.2",
		exportedAt: new Date().toISOString(),
		settings: {
			freejoin: !!window.bonkHost.freejoin,
			autoTeamJoin: !!ex.autoNewJoinBalance,
			autoJoinNext: !!ex.autoJoinNext,
			globalPlayerHistory: !!ex.globalPlayerHistory,
			careerStatsEnabled: ex.careerStatsEnabled !== false,
			smartShuffle: !!ex.smartShuffle,
			rejoinProtection: !!ex.rejoinProtection,
			patchNotesCompact: !!ex.patchNotesCompact,
			showFlags: !!ex.showFlags,
			captainsMode: !!ex.captainsMode,
			autoHost: !!ex.autoHost,
			autoHostTargetName: ex.autoHostTargetName || null
		},
		playerNotes: ex.playerNotes || {},
		playerHistory: ex.playerHistory || {},
		careerStats: ex.careerStats || {},
		clientProfile: ex.clientProfile || {},
		accountLinks: ex.accountLinks || {}
	};
};

window.bonkHost.exportSettings = () => {
	let data = JSON.stringify(window.bonkHost.getExportableSettings(), null, 2);
	try {
		if(navigator.clipboard && navigator.clipboard.writeText) {
			navigator.clipboard.writeText(data);
			if(window.bonkHost.menuFunctions) window.bonkHost.menuFunctions.showStatusMessage("* Host Mod settings copied to clipboard", "#b53030", false);
		}
	}
	catch(e) {}
	prompt("Copy your Host Mod settings:", data);
};

window.bonkHost.importSettings = () => {
	let raw = prompt("Paste Host Mod settings JSON:");
	if(!raw) return;
	try {
		let parsed = JSON.parse(raw);
		let settings = parsed.settings || parsed;
		let ex = window.bonkHost.extraFeatures || {};

		if(typeof settings.freejoin === "boolean") window.bonkHost.freejoin = settings.freejoin;
		if(typeof settings.autoTeamJoin === "boolean") ex.autoNewJoinBalance = settings.autoTeamJoin;
		if(typeof settings.autoJoinNext === "boolean") ex.autoJoinNext = settings.autoJoinNext;
		if(typeof settings.globalPlayerHistory === "boolean") ex.globalPlayerHistory = settings.globalPlayerHistory;
		if(typeof settings.careerStatsEnabled === "boolean") ex.careerStatsEnabled = settings.careerStatsEnabled;
		if(typeof settings.smartShuffle === "boolean") ex.smartShuffle = settings.smartShuffle;
		if(typeof settings.rejoinProtection === "boolean") ex.rejoinProtection = settings.rejoinProtection;
		if(typeof settings.patchNotesCompact === "boolean") ex.patchNotesCompact = settings.patchNotesCompact;
		if(typeof settings.showFlags === "boolean") ex.showFlags = settings.showFlags;
		if(typeof settings.captainsMode === "boolean") ex.captainsMode = settings.captainsMode;
		if(typeof settings.autoHost === "boolean") ex.autoHost = settings.autoHost;
		if(typeof settings.autoHostTargetName === "string" || settings.autoHostTargetName === null) ex.autoHostTargetName = settings.autoHostTargetName;

		if(parsed.playerNotes && typeof parsed.playerNotes === "object") {
			ex.playerNotes = parsed.playerNotes;
			localStorage.setItem("bonkHostPlayerNotes", JSON.stringify(ex.playerNotes));
		}
		if(parsed.playerHistory && typeof parsed.playerHistory === "object") {
			ex.playerHistory = parsed.playerHistory;
			localStorage.setItem("bonkHostPlayerHistory", JSON.stringify(ex.playerHistory));
		}
		if(parsed.careerStats && typeof parsed.careerStats === "object") {
			ex.careerStats = parsed.careerStats;
			localStorage.setItem("bonkHostCareerStats", JSON.stringify(ex.careerStats));
		}
		if(parsed.clientProfile && typeof parsed.clientProfile === "object") {
			ex.clientProfile = parsed.clientProfile;
			localStorage.setItem("bonkHostClientProfile", JSON.stringify(ex.clientProfile));
		}
		if(parsed.accountLinks && typeof parsed.accountLinks === "object") {
			ex.accountLinks = parsed.accountLinks;
			localStorage.setItem("bonkHostAccountLinks", JSON.stringify(ex.accountLinks));
			if(window.bonkHost.rebuildAccountLinkIndex) window.bonkHost.rebuildAccountLinkIndex();
		}

		if(window.bonkHost.updateHostToolButtons) window.bonkHost.updateHostToolButtons();
		if(window.bonkHost.updateExtraFeatureButtons) window.bonkHost.updateExtraFeatureButtons();

		if(window.bonkHost.menuFunctions) window.bonkHost.menuFunctions.showStatusMessage("* Imported Host Mod settings", "#b53030", false);
	}
	catch(e) {
		alert("Invalid Host Mod settings JSON.");
		console.log("Host Mod import failed", e);
	}
};

window.bonkHost.getTeamLabel = (team) => {
	if(team === 0) return "SPEC";
	if(team === 1) return "FFA";
	if(team === 2) return "RED";
	if(team === 3) return "BLUE";
	if(team === 4) return "GREEN";
	if(team === 5) return "YELLOW";
	return "UNKNOWN";
};


// v7.0: Local saved player history for Player Cards.
// Saves join count, last joined/seen, wins, games, and winrate locally in this browser.
window.bonkHost.savePlayerHistory = () => {
	try {
		let ex = window.bonkHost.extraFeatures || {};
		localStorage.setItem("bonkHostPlayerHistory", JSON.stringify(ex.playerHistory || {}));
	}
	catch(e) {}
};

window.bonkHost.getPlayerHistoryRecord = (name) => {
	let ex = window.bonkHost.extraFeatures || {};
	if(!ex.playerHistory) ex.playerHistory = JSON.parse(localStorage.getItem("bonkHostPlayerHistory") || "{}");
	let key = (name || "UNKNOWN").trim();
	if(!ex.playerHistory[key]) {
		ex.playerHistory[key] = {
			joins: 0,
			wins: 0,
			games: 0,
			lastJoin: null,
			lastSeen: null,
			lastLeave: null
		};
	}
	if(window.bonkHost.normalizePlayerHistoryRecord) window.bonkHost.normalizePlayerHistoryRecord(ex.playerHistory[key]);
	return ex.playerHistory[key];
};


window.bonkHost.normalizePlayerHistoryRecord = (rec) => {
	if(!rec) rec = {};
	for(let key of ["custom", "quickplay", "total"]) {
		if(!rec[key]) rec[key] = { wins: 0, games: 0 };
		if(typeof rec[key].wins !== "number") rec[key].wins = 0;
		if(typeof rec[key].games !== "number") rec[key].games = 0;
	}
	if(typeof rec.wins !== "number") rec.wins = rec.total.wins || 0;
	if(typeof rec.games !== "number") rec.games = rec.total.games || 0;
	return rec;
};

window.bonkHost.getHistoryModeKey = () => {
	let ex = window.bonkHost.extraFeatures || {};
	try {
		let gs = window.bonkHost.toolFunctions && window.bonkHost.toolFunctions.getGameSettings ? window.bonkHost.toolFunctions.getGameSettings() : null;
		if(gs && gs.q) return "quickplay";
	} catch(e) {}
	if(ex.packetMonitor && ex.packetMonitor.currentMode === "quickplay") return "quickplay";
	return "custom";
};

window.bonkHost.addPlayerHistoryGameByMode = (rec, mode, games, wins) => {
	rec = window.bonkHost.normalizePlayerHistoryRecord(rec);
	mode = mode === "quickplay" ? "quickplay" : "custom";
	games = Math.max(0, games || 0);
	wins = Math.max(0, wins || 0);
	rec[mode].games += games;
	rec[mode].wins += wins;
	rec.total.games += games;
	rec.total.wins += wins;
	rec.games = rec.total.games;
	rec.wins = rec.total.wins;
	return rec;
};

window.bonkHost.saveCareerStats = () => {
	try { localStorage.setItem("bonkHostCareerStats", JSON.stringify((window.bonkHost.extraFeatures || {}).careerStats || {})); }
	catch(e) {}
};

window.bonkHost.getCareerStatsRecord = (name) => {
	let ex = window.bonkHost.extraFeatures || {};
	if(!ex.careerStats) ex.careerStats = JSON.parse(localStorage.getItem("bonkHostCareerStats") || "{}");
	let key = (name || "ME").trim();
	if(!ex.careerStats[key]) {
		ex.careerStats[key] = { firstSeen: Date.now(), lastSeen: null, currentStreak: 0, bestStreak: 0, custom: { wins: 0, games: 0 }, quickplay: { wins: 0, games: 0 }, total: { wins: 0, games: 0 } };
	}
	return ex.careerStats[key];
};

window.bonkHost.recordCareerGame = (won, mode) => {
	let ex = window.bonkHost.extraFeatures || {};
	if(ex.careerStatsEnabled === false) return;
	let myId = window.bonkHost.getMyPlayerId ? window.bonkHost.getMyPlayerId() : -1;
	let me = window.bonkHost.players && window.bonkHost.players[myId] ? window.bonkHost.players[myId] : null;
	let name = me && me.userName ? me.userName : "ME";
	let rec = window.bonkHost.getCareerStatsRecord(name);
	mode = mode === "quickplay" ? "quickplay" : "custom";
	for(let key of ["custom", "quickplay", "total"]) if(!rec[key]) rec[key] = { wins: 0, games: 0 };
	rec[mode].games += 1;
	rec.total.games += 1;
	if(won) { rec[mode].wins += 1; rec.total.wins += 1; rec.currentStreak = (rec.currentStreak || 0) + 1; rec.bestStreak = Math.max(rec.bestStreak || 0, rec.currentStreak); }
	else rec.currentStreak = 0;
	rec.lastSeen = Date.now();
	window.bonkHost.saveCareerStats();
};

window.bonkHost.formatWR = (wins, games) => games ? Math.round((wins / games) * 100) + "%" : "0%";

window.bonkHost.formatTimeAgo = (time) => {
	if(!time) return "Never";
	let diff = Math.max(0, Date.now() - time);
	let s = Math.floor(diff / 1000);
	if(s < 10) return "just now";
	if(s < 60) return s + "s ago";
	let m = Math.floor(s / 60);
	if(m < 60) return m + "m " + (s % 60) + "s ago";
	let h = Math.floor(m / 60);
	if(h < 24) return h + "h " + (m % 60) + "m ago";
	let d = Math.floor(h / 24);
	return d + "d " + (h % 24) + "h ago";
};

window.bonkHost.recordPlayerJoinHistory = (name) => {
	if(!name) return;
	let rec = window.bonkHost.getPlayerHistoryRecord(name);
	rec.joins = (rec.joins || 0) + 1;
	rec.lastJoin = Date.now();
	rec.lastSeen = Date.now();
	rec.lastLeave = null;
	window.bonkHost.savePlayerHistory();
};

window.bonkHost.recordPlayerLeaveHistory = (name) => {
	if(!name) return;
	let rec = window.bonkHost.getPlayerHistoryRecord(name);
	rec.lastSeen = Date.now();
	rec.lastLeave = Date.now();
	window.bonkHost.savePlayerHistory();
};

window.bonkHost.getActivePlayerNamesForHistory = () => {
	return (window.bonkHost.players || []).filter(p => p && p.userName && p.team !== 0).map(p => p.userName);
};

window.bonkHost.recordPlayerGameHistory = (winnerId, winAmount = 1) => {
	let ex = window.bonkHost.extraFeatures || {};
	if(!ex.playerHistory) ex.playerHistory = JSON.parse(localStorage.getItem("bonkHostPlayerHistory") || "{}");

	let gs = null;
	try { gs = window.bonkHost.toolFunctions.getGameSettings(); } catch(e) {}
	let players = window.bonkHost.players || [];
	let activeNames = window.bonkHost.getActivePlayerNamesForHistory();
	let winnerNames = [];

	let id = parseInt(winnerId);
	if(gs && gs.tea && (id === 2 || id === 3)) {
		winnerNames = players.filter(p => p && p.userName && p.team === id).map(p => p.userName);
	}
	else if(players[id] && players[id].userName) {
		winnerNames = [players[id].userName];
	}
	else {
		let fallback = window.bonkHost.getQuickplayLeaderName ? window.bonkHost.getQuickplayLeaderName(id) : null;
		if(fallback && !["RED","BLUE","GREEN","YELLOW","FFA","SPEC","UNKNOWN"].includes(fallback)) winnerNames = [fallback];
	}

	let mode = window.bonkHost.getHistoryModeKey ? window.bonkHost.getHistoryModeKey() : "custom";
	for(let name of activeNames) {
		let rec = window.bonkHost.getPlayerHistoryRecord(name);
		window.bonkHost.addPlayerHistoryGameByMode(rec, mode, winAmount, 0);
		rec.lastSeen = Date.now();
	}
	for(let name of winnerNames) {
		let rec = window.bonkHost.getPlayerHistoryRecord(name);
		if(activeNames.includes(name)) window.bonkHost.addPlayerHistoryGameByMode(rec, mode, 0, winAmount);
		else window.bonkHost.addPlayerHistoryGameByMode(rec, mode, winAmount, winAmount);
		rec.lastSeen = Date.now();
	}
	try {
		let myId = window.bonkHost.getMyPlayerId ? window.bonkHost.getMyPlayerId() : -1;
		let me = players[myId];
		if(me && me.team !== 0 && activeNames.includes(me.userName)) {
			let won = winnerNames.includes(me.userName);
			for(let n = 0; n < winAmount; n++) window.bonkHost.recordCareerGame && window.bonkHost.recordCareerGame(won, mode);
		}
	}
	catch(e) {}
	window.bonkHost.savePlayerHistory();
};


// v7.3: Packet-aware state tracking.
// Packet reference: incoming 13 = Game End, incoming 15 = Game Start,
// outgoing 14 = Return To Lobby, outgoing 40 = Inform In Game.
window.bonkHost.handleObservedPacket = (direction, packet) => {
	try {
		if(!Array.isArray(packet)) return;
		let type = packet[0];
		let ex = window.bonkHost.extraFeatures || {};
		if(!ex.packetMonitor) ex.packetMonitor = { inGame: false, lastStart: 0, lastEnd: 0, currentMode: "custom", lastPacketType: null };
		ex.packetMonitor.lastPacketType = direction + ":" + type;
		if(direction === "in" && type === 15) {
			ex.packetMonitor.inGame = true;
			ex.packetMonitor.lastStart = Date.now();
			let gs = packet[3] || {};
			ex.packetMonitor.currentMode = gs && gs.q ? "quickplay" : "custom";
			ex.globalHistoryLastScores = null;
		}
		else if(direction === "in" && type === 13) {
			ex.packetMonitor.inGame = false;
			ex.packetMonitor.lastEnd = Date.now();
			setTimeout(() => { try { window.bonkHost.handleGlobalPlayerHistoryScoreTick && window.bonkHost.handleGlobalPlayerHistoryScoreTick("packet-end"); } catch(e) {} }, 175);
		}
		else if(direction === "out" && type === 14) ex.packetMonitor.inGame = false;
		else if(direction === "out" && type === 40) ex.packetMonitor.inGame = true;
	}
	catch(e) {}
};

window.bonkHost.parseSocketPacket = (data) => {
	if(typeof data !== "string" || !data.startsWith("42")) return null;
	try { return JSON.parse(data.slice(2)); } catch(e) { return null; }
};

window.bonkHost.patchPacketMonitorWebSocket = () => {
	if(window.bonkHost.packetMonitorSocketPatched) return;
	window.bonkHost.packetMonitorSocketPatched = true;
	const originalAddEventListener = WebSocket.prototype.addEventListener;
	WebSocket.prototype.addEventListener = function(type, listener, options) {
		if(type === "message" && typeof listener === "function" && !listener.__bonkHostWrapped) {
			const wrapped = function(event) {
				try {
					if(window.bonkHost && window.bonkHost.extraFeatures) window.bonkHost.extraFeatures.activeSocket = this;
					let packet = window.bonkHost.parseSocketPacket(event && event.data);
					if(packet && window.bonkHost.handleObservedPacket) window.bonkHost.handleObservedPacket("in", packet);
				}
				catch(e) {}
				return listener.call(this, event);
			};
			wrapped.__bonkHostWrapped = true;
			return originalAddEventListener.call(this, type, wrapped, options);
		}
		return originalAddEventListener.call(this, type, listener, options);
	};
};
window.bonkHost.patchPacketMonitorWebSocket();

// v7.2: Global Player History.
// Tracks score increases from any room you are in, even as a non-host.
window.bonkHost.handleGlobalPlayerHistoryScoreTick = () => {
	let ex = window.bonkHost.extraFeatures || {};
	if(ex.globalPlayerHistory === false) return;
	if(!window.bonkHost.players || !window.bonkHost.getQuickplayScores) return;

	let scores = window.bonkHost.getQuickplayScores();
	if(!scores || !Array.isArray(scores)) return;

	let last = ex.globalHistoryLastScores;
	if(!last || last.length !== scores.length) {
		ex.globalHistoryLastScores = scores.slice();
		return;
	}

	let changed = false;
	for(let i = 0; i < scores.length; i++) {
		let before = typeof last[i] === "number" ? last[i] : 0;
		let after = typeof scores[i] === "number" ? scores[i] : null;
		if(after === null) continue;

		// Score reset/new map/new game. Re-baseline without counting fake losses.
		if(after < before) {
			ex.globalHistoryLastScores = scores.slice();
			return;
		}

		let diff = after - before;
		if(diff > 0) {
			// Avoid accidental double-counting caused by duplicate score ticks.
			let now = Date.now();
			if(ex.globalHistoryLastRecordTime && now - ex.globalHistoryLastRecordTime < 350) continue;
			ex.globalHistoryLastRecordTime = now;
			window.bonkHost.recordPlayerGameHistory(i, diff);
			changed = true;
		}
	}

	ex.globalHistoryLastScores = scores.slice();
	if(changed && window.bonkHost.updateExtraFeatureButtons) window.bonkHost.updateExtraFeatureButtons();
};

window.bonkHost.startGlobalPlayerHistoryWatcher = () => {
	if(window.bonkHost.globalPlayerHistoryWatcherStarted) return;
	window.bonkHost.globalPlayerHistoryWatcherStarted = true;
	setInterval(() => {
		try { window.bonkHost.handleGlobalPlayerHistoryScoreTick && window.bonkHost.handleGlobalPlayerHistoryScoreTick(); }
		catch(e) {}
	}, 700);
};
window.bonkHost.startGlobalPlayerHistoryWatcher();

window.bonkHost.showPlayerCard = () => {
	let suggested = "";
	try {
		let players = (window.bonkHost.players || []).filter(p => p && p.userName).map(p => p.userName);
		suggested = players[0] || "";
	}
	catch(e) {}
	let name = prompt("Player name for card:", suggested);
	if(!name) return;

	let ex = window.bonkHost.extraFeatures || {};
	let players = window.bonkHost.players || [];
	let playerIndex = players.findIndex(p => p && p.userName && p.userName.toLowerCase() === name.toLowerCase());
	let player = playerIndex >= 0 ? players[playerIndex] : null;
	let realName = player ? player.userName : name.trim();
	let sessionWins = (ex.stats && ex.stats.wins && ex.stats.wins[realName]) ? ex.stats.wins[realName] : 0;
	let note = ex.playerNotes && ex.playerNotes[realName] ? ex.playerNotes[realName] : "None";
	let flag = player && window.bonkHost.getPlayerFlag ? window.bonkHost.getPlayerFlag(player) : "";
	let team = player ? window.bonkHost.getTeamLabel(player.team) : "Not in room";
	let guest = player ? (player.guest ? "YES" : "NO") : "UNKNOWN";
	let streak = ex.stats && ex.stats.streakName === realName ? ex.stats.streak : 0;
	let history = window.bonkHost.getPlayerHistoryRecord ? window.bonkHost.getPlayerHistoryRecord(realName) : {};
	window.bonkHost.normalizePlayerHistoryRecord && window.bonkHost.normalizePlayerHistoryRecord(history);
	let lines = [
		"Name: " + (flag ? flag + " " : "") + realName,
		"Status: " + (player ? "In room" : "Not in room"),
		"Team: " + team,
		"Guest: " + guest,
		"Tracking Scope: " + ((ex.globalPlayerHistory !== false) ? "All Games + Classic Quickplay" : "Host Mod Only"),
		"Total: " + (history.total.wins || 0) + "W / " + (history.total.games || 0) + "G / " + window.bonkHost.formatWR(history.total.wins || 0, history.total.games || 0),
		"Free For All: " + (history.custom.wins || 0) + "W / " + (history.custom.games || 0) + "G / " + window.bonkHost.formatWR(history.custom.wins || 0, history.custom.games || 0),
		"Classic Quickplay: " + (history.quickplay.wins || 0) + "W / " + (history.quickplay.games || 0) + "G / " + window.bonkHost.formatWR(history.quickplay.wins || 0, history.quickplay.games || 0),
		"Session Wins: " + sessionWins,
		"Current Streak: " + streak,
		"Join Count: " + (history.joins || 0),
		"Last Joined: " + window.bonkHost.formatTimeAgo(history.lastJoin),
		"Last Seen: " + (player ? "In Room" : window.bonkHost.formatTimeAgo(history.lastSeen || history.lastLeave)),
		"Last Left: " + (history.lastLeave ? window.bonkHost.formatTimeAgo(history.lastLeave) : "Never"),
		"Note: " + note
	];
	if(window.bonkHost.showHelpMessages) window.bonkHost.showHelpMessages("PLAYER CARD", lines);
	else alert("PLAYER CARD\n\n" + lines.join("\n"));
};

window.bonkHost.showMyCareerStats = () => {
	let myId = window.bonkHost.getMyPlayerId ? window.bonkHost.getMyPlayerId() : -1;
	let me = window.bonkHost.players && window.bonkHost.players[myId] ? window.bonkHost.players[myId] : null;
	let name = me && me.userName ? me.userName : "ME";
	let rec = window.bonkHost.getCareerStatsRecord ? window.bonkHost.getCareerStatsRecord(name) : null;
	if(!rec) return;
	for(let key of ["custom", "quickplay", "total"]) if(!rec[key]) rec[key] = { wins: 0, games: 0 };
	let lines = [
		"Player: " + name,
		"Total: " + rec.total.wins + "W / " + rec.total.games + "G / " + window.bonkHost.formatWR(rec.total.wins, rec.total.games),
		"Free For All: " + rec.custom.wins + "W / " + rec.custom.games + "G / " + window.bonkHost.formatWR(rec.custom.wins, rec.custom.games),
		"Classic Quickplay: " + rec.quickplay.wins + "W / " + rec.quickplay.games + "G / " + window.bonkHost.formatWR(rec.quickplay.wins, rec.quickplay.games),
		"Current Streak: " + (rec.currentStreak || 0),
		"Best Streak: " + (rec.bestStreak || 0),
		"First Seen: " + (rec.firstSeen ? new Date(rec.firstSeen).toLocaleDateString() : "Unknown"),
		"Last Seen: " + window.bonkHost.formatTimeAgo(rec.lastSeen)
	];
	if(window.bonkHost.showHelpMessages) window.bonkHost.showHelpMessages("MY CAREER STATS", lines);
	else alert("MY CAREER STATS\n\n" + lines.join("\n"));
};



// v7.4: Player Database / Client Profile foundation.
// This turns Player Card into a larger local database: profile, XP/level progress,
// player lookup, recent players, leaderboards, and manual account linking.
// Bonk level formula: total XP for level n = 100 * (n - 1)^2. XP to next level = 100 * n^2 - current XP.
window.bonkHost.saveClientProfile = () => {
	try { localStorage.setItem("bonkHostClientProfile", JSON.stringify((window.bonkHost.extraFeatures || {}).clientProfile || {})); }
	catch(e) {}
};

window.bonkHost.saveAccountLinks = () => {
	try { localStorage.setItem("bonkHostAccountLinks", JSON.stringify((window.bonkHost.extraFeatures || {}).accountLinks || {})); }
	catch(e) {}
};

window.bonkHost.rebuildAccountLinkIndex = () => {
	let ex = window.bonkHost.extraFeatures || {};
	ex.linkedCanonicalNames = {};
	let links = ex.accountLinks || {};
	for(let main of Object.keys(links)) {
		ex.linkedCanonicalNames[(main || "").toLowerCase()] = main;
		for(let alt of (links[main] || [])) ex.linkedCanonicalNames[(alt || "").toLowerCase()] = main;
	}
};

window.bonkHost.getCanonicalPlayerName = (name) => {
	let ex = window.bonkHost.extraFeatures || {};
	if(!ex.linkedCanonicalNames) window.bonkHost.rebuildAccountLinkIndex && window.bonkHost.rebuildAccountLinkIndex();
	let clean = (name || "UNKNOWN").trim();
	return (ex.linkedCanonicalNames && ex.linkedCanonicalNames[clean.toLowerCase()]) || clean;
};

window.bonkHost.rebuildAccountLinkIndex();

window.bonkHost.ensureClientProfile = () => {
	let ex = window.bonkHost.extraFeatures || {};
	if(!ex.clientProfile) ex.clientProfile = {};
	let profile = ex.clientProfile;
	if(!profile.joinedDate) {
		profile.joinedDate = new Date().toISOString();
		profile.joinedVersion = "8.0";
	}
	if(!profile.displayName) {
		try {
			let myId = window.bonkHost.getMyPlayerId ? window.bonkHost.getMyPlayerId() : -1;
			let me = window.bonkHost.players && window.bonkHost.players[myId] ? window.bonkHost.players[myId] : null;
			if(me && me.userName) profile.displayName = me.userName;
		} catch(e) {}
	}
	window.bonkHost.saveClientProfile();
	return profile;
};
window.bonkHost.ensureClientProfile();

window.bonkHost.extractNumber = (text) => {
	let m = (text || "").replace(/,/g, "").match(/(\d+)/);
	return m ? parseInt(m[1]) : null;
};

window.bonkHost.getMyPlayerObjectSafe = () => {
	try {
		let myId = window.bonkHost.getMyPlayerId ? window.bonkHost.getMyPlayerId() : -1;
		return window.bonkHost.players && window.bonkHost.players[myId] ? window.bonkHost.players[myId] : null;
	} catch(e) {}
	return null;
};

window.bonkHost.detectBonkLevelFromPlayerObject = () => {
	try {
		let me = window.bonkHost.getMyPlayerObjectSafe ? window.bonkHost.getMyPlayerObjectSafe() : null;
		if(!me) return null;
		let keys = ["level", "lvl", "xpLevel", "accountLevel", "prestigeLevel"];
		for(let k of keys) {
			let v = me[k];
			if(typeof v === "number" && v > 0) return Math.floor(v);
			if(typeof v === "string" && /^\d+$/.test(v.trim())) return parseInt(v.trim());
		}
		for(let boxKey of ["profile", "account", "user", "database", "db"]) {
			let box = me[boxKey];
			if(!box || typeof box !== "object") continue;
			for(let k of keys) {
				let v = box[k];
				if(typeof v === "number" && v > 0) return Math.floor(v);
				if(typeof v === "string" && /^\d+$/.test(v.trim())) return parseInt(v.trim());
			}
		}
	} catch(e) {}
	return null;
};

window.bonkHost.detectBonkLevelFromDom = () => {
	try {
		let directIds = ["pretty_top_level", "pretty_level", "xpbarlevel", "xpbar_level"];
		for(let id of directIds) {
			let el = document.getElementById(id);
			if(!el) continue;
			let t = (el.textContent || el.value || "").trim();
			let m = t.match(/(?:level|lvl)\s*:?\s*#?\s*(\d+)/i) || t.match(/^\s*(\d+)\s*$/);
			if(m) return parseInt(m[1]);
		}
		let candidates = [...document.querySelectorAll("[id*='level' i], [class*='level' i], #xpbartext, [id*='xp' i], [class*='xp' i]")];
		for(let el of candidates) {
			let t = (el.textContent || el.value || "").trim();
			if(!t || /guest/i.test(t)) continue;
			let m = t.match(/(?:level|lvl)\s*:?\s*#?\s*(\d+)/i);
			if(m) return parseInt(m[1]);
		}
	} catch(e) {}
	return null;
};

window.bonkHost.detectBonkXpInfoFromDom = () => {
	try {
		let candidates = [...document.querySelectorAll("#xpbartext, [id*='xp' i], [class*='xp' i]")];
		for(let el of candidates) {
			let raw = (el.textContent || el.value || "").replace(/,/g, "").trim();
			if(!raw) continue;
			let m = raw.match(/(\d+)\s*\/\s*(\d+)\s*xp/i);
			if(m) return { current: parseInt(m[1]), needed: parseInt(m[2]), isProgress: true, source: "dom-progress" };
			m = raw.match(/(?:xp|experience)\s*:?\s*(\d+)/i) || raw.match(/(\d+)\s*xp/i);
			if(m) return { total: parseInt(m[1]), isProgress: false, source: "dom-total" };
		}
	} catch(e) {}
	return null;
};

window.bonkHost.detectBonkXpFromDom = () => {
	let info = window.bonkHost.detectBonkXpInfoFromDom ? window.bonkHost.detectBonkXpInfoFromDom() : null;
	if(!info) return null;
	if(typeof info.total === "number") return info.total;
	if(typeof info.current === "number") return info.current;
	return null;
};

window.bonkHost.xpForLevel = (level) => {
	level = Math.max(1, parseInt(level) || 1);
	return 100 * Math.pow(level - 1, 2);
};

window.bonkHost.levelFromXp = (xp) => {
	xp = Math.max(0, parseInt(xp) || 0);
	return Math.floor(Math.sqrt(xp / 100)) + 1;
};

window.bonkHost.xpForNextLevel = (level) => {
	level = Math.max(1, parseInt(level) || 1);
	return 100 * Math.pow(level, 2);
};

window.bonkHost.normalizeClientProfileXp = (profile, reason = "normalize") => {
	if(!profile) profile = window.bonkHost.ensureClientProfile();

	let level = Math.max(1, parseInt(profile.level || profile.lastPublicLevel || 1) || 1);
	let baseXp = window.bonkHost.xpForLevel(level);
	let nextXp = window.bonkHost.xpForNextLevel(level);

	// One-time migration from older versions that only stored totalXp.
	if(typeof profile.xpProgressWithinLevel !== "number") {
		let existing = parseInt(profile.totalXp || baseXp) || baseXp;
		profile.xpProgressWithinLevel = Math.max(0, existing - baseXp);
	}
	if(typeof profile.trackedRoundWinsSinceLevelSync !== "number") profile.trackedRoundWinsSinceLevelSync = Math.floor((profile.xpProgressWithinLevel || 0) / 100);

	let progress = Math.max(0, parseInt(profile.xpProgressWithinLevel || 0) || 0);
	while(baseXp + progress >= nextXp) {
		progress -= (nextXp - baseXp);
		level += 1;
		baseXp = window.bonkHost.xpForLevel(level);
		nextXp = window.bonkHost.xpForNextLevel(level);
	}
	profile.level = level;
	profile.levelBaseXp = baseXp;
	profile.xpProgressWithinLevel = progress;
	profile.totalXp = baseXp + progress;
	profile.nextLevelXp = nextXp;
	profile.xpUntilNextLevel = Math.max(0, nextXp - profile.totalXp);
	profile.roundWinsUntilNextLevel = Math.ceil(profile.xpUntilNextLevel / 100);
	profile.lastXpNormalizeReason = reason;
	return profile;
};

window.bonkHost.applyExactXpToClientProfile = (profile, exactXp, source = "unknown") => {
	exactXp = parseInt(exactXp);
	if(!Number.isFinite(exactXp) || exactXp < 0) return false;

	let publicLevel = Math.max(1, parseInt(profile.level || profile.lastPublicLevel || 1) || 1);
	let detectedLevelFromXp = window.bonkHost.levelFromXp(exactXp);
	let level = Math.max(publicLevel, detectedLevelFromXp);
	let baseXp = window.bonkHost.xpForLevel(level);

	// If exactXp is lower than the level floor, treat the value as bad/incomplete and ignore it.
	if(exactXp < baseXp) return false;

	profile.level = level;
	profile.levelBaseXp = baseXp;
	profile.xpProgressWithinLevel = exactXp - baseXp;
	profile.totalXp = exactXp;
	profile.xpSource = source;
	profile.lastExactXpAt = Date.now();
	window.bonkHost.normalizeClientProfileXp(profile, "apply-exact-xp");
	return true;
};

window.bonkHost.setManualExactXp = () => {
	let profile = window.bonkHost.ensureClientProfile();
	let current = (profile && profile.totalXp) ? profile.totalXp : "";
	let entered = prompt("Enter your exact total XP. This is saved once, then Trapi's Client tracks +100 XP per round win from here.", current);
	if(entered === null) return;
	let clean = parseInt(String(entered).replace(/,/g, ""));
	if(!Number.isFinite(clean) || clean < 0) {
		alert("Invalid XP amount.");
		return;
	}
	if(window.bonkHost.applyExactXpToClientProfile(profile, clean, "manual-exact")) {
		profile.manualExactXpSetAt = Date.now();
		window.bonkHost.saveClientProfile();
		if(window.bonkHost.menuFunctions) window.bonkHost.menuFunctions.showStatusMessage("* Exact XP saved", "#2f8f2f", false);
	}
};

window.bonkHost.syncClientProfileFromPublicBonk = (reason = "manual") => {
	let profile = window.bonkHost.ensureClientProfile();
	let changed = false;
	let detectedLevel = (window.bonkHost.detectBonkLevelFromPlayerObject && window.bonkHost.detectBonkLevelFromPlayerObject()) || (window.bonkHost.detectBonkLevelFromDom && window.bonkHost.detectBonkLevelFromDom());
	let xpInfo = window.bonkHost.detectBonkXpInfoFromDom ? window.bonkHost.detectBonkXpInfoFromDom() : null;
	let now = Date.now();

	try {
		let me = window.bonkHost.getMyPlayerObjectSafe ? window.bonkHost.getMyPlayerObjectSafe() : null;
		if(me && me.userName && profile.displayName !== me.userName) { profile.displayName = me.userName; changed = true; }
	} catch(e) {}

	let oldLevel = Math.max(1, parseInt(profile.level || profile.lastPublicLevel || 1) || 1);
	let oldBase = window.bonkHost.xpForLevel(oldLevel);
	let oldProgress = Math.max(0, parseInt(profile.xpProgressWithinLevel || ((profile.totalXp || oldBase) - oldBase) || 0) || 0);

	if(detectedLevel && detectedLevel > 0) {
		let newBase = window.bonkHost.xpForLevel(detectedLevel);

		// Bonk often exposes level publicly but not exact total XP.
		// If the level is the same, keep locally tracked progress instead of resetting to level floor.
		// If the level increased publicly, reset progress to 0 unless exact/progress XP below overrides it.
		if(!profile.level || detectedLevel > oldLevel) {
			profile.level = detectedLevel;
			profile.levelBaseXp = newBase;
			profile.xpProgressWithinLevel = 0;
			profile.trackedRoundWinsSinceLevelSync = 0;
			profile.totalXp = newBase;
			changed = true;
		}
		else if(detectedLevel === oldLevel) {
			profile.level = detectedLevel;
			profile.levelBaseXp = newBase;
			profile.xpProgressWithinLevel = oldProgress;
			profile.totalXp = newBase + oldProgress;
			changed = true;
		}
		else if(detectedLevel < oldLevel) {
			// Ignore older/stale public level data; keep saved newer local level/XP.
			detectedLevel = oldLevel;
		}

		profile.lastPublicLevel = detectedLevel;
		profile.lastPublicLevelAt = now;
		profile.levelSource = "bonk-public";
		changed = true;

		if(xpInfo) {
			let resolvedXp = null;
			if(typeof xpInfo.total === "number") {
				// Only trust total XP if it is at least the floor for the current level.
				if(xpInfo.total >= window.bonkHost.xpForLevel(profile.level || detectedLevel)) resolvedXp = xpInfo.total;
			}
			else if(xpInfo.isProgress && typeof xpInfo.current === "number") {
				resolvedXp = window.bonkHost.xpForLevel(profile.level || detectedLevel) + Math.max(0, xpInfo.current);
			}

			if(typeof resolvedXp === "number") {
				// Public exact/progress XP should correct the estimate only when it is newer/higher.
				// Never let a level-only/base read wipe out locally tracked extra XP.
				let currentTotal = profile.totalXp || window.bonkHost.xpForLevel(profile.level || detectedLevel);
				if(resolvedXp >= currentTotal || xpInfo.isProgress) {
					if(window.bonkHost.applyExactXpToClientProfile(profile, resolvedXp, xpInfo.source || "bonk-public")) changed = true;
				}
			}
		}
	}
	else if(xpInfo && typeof xpInfo.total === "number") {
		if(window.bonkHost.applyExactXpToClientProfile(profile, xpInfo.total, xpInfo.source || "bonk-public")) changed = true;
	}

	window.bonkHost.normalizeClientProfileXp(profile, "sync-" + reason);
	if(changed) window.bonkHost.saveClientProfile();
	return profile;
};


window.bonkHost.getXpAwardHistory = (profile) => {
	if(!profile) profile = window.bonkHost.ensureClientProfile();
	if(!Array.isArray(profile.xpAwardHistory)) profile.xpAwardHistory = [];
	let now = Date.now();
	profile.xpAwardHistory = profile.xpAwardHistory
		.map(x => parseInt(x))
		.filter(x => Number.isFinite(x) && x > 0 && now - x <= 24 * 60 * 60 * 1000);
	return profile.xpAwardHistory;
};

window.bonkHost.getXpCapStatus = (profile) => {
	if(!profile) profile = window.bonkHost.ensureClientProfile();
	let now = Date.now();
	let history = window.bonkHost.getXpAwardHistory(profile);
	const WINDOW_20M = 20 * 60 * 1000;
	const WINDOW_24H = 24 * 60 * 60 * 1000;
	const CAP_20M_WINS = 35;   // 3,500 XP / 100 XP per round win
	const CAP_24H_WINS = 180;  // 18,000 XP / 100 XP per round win

	let recent20 = history.filter(t => now - t < WINDOW_20M);
	let recent24 = history.filter(t => now - t < WINDOW_24H);
	let reset20 = recent20.length >= CAP_20M_WINS ? Math.min(...recent20) + WINDOW_20M : 0;
	let reset24 = recent24.length >= CAP_24H_WINS ? Math.min(...recent24) + WINDOW_24H : 0;
	let cappedUntil = Math.max(reset20, reset24);
	let capped = cappedUntil > now;

	profile.xpCap = {
		capped,
		cappedUntil: capped ? cappedUntil : 0,
		reason: capped ? (reset24 >= reset20 ? "Daily cap" : "20-minute cap") : "Not capped",
		recent20mWins: recent20.length,
		recent24hWins: recent24.length,
		limit20mWins: CAP_20M_WINS,
		limit24hWins: CAP_24H_WINS,
		lastChecked: now
	};
	return profile.xpCap;
};

window.bonkHost.formatDuration = (ms) => {
	ms = Math.max(0, parseInt(ms) || 0);
	let s = Math.ceil(ms / 1000);
	if(s < 60) return s + "s";
	let m = Math.floor(s / 60);
	let sec = s % 60;
	if(m < 60) return m + "m " + sec + "s";
	let h = Math.floor(m / 60);
	let min = m % 60;
	if(h < 24) return h + "h " + min + "m";
	let d = Math.floor(h / 24);
	return d + "d " + (h % 24) + "h";
};

window.bonkHost.recordXpAwardTimestamp = (profile) => {
	if(!profile) profile = window.bonkHost.ensureClientProfile();
	let history = window.bonkHost.getXpAwardHistory(profile);
	history.push(Date.now());
	profile.xpAwardHistory = history;
	window.bonkHost.getXpCapStatus(profile);
	return profile.xpCap;
};

window.bonkHost.showXpCapStatus = () => {
	let profile = window.bonkHost.ensureClientProfile();
	let cap = window.bonkHost.getXpCapStatus(profile);
	let lines = [
		"XP CAPPED: " + (cap.capped ? "YES" : "NO"),
		"Reason: " + cap.reason,
		"Time Until Uncapped: " + (cap.capped ? window.bonkHost.formatDuration(cap.cappedUntil - Date.now()) : "Now"),
		"20-Minute XP: " + ((cap.recent20mWins || 0) * 100).toLocaleString() + " / 3,500",
		"Daily XP: " + ((cap.recent24hWins || 0) * 100).toLocaleString() + " / 18,000",
		"",
		"Note: Trapi's Client still tracks wins/games while capped. It only blocks local XP increases when the cap estimate says Bonk would not award XP."
	];
	window.bonkHost.showDatabasePanel("XP CAP STATUS", lines);
};

window.bonkHost.getBonkXpProfile = () => {
	let profile = window.bonkHost.syncClientProfileFromPublicBonk ? window.bonkHost.syncClientProfileFromPublicBonk("profile-open") : window.bonkHost.ensureClientProfile();

	// No more repeated forced XP prompts. If Bonk has not exposed exact XP yet, keep the saved local estimate.
	if(!profile.level) profile.level = 1;
	window.bonkHost.normalizeClientProfileXp(profile, "profile-open");
	window.bonkHost.saveClientProfile();

	let level = profile.level || 1;
	let totalXp = profile.totalXp || window.bonkHost.xpForLevel(level);
	let nextXp = window.bonkHost.xpForNextLevel(level);
	let remaining = Math.max(0, nextXp - totalXp);
	let cap = window.bonkHost.getXpCapStatus ? window.bonkHost.getXpCapStatus(profile) : { capped: false };
	return {
		level,
		totalXp,
		nextXp,
		remaining,
		roundWinsUntilNext: Math.ceil(remaining / 100),
		xpProgressWithinLevel: profile.xpProgressWithinLevel || 0,
		trackedRoundWinsSinceLevelSync: profile.trackedRoundWinsSinceLevelSync || 0,
		joinedDate: profile.joinedDate,
		displayName: profile.displayName || "ME",
		levelSource: profile.levelSource || "saved/local",
		xpSource: profile.xpSource || "saved/local-estimate",
		lastPublicLevelAt: profile.lastPublicLevelAt || null,
		xpCap: cap
	};
};

window.bonkHost.updateProfileXpAfterWin = () => {
	let profile = window.bonkHost.ensureClientProfile();
	window.bonkHost.normalizeClientProfileXp(profile, "before-win");

	let cap = window.bonkHost.getXpCapStatus ? window.bonkHost.getXpCapStatus(profile) : { capped: false };
	if(cap && cap.capped) {
		profile.xpSource = "xp-cap-blocked";
		profile.lastXpCapBlockedAt = Date.now();
		profile.lastXpCapBlockedUntil = cap.cappedUntil || 0;
		window.bonkHost.saveClientProfile();

		// Still sync after the round. If Bonk exposes real XP and it moved anyway, public data can correct us.
		setTimeout(() => { try { window.bonkHost.syncClientProfileFromPublicBonk && window.bonkHost.syncClientProfileFromPublicBonk("after-capped-win"); } catch(e) {} }, 1200);
		return;
	}

	profile.xpProgressWithinLevel = Math.max(0, parseInt(profile.xpProgressWithinLevel || 0) || 0) + 100;
	profile.trackedRoundWinsSinceLevelSync = Math.max(0, parseInt(profile.trackedRoundWinsSinceLevelSync || 0) || 0) + 1;
	profile.xpSource = "local-round-wins";
	profile.lastLocalXpUpdate = Date.now();
	if(window.bonkHost.recordXpAwardTimestamp) window.bonkHost.recordXpAwardTimestamp(profile);

	window.bonkHost.normalizeClientProfileXp(profile, "after-win");
	window.bonkHost.saveClientProfile();

	// If Bonk exposes a fresher public exact/progress value after the game updates, it can correct the local estimate.
	// Level-only reads will not reset exact local progress anymore.
	setTimeout(() => { try { window.bonkHost.syncClientProfileFromPublicBonk && window.bonkHost.syncClientProfileFromPublicBonk("after-win"); } catch(e) {} }, 1200);
};

if(!window.bonkHost.publicProfileAutoSyncStarted) {
	window.bonkHost.publicProfileAutoSyncStarted = true;
	setInterval(() => {
		try { window.bonkHost.syncClientProfileFromPublicBonk && window.bonkHost.syncClientProfileFromPublicBonk("timer"); }
		catch(e) {}
	}, 2500);
}

window.bonkHost.getMatchCategoryKey = () => {
	try {
		let gs = window.bonkHost.toolFunctions && window.bonkHost.toolFunctions.getGameSettings ? window.bonkHost.toolFunctions.getGameSettings() : null;
		if(gs && gs.q) return "quickplay";
		// Only count Team Game stats when Bonk team mode is actually enabled.
		if(gs && gs.tea) return "team";

		let active = (window.bonkHost.players || []).filter(p => p && p.userName && p.team !== 0);
		// Non-team games with exactly 2 active players count as 1v1s.
		if(active.length === 2) return "onevone";
		// Non-team games with 3+ active players count as Free For All.
		if(active.length > 2) return "custom";
	} catch(e) {}
	// Not enough information to safely categorize.
	return "unknown";
};

window.bonkHost.normalizeStatsBuckets = (rec) => {
	if(!rec) rec = {};
	for(let key of ["custom", "quickplay", "onevone", "team", "total"]) {
		if(!rec[key]) rec[key] = { wins: 0, games: 0 };
		if(typeof rec[key].wins !== "number") rec[key].wins = 0;
		if(typeof rec[key].games !== "number") rec[key].games = 0;
	}
	if(typeof rec.wins !== "number") rec.wins = rec.total.wins || 0;
	if(typeof rec.games !== "number") rec.games = rec.total.games || 0;
	return rec;
};

// Override old normalizer so new database buckets are always available.
window.bonkHost.normalizePlayerHistoryRecord = window.bonkHost.normalizeStatsBuckets;

window.bonkHost.mergeHistoryRecords = (names) => {
	let ex = window.bonkHost.extraFeatures || {};
	let merged = window.bonkHost.normalizeStatsBuckets({});
	merged.joins = 0;
	merged.lastJoin = null;
	merged.lastSeen = null;
	merged.lastLeave = null;
	for(let name of names) {
		let rec = (ex.playerHistory || {})[name];
		if(!rec) continue;
		window.bonkHost.normalizeStatsBuckets(rec);
		for(let key of ["custom", "quickplay", "onevone", "team", "total"]) {
			merged[key].wins += rec[key].wins || 0;
			merged[key].games += rec[key].games || 0;
		}
		merged.joins += rec.joins || 0;
		for(let t of ["lastJoin", "lastSeen", "lastLeave"]) if(rec[t] && (!merged[t] || rec[t] > merged[t])) merged[t] = rec[t];
	}
	merged.wins = merged.total.wins;
	merged.games = merged.total.games;
	return merged;
};

window.bonkHost.getLinkedNamesFor = (name) => {
	let ex = window.bonkHost.extraFeatures || {};
	let main = window.bonkHost.getCanonicalPlayerName(name);
	let linked = [main].concat((ex.accountLinks && ex.accountLinks[main]) || []);
	return [...new Set(linked.filter(Boolean))];
};

window.bonkHost.addPlayerHistoryGameByMode = (rec, mode, games, wins) => {
	rec = window.bonkHost.normalizeStatsBuckets(rec);
	let category = mode === "quickplay" ? "quickplay" : (window.bonkHost.getMatchCategoryKey ? window.bonkHost.getMatchCategoryKey() : "unknown");
	games = Math.max(0, games || 0);
	wins = Math.max(0, wins || 0);

	// Category rules:
	// quickplay = Bonk quickplay/classic quickplay
	// team = team mode enabled
	// onevone = non-team with exactly 2 active players
	// custom = Free For All, non-team with 3+ active players
	// unknown = only total, no specific category
	if(["quickplay", "team", "onevone", "custom"].includes(category)) {
		rec[category].games += games;
		rec[category].wins += wins;
	}
	rec.total.games += games;
	rec.total.wins += wins;
	rec.games = rec.total.games;
	rec.wins = rec.total.wins;
	return rec;
};

window.bonkHost.getPlayerHistoryRecord = (name) => {
	let ex = window.bonkHost.extraFeatures || {};
	if(!ex.playerHistory) ex.playerHistory = JSON.parse(localStorage.getItem("bonkHostPlayerHistory") || "{}");
	let key = window.bonkHost.getCanonicalPlayerName ? window.bonkHost.getCanonicalPlayerName(name || "UNKNOWN") : (name || "UNKNOWN").trim();
	if(!ex.playerHistory[key]) {
		ex.playerHistory[key] = { joins: 0, wins: 0, games: 0, lastJoin: null, lastSeen: null, lastLeave: null };
	}
	window.bonkHost.normalizeStatsBuckets(ex.playerHistory[key]);
	return ex.playerHistory[key];
};

window.bonkHost.recordCareerGame = (won, mode) => {
	let ex = window.bonkHost.extraFeatures || {};
	if(ex.careerStatsEnabled === false) return;
	let myId = window.bonkHost.getMyPlayerId ? window.bonkHost.getMyPlayerId() : -1;
	let me = window.bonkHost.players && window.bonkHost.players[myId] ? window.bonkHost.players[myId] : null;
	let name = window.bonkHost.getCanonicalPlayerName(me && me.userName ? me.userName : "ME");
	let rec = window.bonkHost.getCareerStatsRecord(name);
	window.bonkHost.normalizeStatsBuckets(rec);
	let category = mode === "quickplay" ? "quickplay" : (window.bonkHost.getMatchCategoryKey ? window.bonkHost.getMatchCategoryKey() : "unknown");

	if(["quickplay", "team", "onevone", "custom"].includes(category)) rec[category].games += 1;
	rec.total.games += 1;
	if(won) {
		if(["quickplay", "team", "onevone", "custom"].includes(category)) rec[category].wins += 1;
		rec.total.wins += 1;
		rec.currentStreak = (rec.currentStreak || 0) + 1;
		rec.bestStreak = Math.max(rec.bestStreak || 0, rec.currentStreak);
		window.bonkHost.updateProfileXpAfterWin && window.bonkHost.updateProfileXpAfterWin();
	} else rec.currentStreak = 0;
	rec.lastSeen = Date.now();
	window.bonkHost.saveCareerStats();
};

window.bonkHost.showDatabasePanel = (title, lines) => {
	let panel = document.getElementById("hostPlayerMenuDatabasePanel");
	let titleEl = document.getElementById("hostPlayerMenuDatabasePanelTitle");
	let bodyEl = document.getElementById("hostPlayerMenuDatabasePanelBody");
	if(panel && titleEl && bodyEl) {
		panel.style.display = "block";
		titleEl.textContent = title || "PLAYER DATABASE";
		bodyEl.textContent = (lines || []).join("\n");
	}
	if(window.bonkHost.showHelpMessages) window.bonkHost.showHelpMessages(title || "PLAYER DATABASE", lines || []);
};

window.bonkHost.statLine = (label, rec) => {
	rec = rec || { wins: 0, games: 0 };
	return label + ": " + (rec.wins || 0) + "W / " + (rec.games || 0) + "G / " + window.bonkHost.formatWR(rec.wins || 0, rec.games || 0);
};

window.bonkHost.showMyProfile = () => {
	let xp = window.bonkHost.getBonkXpProfile();
	let name = xp.displayName;
	let career = window.bonkHost.getCareerStatsRecord ? window.bonkHost.getCareerStatsRecord(window.bonkHost.getCanonicalPlayerName(name)) : null;
	window.bonkHost.normalizeStatsBuckets(career);
	let profile = window.bonkHost.ensureClientProfile();
	let lines = [
		"Name: " + name,
		"Client Joined: " + new Date(xp.joinedDate).toLocaleDateString(),
		"Joined Version: " + (profile.joinedVersion || "8.1"),
		"Level: " + xp.level,
		"Total XP: " + xp.totalXp.toLocaleString(),
		"XP This Level: " + (xp.xpProgressWithinLevel || 0).toLocaleString(),
		"Tracked Round Wins This Level: " + (xp.trackedRoundWinsSinceLevelSync || 0),
		"Next Level XP: " + xp.nextXp.toLocaleString(),
		"XP Until Next Level: " + xp.remaining.toLocaleString(),
		"Round Wins Until Next Level: " + xp.roundWinsUntilNext,
		"Level Source: " + (xp.levelSource || "saved/local"),
		"XP Source: " + (xp.xpSource || "saved/local"),
		"XP CAPPED: " + (xp.xpCap && xp.xpCap.capped ? "YES" : "NO"),
		"XP Cap Reset: " + (xp.xpCap && xp.xpCap.capped ? window.bonkHost.formatDuration(xp.xpCap.cappedUntil - Date.now()) : "Not capped"),
		"20m XP Window: " + (xp.xpCap ? ((xp.xpCap.recent20mWins || 0) * 100).toLocaleString() + " / 3,500" : "Unknown"),
		"Daily XP Window: " + (xp.xpCap ? ((xp.xpCap.recent24hWins || 0) * 100).toLocaleString() + " / 18,000" : "Unknown"),
		"Last Public Level Sync: " + (xp.lastPublicLevelAt ? window.bonkHost.formatTimeAgo(xp.lastPublicLevelAt) : "Waiting for Bonk data"),
		"",
		window.bonkHost.statLine("Total Games Won", career.total),
		window.bonkHost.statLine("Classic Quickplay", career.quickplay),
		window.bonkHost.statLine("1v1s", career.onevone),
		window.bonkHost.statLine("Team Games", career.team),
		window.bonkHost.statLine("Free For All", career.custom),
		"Current Streak: " + (career.currentStreak || 0),
		"Best Streak: " + (career.bestStreak || 0),
		"Last Seen: " + window.bonkHost.formatTimeAgo(career.lastSeen),
		"",
		"Note: Level auto-syncs from Bonk public data, but exact XP now keeps locally tracked progress above that level floor. If your current XP was already ahead before installing this version, use Player Database > Set Exact XP once, then the client will keep adding +100 XP per round win unless the XP cap is active. Stat categories use current match rules: Team only when teams are enabled, 1v1 only with exactly 2 active players, and Free For All with 3+ active non-team players."
	];
	window.bonkHost.showDatabasePanel("MY PROFILE", lines);
};

window.bonkHost.showPlayerLookup = () => {
	let suggested = "";
	try {
		let players = (window.bonkHost.players || []).filter(p => p && p.userName).map(p => p.userName);
		suggested = players[0] || "";
	} catch(e) {}
	let name = prompt("Player name to look up:", suggested);
	if(!name) return;
	let realName = window.bonkHost.getCanonicalPlayerName(name.trim());
	let linked = window.bonkHost.getLinkedNamesFor(realName);
	let history = window.bonkHost.mergeHistoryRecords(linked);
	let ex = window.bonkHost.extraFeatures || {};
	let player = (window.bonkHost.players || []).find(p => p && p.userName && linked.map(x=>x.toLowerCase()).includes(p.userName.toLowerCase()));
	let note = ex.playerNotes && ex.playerNotes[realName] ? ex.playerNotes[realName] : "None";
	let lines = [
		"Name: " + realName,
		"Linked Accounts: " + (linked.length > 1 ? linked.join(", ") : "None"),
		"Status: " + (player ? "In Room" : "Not in room"),
		"Team: " + (player ? window.bonkHost.getTeamLabel(player.team) : "Not in room"),
		"Guest: " + (player ? (player.guest ? "YES" : "NO") : "UNKNOWN"),
		"",
		window.bonkHost.statLine("All Games With You", history.total),
		window.bonkHost.statLine("Classic Quickplay", history.quickplay),
		window.bonkHost.statLine("1v1s", history.onevone),
		window.bonkHost.statLine("Team Games", history.team),
		window.bonkHost.statLine("Free For All", history.custom),
		"Join Count: " + (history.joins || 0),
		"First Tracked: " + (history.firstSeen ? new Date(history.firstSeen).toLocaleDateString() : "Unknown"),
		"Last Joined: " + window.bonkHost.formatTimeAgo(history.lastJoin),
		"Last Seen: " + (player ? "In Room" : window.bonkHost.formatTimeAgo(history.lastSeen || history.lastLeave)),
		"Last Left: " + (history.lastLeave ? window.bonkHost.formatTimeAgo(history.lastLeave) : "Never"),
		"Note: " + note
	];
	window.bonkHost.showDatabasePanel("PLAYER LOOKUP", lines);
};

window.bonkHost.showPlayerCard = window.bonkHost.showPlayerLookup;
window.bonkHost.showMyCareerStats = window.bonkHost.showMyProfile;

window.bonkHost.showRecentPlayers = () => {
	let ex = window.bonkHost.extraFeatures || {};
	let rows = Object.entries(ex.playerHistory || {})
		.map(([name, rec]) => ({name, rec: window.bonkHost.normalizeStatsBuckets(rec), t: rec.lastSeen || rec.lastJoin || 0}))
		.sort((a,b)=>b.t-a.t)
		.slice(0, 12)
		.map(x => x.name + " — " + window.bonkHost.formatTimeAgo(x.t) + " — " + (x.rec.total.games || 0) + " games");
	window.bonkHost.showDatabasePanel("RECENT PLAYERS", rows.length ? rows : ["No tracked players yet."]);
};

window.bonkHost.showTopPlayedWith = () => {
	let ex = window.bonkHost.extraFeatures || {};
	let rows = Object.entries(ex.playerHistory || {})
		.map(([name, rec]) => ({name, rec: window.bonkHost.normalizeStatsBuckets(rec)}))
		.sort((a,b)=>(b.rec.total.games||0)-(a.rec.total.games||0))
		.slice(0, 12)
		.map((x,i) => (i+1) + ". " + x.name + " — " + (x.rec.total.games || 0) + " games — " + window.bonkHost.formatWR(x.rec.total.wins || 0, x.rec.total.games || 0));
	window.bonkHost.showDatabasePanel("TOP PLAYED WITH", rows.length ? rows : ["No tracked games yet."]);
};

window.bonkHost.showTopWinrate = () => {
	let ex = window.bonkHost.extraFeatures || {};
	let rows = Object.entries(ex.playerHistory || {})
		.map(([name, rec]) => ({name, rec: window.bonkHost.normalizeStatsBuckets(rec)}))
		.filter(x => (x.rec.total.games || 0) >= 5)
		.sort((a,b)=>((b.rec.total.wins||0)/(b.rec.total.games||1))-((a.rec.total.wins||0)/(a.rec.total.games||1)))
		.slice(0, 12)
		.map((x,i) => (i+1) + ". " + x.name + " — " + window.bonkHost.formatWR(x.rec.total.wins || 0, x.rec.total.games || 0) + " — " + (x.rec.total.games || 0) + " games");
	window.bonkHost.showDatabasePanel("TOP WINRATE", rows.length ? rows : ["No players with 5+ tracked games yet."]);
};

window.bonkHost.linkAccountsPrompt = () => {
	let main = prompt("Main account name:");
	if(!main) return;
	let alt = prompt("Alt / linked account name:");
	if(!alt) return;
	main = main.trim();
	alt = alt.trim();
	if(!main || !alt || main.toLowerCase() === alt.toLowerCase()) return;
	let ex = window.bonkHost.extraFeatures || {};
	if(!ex.accountLinks) ex.accountLinks = {};
	let canonicalMain = window.bonkHost.getCanonicalPlayerName(main);
	if(canonicalMain !== main && ex.accountLinks[canonicalMain]) main = canonicalMain;
	if(!ex.accountLinks[main]) ex.accountLinks[main] = [];
	if(!ex.accountLinks[main].some(x => x.toLowerCase() === alt.toLowerCase())) ex.accountLinks[main].push(alt);
	window.bonkHost.saveAccountLinks();
	window.bonkHost.rebuildAccountLinkIndex();
	if(window.bonkHost.menuFunctions) window.bonkHost.menuFunctions.showStatusMessage("* Linked " + alt + " to " + main, "#b53030", false);
	window.bonkHost.showAccountLinks();
};

window.bonkHost.showAccountLinks = () => {
	let ex = window.bonkHost.extraFeatures || {};
	let links = ex.accountLinks || {};
	let rows = Object.keys(links).length ? Object.keys(links).map(main => main + " ← " + (links[main] || []).join(", ")) : ["No linked accounts yet."];
	window.bonkHost.showDatabasePanel("ACCOUNT LINKS", rows);
};

window.bonkHost.showDatabaseHealth = () => {
	let ex = window.bonkHost.extraFeatures || {};
	let playerCount = Object.keys(ex.playerHistory || {}).length;
	let careerCount = Object.keys(ex.careerStats || {}).length;
	let linkCount = Object.values(ex.accountLinks || {}).reduce((a,b)=>a+(b||[]).length,0);
	let profile = window.bonkHost.ensureClientProfile();
	let lines = [
		"Client Version: 8.2",
		"Profile Created: " + new Date(profile.joinedDate).toLocaleDateString(),
		"Tracked Players: " + playerCount,
		"Career Profiles: " + careerCount,
		"Linked Alts: " + linkCount,
		"Packet Monitor: " + ((ex.packetMonitor && ex.packetMonitor.lastPacketType) || "waiting"),
		"Active Socket: " + (ex.activeSocket ? "detected" : "not detected yet"),
		"Global Player History: " + (ex.globalPlayerHistory === false ? "OFF" : "ON"),
		"Career Stats: " + (ex.careerStatsEnabled === false ? "OFF" : "ON")
	];
	window.bonkHost.showDatabasePanel("DATABASE HEALTH", lines);
};

// Improve packet monitoring by wrapping WebSocket.send too, so outgoing packets and active socket are known even if Bonk does not use addEventListener.
window.bonkHost.patchPacketMonitorSend = () => {
	if(window.bonkHost.packetMonitorSendPatched) return;
	window.bonkHost.packetMonitorSendPatched = true;
	const originalSend = WebSocket.prototype.send;
	WebSocket.prototype.send = function(data) {
		try {
			if(window.bonkHost && window.bonkHost.extraFeatures) window.bonkHost.extraFeatures.activeSocket = this;
			let packet = window.bonkHost.parseSocketPacket ? window.bonkHost.parseSocketPacket(data) : null;
			if(packet && window.bonkHost.handleObservedPacket) window.bonkHost.handleObservedPacket("out", packet);
		} catch(e) {}
		return originalSend.apply(this, arguments);
	};
};
window.bonkHost.patchPacketMonitorSend();


/* v8.2 Client UX + Social tools */
(function(){
	if(!window.bonkHost) return;
	const safeJSON = (x, fallback) => { try { return JSON.parse(x); } catch(e) { return fallback; } };
	const ex = window.bonkHost.extraFeatures = window.bonkHost.extraFeatures || {};
	ex.savedRoomWatch = safeJSON(localStorage.getItem("trapisClientSavedRooms") || "[]", []);
	ex.savedRoomWatchData = safeJSON(localStorage.getItem("trapisClientSavedRoomData") || "{}", {});
	ex.savedRoomNotified = ex.savedRoomNotified || {};
	ex.privateChat = ex.privateChat || {
		target: null,
		targetKey: null,
		lastHandshake: 0,
		pmUsers: [],
		ignore: safeJSON(localStorage.getItem("trapisClientPrivateChatIgnore") || "[]", [])
	};

	window.bonkHost.injectClientUiCss = () => {
		if(document.getElementById("trapisClientV82Css")) return;
		let st = document.createElement("style");
		st.id = "trapisClientV82Css";
		st.textContent = `
			#hostPlayerMenu { min-width: 190px !important; max-width: 245px !important; width: 218px !important; }
			#hostPlayerMenuControls { max-height: 70vh; overflow-y: auto; overflow-x: hidden; }
			#hostPlayerMenuControls .newbonklobby_settings_button {
				box-sizing: border-box !important;
				white-space: normal !important;
				overflow-wrap: anywhere !important;
				line-height: 15px !important;
				height: auto !important;
				min-height: 31px !important;
				padding: 6px 7px !important;
				display: flex !important;
				align-items: center !important;
				justify-content: center !important;
			}
			#hostPlayerMenuMainDropdown,
			#hostPlayerMenuTeamToolsDropdown,
			#hostPlayerMenuHostToolsDropdown,
			#hostPlayerMenuPlaylistsDropdown,
			#hostPlayerMenuStatsDropdown,
			#hostPlayerMenuPlayersDropdown,
			#hostPlayerMenuDatabaseDropdown,
			#hostPlayerMenuSocialDropdown,
			#hostPlayerMenuSettingsDropdown,
			#hostPlayerMenuHelpDropdown,
			#hostPlayerMenuPatchNotesDropdown {
				width: 100% !important;
				box-sizing: border-box !important;
			}
			#hostPlayerMenuPatchNotesDropdown .hostPlayerMenuDropdownItem,
			#hostPlayerMenuHelpDropdown .hostPlayerMenuDropdownItem,
			#hostPlayerMenuRoadmapButton {
				text-align: left !important;
				justify-content: flex-start !important;
				text-transform: none !important;
				font-size: 12px !important;
				line-height: 15px !important;
				padding: 7px 9px !important;
			}
			#hostPlayerMenuDatabasePanel {
				max-height: 340px !important;
				overflow-y: auto !important;
				overflow-x: hidden !important;
				word-break: break-word !important;
			}
			#hostPlayerMenuSocialToggle,
			#hostPlayerMenuSaveRoomButton,
			#hostPlayerMenuSavedRoomsButton,
			#hostPlayerMenuClearSavedRoomsButton,
			#hostPlayerMenuPrivateChatButton,
			#hostPlayerMenuPrivateChatUsersButton {
				width: 100%;
				border-width: 0 !important;
			}
			#hostPlayerMenuSocialToggle {
				background-color: #6f4f42 !important;
				color: #ffffff !important;
				text-align: center !important;
				font-size: 17px !important;
				font-weight: 700 !important;
				text-transform: uppercase;
			}
			#hostPlayerMenuSocialDropdown {
				display: none;
				width: 100%;
			}
			#hostPlayerMenuSocialDropdown .hostPlayerMenuDropdownItem {
				background-color: #8b6f61 !important;
				color: #ffffff !important;
				box-sizing: border-box;
				text-align: left !important;
				justify-content: flex-start !important;
				padding-left: 10px !important;
				font-size: 14px !important;
				font-weight: 600 !important;
				font-family: futurept_b1, Arial, sans-serif;
				text-transform: uppercase;
			}
		`;
		document.head.appendChild(st);
	};
	window.bonkHost.injectClientUiCss();

	window.bonkHost.makeClientButton = (id, text) => {
		let d = document.createElement("div");
		d.className = "newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem";
		d.id = id;
		d.textContent = text;
		return d;
	};

	window.bonkHost.ensureSocialMenu = () => {
		if(document.getElementById("hostPlayerMenuSocialToggle")) return;
		let controls = document.getElementById("hostPlayerMenuControls");
		let main = document.getElementById("hostPlayerMenuMainDropdown") || controls;
		if(!main) return;
		let toggle = window.bonkHost.makeClientButton("hostPlayerMenuSocialToggle", "SOCIAL ▼");
		toggle.classList.remove("hostPlayerMenuDropdownItem");
		let drop = document.createElement("div");
		drop.id = "hostPlayerMenuSocialDropdown";
		drop.appendChild(window.bonkHost.makeClientButton("hostPlayerMenuPrivateChatButton", "PRIVATE CHAT"));
		drop.appendChild(window.bonkHost.makeClientButton("hostPlayerMenuPrivateChatUsersButton", "PRIVATE CHAT USERS"));
		drop.appendChild(window.bonkHost.makeClientButton("hostPlayerMenuSaveRoomButton", "SAVE ROOM WATCH"));
		drop.appendChild(window.bonkHost.makeClientButton("hostPlayerMenuSavedRoomsButton", "SAVED ROOMS"));
		drop.appendChild(window.bonkHost.makeClientButton("hostPlayerMenuClearSavedRoomsButton", "CLEAR SAVED ROOMS"));

		let settings = document.getElementById("hostPlayerMenuSettingsToggle");
		if(settings && settings.parentNode === main) {
			main.insertBefore(drop, settings);
			main.insertBefore(toggle, drop);
		} else {
			main.appendChild(toggle);
			main.appendChild(drop);
		}
		toggle.addEventListener("click", () => {
			let open = window.getComputedStyle(drop).display !== "none";
			drop.style.display = open ? "none" : "block";
			toggle.textContent = open ? "SOCIAL ▼" : "SOCIAL ▲";
			window.bonkHost.updateHostPlayerMenuDropdownHeight && window.bonkHost.updateHostPlayerMenuDropdownHeight();
		});
		document.getElementById("hostPlayerMenuPrivateChatButton").addEventListener("click", () => window.bonkHost.startPrivateChatPrompt());
		document.getElementById("hostPlayerMenuPrivateChatUsersButton").addEventListener("click", () => window.bonkHost.requestPrivateChatUsers());
		document.getElementById("hostPlayerMenuSaveRoomButton").addEventListener("click", () => window.bonkHost.saveCurrentRoomWatchPrompt());
		document.getElementById("hostPlayerMenuSavedRoomsButton").addEventListener("click", () => window.bonkHost.showSavedRooms());
		document.getElementById("hostPlayerMenuClearSavedRoomsButton").addEventListener("click", () => window.bonkHost.clearSavedRooms());
	};
	window.bonkHost.ensureSocialMenu();

	const oldCollapse = window.bonkHost.collapseAllHostMenuTabs;
	window.bonkHost.collapseAllHostMenuTabs = () => {
		try { oldCollapse && oldCollapse(); } catch(e) {}
		const extra = [
			["hostPlayerMenuSocialDropdown", "hostPlayerMenuSocialToggle", "SOCIAL ▼"],
			["hostPlayerMenuDatabasePanel", null, null]
		];
		for(const [dropId, togId, text] of extra) {
			let d = document.getElementById(dropId);
			let t = togId ? document.getElementById(togId) : null;
			if(d) d.style.display = "none";
			if(t && text) t.textContent = text;
		}
	};
	const oldHeight = window.bonkHost.updateHostPlayerMenuDropdownHeight;
	window.bonkHost.updateHostPlayerMenuDropdownHeight = () => {
		try { oldHeight && oldHeight(); } catch(e) {}
		let box = document.getElementById("hostPlayerMenuBox");
		if(box) box.style.maxHeight = "calc(47px * 5)";
	};

	window.bonkHost.clientStatus = (msg) => {
		try {
			if(window.bonkHost.menuFunctions && window.bonkHost.menuFunctions.showStatusMessage) window.bonkHost.menuFunctions.showStatusMessage("* " + msg, "#b53030", false);
			else console.log("[Trapi's Client] " + msg);
		} catch(e) { console.log("[Trapi's Client] " + msg); }
	};

	window.bonkHost.escapeClientText = (s) => String(s ?? "").replace(/[&<>]/g, ch => ({'&':'&amp;','<':'&lt;','>':'&gt;'}[ch]));
	window.bonkHost.appendClientChatLine = (prefix, message, color="#7aff7a") => {
		let htmlPrefix = `<span style="color:${color};font-weight:bold;">${window.bonkHost.escapeClientText(prefix)}</span> `;
		let htmlMessage = window.bonkHost.escapeClientText(message);
		for(const id of ["newbonklobby_chat_content", "ingamechatcontent"]) {
			let cont = document.getElementById(id);
			if(!cont) continue;
			let row = document.createElement("div");
			row.style.parsed = true;
			row.innerHTML = htmlPrefix + htmlMessage;
			cont.appendChild(row);
			cont.scrollTop = cont.scrollHeight;
		}
	};

	window.bonkHost.getSelfName = () => {
		try {
			let myId = window.bonkHost.getMyPlayerId ? window.bonkHost.getMyPlayerId() : -1;
			let p = window.bonkHost.players && window.bonkHost.players[myId];
			if(p && p.userName) return p.userName;
		} catch(e) {}
		try {
			let profile = window.bonkHost.ensureClientProfile && window.bonkHost.ensureClientProfile();
			if(profile && profile.name) return profile.name;
		} catch(e) {}
		return localStorage.getItem("trapisClientLastUsername") || prompt("Your Bonk username for private chat:") || "Unknown";
	};

	window.bonkHost.sendClientPayload = (obj) => {
		try {
			let sock = window.bonkHost.extraFeatures && window.bonkHost.extraFeatures.activeSocket;
			if(sock && sock.readyState === WebSocket.OPEN) {
				sock.send("42" + JSON.stringify([4, obj]));
				return true;
			}
		} catch(e) {}
		window.bonkHost.clientStatus("No active socket yet. Join a room first.");
		return false;
	};

	window.bonkHost.str2ab = (str) => {
		const buf = new ArrayBuffer(str.length);
		const view = new Uint8Array(buf);
		for(let i=0;i<str.length;i++) view[i] = str.charCodeAt(i);
		return buf;
	};
	window.bonkHost.ab2str = (buffer) => String.fromCharCode.apply(null, new Uint8Array(buffer));
	window.bonkHost.ensurePrivateChatKeys = async () => {
		let pc = window.bonkHost.extraFeatures.privateChat;
		if(pc.privateKey && pc.publicKey) return pc;
		let keys = await crypto.subtle.generateKey({name:"RSA-OAEP", modulusLength:2048, publicExponent:new Uint8Array([1,0,1]), hash:{name:"SHA-256"}}, true, ["encrypt","decrypt"]);
		pc.privateKey = keys.privateKey;
		pc.publicKey = keys.publicKey;
		return pc;
	};
	window.bonkHost.exportPublicKey = async () => {
		let pc = await window.bonkHost.ensurePrivateChatKeys();
		let raw = await crypto.subtle.exportKey("spki", pc.publicKey);
		return btoa(window.bonkHost.ab2str(raw));
	};
	window.bonkHost.importPublicKey = async (key) => {
		let pc = await window.bonkHost.ensurePrivateChatKeys();
		return crypto.subtle.importKey("spki", window.bonkHost.str2ab(atob(key)), pc.publicKey.algorithm, true, ["encrypt"]);
	};
	window.bonkHost.encryptPrivateMessage = async (publicKey, text) => {
		let enc = await crypto.subtle.encrypt({name:"RSA-OAEP"}, publicKey, new TextEncoder().encode(text));
		return btoa(window.bonkHost.ab2str(enc));
	};
	window.bonkHost.decryptPrivateMessage = async (cipher) => {
		let pc = await window.bonkHost.ensurePrivateChatKeys();
		let dec = await crypto.subtle.decrypt({name:"RSA-OAEP"}, pc.privateKey, window.bonkHost.str2ab(atob(cipher)));
		return new TextDecoder().decode(dec);
	};

	window.bonkHost.startPrivateChatPrompt = () => {
		let target = prompt("Private chat with which player?");
		if(!target) return;
		target = target.trim();
		if(!target) return;
		window.bonkHost.connectPrivateChat(target, true);
	};

	window.bonkHost.connectPrivateChat = async (target, askMessage=false) => {
		await window.bonkHost.ensurePrivateChatKeys();
		let pc = window.bonkHost.extraFeatures.privateChat;
		pc.target = target;
		pc.targetKey = null;
		pc.lastHandshake = Date.now();
		let from = window.bonkHost.getSelfName();
		localStorage.setItem("trapisClientLastUsername", from);
		window.bonkHost.sendClientPayload({type:"request public key", from, to:target});
		window.bonkHost.clientStatus("Requesting private chat key from " + target + "...");
		if(askMessage) {
			setTimeout(async () => {
				let msg = prompt("Message to " + target + ":");
				if(msg) await window.bonkHost.sendPrivateChatMessage(msg);
			}, 1500);
		}
	};

	window.bonkHost.sendPrivateChatMessage = async (message) => {
		let pc = window.bonkHost.extraFeatures.privateChat;
		if(!pc.target) { window.bonkHost.startPrivateChatPrompt(); return; }
		if(!pc.targetKey) { await window.bonkHost.connectPrivateChat(pc.target, false); window.bonkHost.clientStatus("Connecting first. Try the message again in a second."); return; }
		let from = window.bonkHost.getSelfName();
		let cipher = await window.bonkHost.encryptPrivateMessage(pc.targetKey, message);
		if(window.bonkHost.sendClientPayload({type:"private chat", from, to:pc.target, message:cipher})) {
			window.bonkHost.appendClientChatLine("[PM to " + pc.target + "]", message, "#42f56f");
		}
	};

	window.bonkHost.requestPrivateChatUsers = () => {
		let pc = window.bonkHost.extraFeatures.privateChat;
		pc.pmUsers = [];
		pc.pmUsersTime = Date.now();
		let from = window.bonkHost.getSelfName();
		window.bonkHost.sendClientPayload({type:"request private chat users", from});
		setTimeout(() => {
			let list = pc.pmUsers.length ? pc.pmUsers.join(", ") : "No compatible users answered.";
			if(window.bonkHost.showDatabasePanel) window.bonkHost.showDatabasePanel("PRIVATE CHAT USERS", [list]);
			else alert(list);
		}, 1600);
	};

	window.bonkHost.handlePrivateChatPayload = async (packet) => {
		if(!Array.isArray(packet) || packet[0] !== 7) return;
		let payload = packet[2];
		if(!payload || typeof payload !== "object" || payload.i !== undefined) return;
		let myName = window.bonkHost.getSelfName();
		let pc = window.bonkHost.extraFeatures.privateChat;
		if(payload.type === "request public key" && (!payload.to || payload.to === myName)) {
			let pub = await window.bonkHost.exportPublicKey();
			window.bonkHost.sendClientPayload({type:"public key", from:myName, to:payload.from || "", "public key":pub});
		}
		else if(payload.type === "public key" && payload["public key"]) {
			if(!pc.target || (payload.from && payload.from.toLowerCase() === pc.target.toLowerCase()) || payload.to === myName) {
				try {
					pc.target = payload.from || pc.target;
					pc.targetKey = await window.bonkHost.importPublicKey(payload["public key"]);
					window.bonkHost.clientStatus("Private chat connected with " + pc.target);
				} catch(e) { window.bonkHost.clientStatus("Private chat key import failed."); }
			}
		}
		else if(payload.type === "private chat" && (!payload.to || payload.to === myName) && payload.message) {
			let from = payload.from || "Unknown";
			if(pc.ignore && pc.ignore.map(x=>String(x).toLowerCase()).includes(String(from).toLowerCase())) return;
			try {
				let msg = await window.bonkHost.decryptPrivateMessage(payload.message);
				window.bonkHost.appendClientChatLine("[PM from " + from + "]", msg, "#42f56f");
			} catch(e) {
				let pub = await window.bonkHost.exportPublicKey();
				window.bonkHost.sendClientPayload({type:"public key correction", from:myName, to:from, "public key":pub});
			}
		}
		else if(payload.type === "request private chat users") {
			window.bonkHost.sendClientPayload({type:"private chat users", from:myName, to:payload.from || ""});
		}
		else if(payload.type === "private chat users" && (!payload.to || payload.to === myName)) {
			if(payload.from && !pc.pmUsers.includes(payload.from)) pc.pmUsers.push(payload.from);
		}
	};

	window.bonkHost.bindPrivateChatCommand = () => {
		for(const id of ["newbonklobby_chat_input", "ingamechatinputtext"]) {
			let inp = document.getElementById(id);
			if(!inp || inp.dataset.trapiPrivateChatBound === "1") continue;
			inp.dataset.trapiPrivateChatBound = "1";
			inp.addEventListener("keydown", async (e) => {
				if(e.key !== "Enter") return;
				let val = inp.value || "";
				if(val.startsWith("/msg ")) {
					e.stopImmediatePropagation(); e.preventDefault();
					let msg = val.slice(5);
					inp.value = "";
					await window.bonkHost.sendPrivateChatMessage(msg);
				}
				else if(val.startsWith("/chatw ")) {
					e.stopImmediatePropagation(); e.preventDefault();
					let target = val.slice(7).trim();
					inp.value = "";
					if(target) window.bonkHost.connectPrivateChat(target, false);
				}
				else if(val === "/pmusers") {
					e.stopImmediatePropagation(); e.preventDefault();
					inp.value = "";
					window.bonkHost.requestPrivateChatUsers();
				}
			}, true);
		}
	};
	setInterval(() => { try { window.bonkHost.bindPrivateChatCommand(); } catch(e) {} }, 1500);

	window.bonkHost.saveSavedRooms = () => localStorage.setItem("trapisClientSavedRooms", JSON.stringify(window.bonkHost.extraFeatures.savedRoomWatch || []));
	window.bonkHost.currentRoomAddress = () => {
		try {
			if(window.bonkHost.currentroomaddress && window.bonkHost.currentroomaddress !== -1) return String(window.bonkHost.currentroomaddress);
			if(window.currentroomaddress && window.currentroomaddress !== -1) return String(window.currentroomaddress);
		} catch(e) {}
		return "";
	};
	window.bonkHost.saveCurrentRoomWatchPrompt = () => {
		let addr = window.bonkHost.currentRoomAddress();
		let name = "";
		try { name = document.getElementById("newbonklobby_roomname")?.textContent || ""; } catch(e) {}
		let label = prompt("Save room name/code to watch. If the current room code is detected, it is prefilled below:", addr || name || "");
		if(!label) return;
		label = label.trim();
		let list = window.bonkHost.extraFeatures.savedRoomWatch || [];
		if(!list.some(x => String(x).toLowerCase() === label.toLowerCase())) list.push(label);
		window.bonkHost.extraFeatures.savedRoomWatch = list;
		window.bonkHost.saveSavedRooms();
		window.bonkHost.clientStatus("Saved room watch: " + label);
	};
	window.bonkHost.showSavedRooms = () => {
		let list = window.bonkHost.extraFeatures.savedRoomWatch || [];
		let rows = list.length ? list.map((x,i)=>(i+1)+". "+x) : ["No saved rooms yet."];
		if(window.bonkHost.showDatabasePanel) window.bonkHost.showDatabasePanel("SAVED ROOMS", rows);
		else alert(rows.join("\n"));
	};
	window.bonkHost.clearSavedRooms = () => {
		if(!confirm("Clear all saved room watches?")) return;
		window.bonkHost.extraFeatures.savedRoomWatch = [];
		window.bonkHost.saveSavedRooms();
		window.bonkHost.clientStatus("Saved rooms cleared.");
	};

	window.bonkHost.scanRoomsForSavedOpen = (data) => {
		let list = window.bonkHost.extraFeatures.savedRoomWatch || [];
		if(!list.length || !data) return;
		let text = "";
		try { text = JSON.stringify(data); } catch(e) { text = String(data); }
		for(const saved of list) {
			if(!saved) continue;
			let found = text.toLowerCase().includes(String(saved).toLowerCase());
			if(found && !window.bonkHost.extraFeatures.savedRoomNotified[saved]) {
				window.bonkHost.extraFeatures.savedRoomNotified[saved] = Date.now();
				window.bonkHost.appendClientChatLine("[Saved Room]", saved + " may be open now.", "#ffd447");
				window.bonkHost.clientStatus("Saved room may be open: " + saved);
			}
		}
	};

	window.bonkHost.patchSavedRoomXHR = () => {
		if(window.bonkHost.savedRoomXHRPatched) return;
		window.bonkHost.savedRoomXHRPatched = true;
		const open = XMLHttpRequest.prototype.open;
		const send = XMLHttpRequest.prototype.send;
		XMLHttpRequest.prototype.open = function(method, url) {
			try {
				this.__trapiGetRooms = typeof url === "string" && url.includes("getrooms.php");
				this.__trapiGetRoomAddress = typeof url === "string" && url.includes("getroomaddress.php");
			} catch(e) {}
			return open.apply(this, arguments);
		};
		XMLHttpRequest.prototype.send = function(data) {
			try {
				if(this.__trapiGetRoomAddress && typeof data === "string") {
					let n = parseInt(data.slice(3));
					if(!Number.isNaN(n)) window.bonkHost.currentroomaddress = n;
				}
				if(this.__trapiGetRooms) {
					this.addEventListener("load", function() {
						try { window.bonkHost.scanRoomsForSavedOpen(JSON.parse(this.responseText)); }
						catch(e) { window.bonkHost.scanRoomsForSavedOpen(this.responseText); }
					});
				}
			} catch(e) {}
			return send.apply(this, arguments);
		};
	};
	window.bonkHost.patchSavedRoomXHR();

	const oldObserved = window.bonkHost.handleObservedPacket;
	window.bonkHost.handleObservedPacket = function(direction, packet) {
		try { oldObserved && oldObserved(direction, packet); } catch(e) {}
		try {
			if(direction === "in") window.bonkHost.handlePrivateChatPayload(packet);
		} catch(e) {}
	};

	window.bonkHost.clientStatus("v8.2 social tools loaded.");
})();


/* v8.4 Player Database expansion + UI polish */
(function(){
    if(!window.bonkHost || window.bonkHost.v84Loaded) return;
    window.bonkHost.v84Loaded = true;
    const safeJSON = (raw, fallback) => { try { return JSON.parse(raw); } catch(e) { return fallback; } };
    const ex = window.bonkHost.extraFeatures = window.bonkHost.extraFeatures || {};
    ex.playerMeta = safeJSON(localStorage.getItem("trapisClientPlayerMeta") || "{}", {});
    ex.lastWinningPlayers = ex.lastWinningPlayers || [];
    ex.lastWinningTeam = ex.lastWinningTeam || null;

    window.bonkHost.savePlayerMeta = () => {
        try { localStorage.setItem("trapisClientPlayerMeta", JSON.stringify((window.bonkHost.extraFeatures || {}).playerMeta || {})); } catch(e) {}
    };
    window.bonkHost.getPlayerMeta = (name) => {
        let clean = window.bonkHost.getCanonicalPlayerName ? window.bonkHost.getCanonicalPlayerName((name || "").trim()) : (name || "").trim();
        if(!clean) clean = "UNKNOWN";
        let meta = window.bonkHost.extraFeatures.playerMeta = window.bonkHost.extraFeatures.playerMeta || {};
        if(!meta[clean]) meta[clean] = { tags: [], favoriteMode: "", notes: "", lastRoomTitle: "", lastRoomAt: 0 };
        if(!Array.isArray(meta[clean].tags)) meta[clean].tags = [];
        return meta[clean];
    };
    window.bonkHost.getCurrentRoomTitle = () => {
        const ids = ["newbonklobby_roomname", "roomname", "pretty_top_roomname", "newbonklobby_room_title"];
        for(const id of ids) {
            let el = document.getElementById(id);
            let t = (el && (el.textContent || el.value) || "").trim();
            if(t && !/^(room|bonk\.io)$/i.test(t)) return t;
        }
        try {
            let candidates = [...document.querySelectorAll("[id*='room' i], [class*='room' i]")].slice(0, 80);
            for(const el of candidates) {
                let t = (el.textContent || el.value || "").trim();
                if(t && t.length >= 3 && t.length <= 80 && /[A-Za-z0-9]/.test(t) && !/^(room|rooms|custom games|public|private)$/i.test(t)) return t;
            }
        } catch(e) {}
        return window.bonkHost.currentroomtitle || "Unknown Room";
    };
    window.bonkHost.updateCurrentRoomTitleTracking = () => {
        let title = window.bonkHost.getCurrentRoomTitle();
        if(title && title !== "Unknown Room") {
            window.bonkHost.currentroomtitle = title;
            localStorage.setItem("trapisClientLastRoomTitle", title);
        } else {
            title = localStorage.getItem("trapisClientLastRoomTitle") || "Unknown Room";
        }
        try {
            for(const p of (window.bonkHost.players || [])) {
                if(!p || !p.userName) continue;
                let meta = window.bonkHost.getPlayerMeta(p.userName);
                meta.lastRoomTitle = title;
                meta.lastRoomAt = Date.now();
            }
            window.bonkHost.savePlayerMeta();
        } catch(e) {}
        return title;
    };
    setInterval(() => { try { window.bonkHost.updateCurrentRoomTitleTracking(); } catch(e) {} }, 4000);

    // Styled client popup used instead of the old out-of-place info box.
    window.bonkHost.showClientPopup = (title, content) => {
        let old = document.getElementById("trapisClientInfoPopup");
        if(old) old.remove();
        let lines = Array.isArray(content) ? content : [String(content || "")];
        let box = document.createElement("div");
        box.id = "trapisClientInfoPopup";
        box.innerHTML = `<div id="trapisClientInfoPopupTop"><span>${window.bonkHost.escapeClientText ? window.bonkHost.escapeClientText(title || "TRAPI'S CLIENT") : (title || "TRAPI'S CLIENT")}</span><button id="trapisClientInfoPopupClose">×</button></div><div id="trapisClientInfoPopupBody"></div>`;
        let body = box.querySelector("#trapisClientInfoPopupBody");
        body.textContent = lines.join("\n");
        (document.getElementById("pagecontainer") || document.body || document.documentElement).appendChild(box);
        let close = document.getElementById("trapisClientInfoPopupClose");
        if(close) close.onclick = () => box.remove();
    };
    const st = document.createElement("style");
    st.id = "trapisClientV84Css";
    st.textContent = `
        #hostPlayerMenu { min-width: 205px !important; max-width: 265px !important; width: 235px !important; }
        #hostPlayerMenuLauncher { display:none; position:absolute !important; left:10px !important; top:60px !important; z-index:2147483000 !important; width:38px !important; height:38px !important; line-height:38px !important; cursor:pointer !important; }
        #hostPlayerMenuCloseButton { z-index:2147483001 !important; cursor:pointer !important; }
        #hostPlayerMenuDatabasePanel { background:#2f2927 !important; color:#fff !important; border-top:2px solid #6f4f42 !important; font-family:futurept_b1,Arial,sans-serif !important; }
        #hostPlayerMenuDatabasePanelBody { white-space:pre-wrap !important; line-height:16px !important; }
        #trapisClientInfoPopup { position:absolute; left:275px; top:64px; width:300px; max-width:calc(100vw - 300px); max-height:60vh; overflow:hidden; z-index:2147483002; background:#2f2927; color:#fff; border:2px solid #6f4f42; border-radius:8px; box-shadow:0 8px 20px rgba(0,0,0,.35); font-family:futurept_b1,Arial,sans-serif; }
        #trapisClientInfoPopupTop { height:30px; display:flex; align-items:center; justify-content:space-between; padding:0 7px 0 10px; background:#6f4f42; font-size:14px; font-weight:800; text-transform:uppercase; }
        #trapisClientInfoPopupClose { width:24px; height:24px; border:0; border-radius:3px; background:#8b6f61; color:#fff; font-size:18px; line-height:20px; cursor:pointer; font-weight:900; }
        #trapisClientInfoPopupBody { padding:10px; overflow:auto; max-height:calc(60vh - 34px); white-space:pre-wrap; font-size:12px; line-height:16px; }
        #hostPlayerMenuRivalsButton,#hostPlayerMenuTeamChemistryButton,#hostPlayerMenuEditPlayerNoteButton,#hostPlayerMenuEditFavoriteModeButton,#hostPlayerMenuTrustedButton,#hostPlayerMenuProblemButton,#hostPlayerMenuCommandShortcutsButton { width:100%; border-width:0 !important; }
    `;
    document.head.appendChild(st);

    // Fix close/reopen button behavior.
    window.bonkHost.ensureClientLauncher = () => {
        let launcher = document.getElementById("hostPlayerMenuLauncher");
        if(!launcher) {
            launcher = document.createElement("div");
            launcher.id = "hostPlayerMenuLauncher";
            launcher.className = "newbonklobby_settings_button brownButton brownButton_classic buttonShadow";
            launcher.textContent = "T";
            launcher.title = "Open Trapi's Client";
            (document.getElementById("pagecontainer") || document.body || document.documentElement).appendChild(launcher);
        }
        launcher.onclick = () => window.bonkHost.openClient && window.bonkHost.openClient();
        return launcher;
    };
    window.bonkHost.closeClient = () => {
        try {
            window.bonkHost.clientClosed = true;
            localStorage.setItem("trapisClientClosed", "true");
            let menu = document.getElementById("hostPlayerMenu");
            let launcher = window.bonkHost.ensureClientLauncher();
            if(menu) menu.style.display = "none";
            if(launcher) launcher.style.display = "block";
        } catch(e) { console.warn("Trapi's Client close failed", e); }
    };
    window.bonkHost.openClient = () => {
        try {
            window.bonkHost.clientClosed = false;
            localStorage.setItem("trapisClientClosed", "false");
            let menu = document.getElementById("hostPlayerMenu");
            let launcher = window.bonkHost.ensureClientLauncher();
            if(menu) menu.style.display = "block";
            if(launcher) launcher.style.display = "none";
        } catch(e) { console.warn("Trapi's Client open failed", e); }
    };
    window.bonkHost.ensureClientLauncher();

    const oldShowDatabasePanel = window.bonkHost.showDatabasePanel;
    window.bonkHost.showDatabasePanel = (title, lines) => {
        try { oldShowDatabasePanel && oldShowDatabasePanel(title, lines); } catch(e) {}
        try { window.bonkHost.showClientPopup(title, lines); } catch(e) {}
    };

    const oldRecordJoin = window.bonkHost.recordPlayerJoinHistory;
    window.bonkHost.recordPlayerJoinHistory = (name) => {
        try { oldRecordJoin && oldRecordJoin(name); } catch(e) {}
        let meta = window.bonkHost.getPlayerMeta(name);
        meta.lastRoomTitle = window.bonkHost.updateCurrentRoomTitleTracking();
        meta.lastRoomAt = Date.now();
        window.bonkHost.savePlayerMeta();
    };
    const oldRecordGame = window.bonkHost.recordPlayerGameHistory;
    window.bonkHost.recordPlayerGameHistory = (winnerId, winAmount=1) => {
        try {
            let players = window.bonkHost.players || [];
            let gs = null; try { gs = window.bonkHost.toolFunctions.getGameSettings(); } catch(e) {}
            let id = parseInt(winnerId);
            let winners = [];
            if(gs && gs.tea && (id === 2 || id === 3)) winners = players.filter(p => p && p.userName && p.team === id).map(p => p.userName);
            else if(players[id] && players[id].userName) winners = [players[id].userName];
            ex.lastWinningPlayers = winners;
            ex.lastWinningTeam = (gs && gs.tea && (id === 2 || id === 3)) ? id : null;
        } catch(e) {}
        try { oldRecordGame && oldRecordGame(winnerId, winAmount); } catch(e) {}
        try { window.bonkHost.updateCurrentRoomTitleTracking(); } catch(e) {}
    };

    window.bonkHost.togglePlayerTag = (name, tag) => {
        let meta = window.bonkHost.getPlayerMeta(name);
        let i = meta.tags.findIndex(x => String(x).toLowerCase() === String(tag).toLowerCase());
        if(i >= 0) meta.tags.splice(i, 1); else meta.tags.push(tag);
        window.bonkHost.savePlayerMeta();
        return meta.tags;
    };
    window.bonkHost.editPlayerNotePrompt = (name) => {
        name = name || prompt("Player name:"); if(!name) return;
        let meta = window.bonkHost.getPlayerMeta(name);
        let note = prompt("Notes for " + name + ":", meta.notes || "");
        if(note === null) return;
        meta.notes = note;
        window.bonkHost.savePlayerMeta();
        window.bonkHost.showPlayerProfile(name);
    };
    window.bonkHost.editFavoriteModePrompt = (name) => {
        name = name || prompt("Player name:"); if(!name) return;
        let meta = window.bonkHost.getPlayerMeta(name);
        let mode = prompt("Favorite mode for " + name + " (example: WDB, 1v1, FFA, Classic):", meta.favoriteMode || "");
        if(mode === null) return;
        meta.favoriteMode = mode;
        window.bonkHost.savePlayerMeta();
        window.bonkHost.showPlayerProfile(name);
    };
    window.bonkHost.markTrustedPrompt = () => { let n = prompt("Mark/unmark Trusted player:"); if(n) { window.bonkHost.togglePlayerTag(n, "Trusted"); window.bonkHost.showPlayerProfile(n); } };
    window.bonkHost.markProblemPrompt = () => { let n = prompt("Mark/unmark Problem Player:"); if(n) { window.bonkHost.togglePlayerTag(n, "Problem Player"); window.bonkHost.showPlayerProfile(n); } };

    window.bonkHost.showPlayerProfile = (name) => {
        if(!name) return window.bonkHost.showPlayerLookup && window.bonkHost.showPlayerLookup();
        let realName = window.bonkHost.getCanonicalPlayerName ? window.bonkHost.getCanonicalPlayerName(name.trim()) : name.trim();
        let linked = window.bonkHost.getLinkedNamesFor ? window.bonkHost.getLinkedNamesFor(realName) : [realName];
        let history = window.bonkHost.mergeHistoryRecords ? window.bonkHost.mergeHistoryRecords(linked) : (window.bonkHost.getPlayerHistoryRecord ? window.bonkHost.getPlayerHistoryRecord(realName) : {});
        if(window.bonkHost.normalizeStatsBuckets) window.bonkHost.normalizeStatsBuckets(history);
        let meta = window.bonkHost.getPlayerMeta(realName);
        let player = (window.bonkHost.players || []).find(p => p && p.userName && linked.map(x=>x.toLowerCase()).includes(p.userName.toLowerCase()));
        let lines = [
            "Name: " + realName,
            "Tags: " + (meta.tags.length ? meta.tags.join(", ") : "None"),
            "Favorite Mode: " + (meta.favoriteMode || "Not set"),
            "Status: " + (player ? "In Room" : "Not in room"),
            "Current Team: " + (player && window.bonkHost.getTeamLabel ? window.bonkHost.getTeamLabel(player.team) : "Unknown"),
            "Last Room Joined: " + (meta.lastRoomTitle || "Unknown"),
            "Last Room Time: " + (meta.lastRoomAt && window.bonkHost.formatTimeAgo ? window.bonkHost.formatTimeAgo(meta.lastRoomAt) : "Unknown"),
            "Linked Accounts: " + (linked.length > 1 ? linked.join(", ") : "None"),
            "",
            window.bonkHost.statLine ? window.bonkHost.statLine("All Games With You", history.total) : "Games: " + ((history.total && history.total.games) || 0),
            window.bonkHost.statLine ? window.bonkHost.statLine("Classic Quickplay", history.quickplay) : "",
            window.bonkHost.statLine ? window.bonkHost.statLine("1v1s", history.onevone) : "",
            window.bonkHost.statLine ? window.bonkHost.statLine("Team Games", history.team) : "",
            window.bonkHost.statLine ? window.bonkHost.statLine("Free For All", history.custom) : "",
            "Join Count: " + (history.joins || 0),
            "First Tracked: " + (history.firstSeen ? new Date(history.firstSeen).toLocaleDateString() : "Unknown"),
            "Last Seen: " + (player ? "In Room" : (window.bonkHost.formatTimeAgo ? window.bonkHost.formatTimeAgo(history.lastSeen || history.lastLeave) : "Unknown")),
            "",
            "Notes: " + (meta.notes || "None")
        ].filter(Boolean);
        window.bonkHost.showDatabasePanel("PLAYER PROFILE", lines);
    };
    window.bonkHost.showPlayerLookup = () => {
        let suggested = "";
        try { suggested = ((window.bonkHost.players || []).filter(p=>p&&p.userName).map(p=>p.userName)[0]) || ""; } catch(e) {}
        let name = prompt("Player name to look up:", suggested);
        if(name) window.bonkHost.showPlayerProfile(name);
    };
    window.bonkHost.showPlayerCard = window.bonkHost.showPlayerLookup;

    window.bonkHost.showRivals = () => {
        let rows = Object.entries(ex.playerHistory || {})
            .map(([name, rec]) => ({name, rec: window.bonkHost.normalizeStatsBuckets ? window.bonkHost.normalizeStatsBuckets(rec) : rec}))
            .filter(x => x.rec && x.rec.total && (x.rec.total.games || 0) >= 3)
            .sort((a,b)=>(b.rec.total.games||0)-(a.rec.total.games||0))
            .slice(0, 12)
            .map((x,i)=>`${i+1}. ${x.name} — ${x.rec.total.wins||0}W/${x.rec.total.games||0}G — ${window.bonkHost.formatWR ? window.bonkHost.formatWR(x.rec.total.wins||0,x.rec.total.games||0) : ""}`);
        window.bonkHost.showDatabasePanel("RIVALS", rows.length ? rows : ["No rival data yet. Play 3+ games with someone first."]);
    };
    window.bonkHost.showTeamChemistry = () => {
        let rows = Object.entries(ex.playerHistory || {})
            .map(([name, rec]) => ({name, rec: window.bonkHost.normalizeStatsBuckets ? window.bonkHost.normalizeStatsBuckets(rec) : rec}))
            .filter(x => x.rec && x.rec.team && (x.rec.team.games || 0) >= 2)
            .sort((a,b)=>((b.rec.team.wins||0)/(b.rec.team.games||1))-((a.rec.team.wins||0)/(a.rec.team.games||1)))
            .slice(0, 12)
            .map((x,i)=>`${i+1}. ${x.name} — Team WR ${window.bonkHost.formatWR ? window.bonkHost.formatWR(x.rec.team.wins||0,x.rec.team.games||0) : ""} — ${x.rec.team.games||0} games`);
        window.bonkHost.showDatabasePanel("TEAM CHEMISTRY", rows.length ? rows : ["No team chemistry data yet."]);
    };
    window.bonkHost.showCommandShortcuts = () => {
        window.bonkHost.showDatabasePanel("COMMAND SHORTCUTS", [
            "/kick, /ban, /unban — player moderation",
            "/mute, /unmute — chat control",
            "/lock, /unlock — room control",
            "/balance, /balanceall — team tools",
            "/start — start game",
            "/freejoin — toggle freejoin",
            "/scoreboard — show scoreboard",
            "/resetpos — reset position tools",
            "/msg, /chatw, /pmusers — private chat tools"
        ]);
    };

    window.bonkHost.ensureV84Buttons = () => {
        const make = window.bonkHost.makeClientButton || ((id, text) => { let d=document.createElement("div"); d.className="newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem"; d.id=id; d.textContent=text; return d; });
        let db = document.getElementById("hostPlayerMenuDatabaseDropdown");
        if(db && !document.getElementById("hostPlayerMenuRivalsButton")) {
            let ref = document.getElementById("hostPlayerMenuTopWinrateButton") || db.lastChild;
            const buttons = [
                ["hostPlayerMenuRivalsButton", "RIVALS", () => window.bonkHost.showRivals()],
                ["hostPlayerMenuTeamChemistryButton", "TEAM CHEMISTRY", () => window.bonkHost.showTeamChemistry()],
                ["hostPlayerMenuEditFavoriteModeButton", "EDIT FAVORITE MODE", () => window.bonkHost.editFavoriteModePrompt()],
                ["hostPlayerMenuEditPlayerNoteButton", "EDIT PLAYER NOTES", () => window.bonkHost.editPlayerNotePrompt()],
                ["hostPlayerMenuTrustedButton", "TOGGLE TRUSTED", () => window.bonkHost.markTrustedPrompt()],
                ["hostPlayerMenuProblemButton", "TOGGLE PROBLEM", () => window.bonkHost.markProblemPrompt()]
            ];
            for(const [id,text,fn] of buttons) { let b=make(id,text); b.addEventListener("click", fn); db.insertBefore(b, ref ? ref.nextSibling : null); ref=b; }
        }
        let social = document.getElementById("hostPlayerMenuSocialDropdown");
        if(social && !document.getElementById("hostPlayerMenuCommandShortcutsButton")) {
            let b = make("hostPlayerMenuCommandShortcutsButton", "COMMAND SHORTCUTS");
            b.addEventListener("click", () => window.bonkHost.showCommandShortcuts());
            social.appendChild(b);
        }
    };
    setTimeout(window.bonkHost.ensureV84Buttons, 500);
    setInterval(() => { try { window.bonkHost.ensureV84Buttons(); } catch(e) {} }, 2500);

    // A true/fair team shuffle: separates the last winning team first, then balances by session/player history.
    window.bonkHost.getPlayerFairScore = (id) => {
        let name = window.bonkHost.getPlayerNameById ? window.bonkHost.getPlayerNameById(id) : "";
        let sessionWins = (((window.bonkHost.extraFeatures || {}).stats || {}).wins || {})[name] || 0;
        let rec = (window.bonkHost.extraFeatures.playerHistory || {})[window.bonkHost.getCanonicalPlayerName ? window.bonkHost.getCanonicalPlayerName(name) : name];
        let wrScore = rec && rec.total && rec.total.games ? (rec.total.wins / rec.total.games) * 3 : 0;
        return sessionWins + wrScore + 1;
    };
    window.bonkHost.balanceBySessionWinrate = () => {
        if(typeof isHost === "function" && !isHost()) return;
        let active = window.bonkHost.getActivePlayerIDs ? window.bonkHost.getActivePlayerIDs() : [];
        if(active.length < 2) return;
        let lastWinners = ((window.bonkHost.extraFeatures || {}).lastWinningPlayers || []).map(x => String(x).toLowerCase());
        let winners = active.filter(id => lastWinners.includes(String(window.bonkHost.getPlayerNameById ? window.bonkHost.getPlayerNameById(id) : "").toLowerCase()));
        let others = active.filter(id => !winners.includes(id));
        winners.sort((a,b)=>window.bonkHost.getPlayerFairScore(b)-window.bonkHost.getPlayerFairScore(a));
        others.sort((a,b)=>window.bonkHost.getPlayerFairScore(b)-window.bonkHost.getPlayerFairScore(a));
        let red=[], blue=[], redScore=0, blueScore=0;
        const place = (id, forceAlternate=false) => {
            let score = window.bonkHost.getPlayerFairScore(id);
            let toRed = redScore <= blueScore;
            if(forceAlternate) toRed = winners.indexOf(id) % 2 === 0;
            if(toRed) { red.push(id); redScore += score; } else { blue.push(id); blueScore += score; }
        };
        winners.forEach(id => place(id, true));
        others.forEach(id => place(id, false));
        for(const id of red) try { window.bonkHost.toolFunctions.networkEngine.changeOtherTeam(id, 2); } catch(e) {}
        for(const id of blue) try { window.bonkHost.toolFunctions.networkEngine.changeOtherTeam(id, 3); } catch(e) {}
        if(window.bonkHost.clientStatus) window.bonkHost.clientStatus("Fair shuffled teams. Last winners were split first.");
    };
    window.bonkHost.shuffleRedBlue = () => window.bonkHost.balanceBySessionWinrate();

    // Player click helper: click a player name, then the client offers profile / PM / tags.
    document.addEventListener("click", (e) => {
        try {
            let row = e.target.closest && e.target.closest(".newbonklobby_playerentry, [id^='newbonklobby_playerentry']");
            if(!row || row.dataset.trapiClickMenuBound === "1") return;
            row.dataset.trapiClickMenuBound = "1";
            setTimeout(() => {
                let nameEl = row.querySelector(".newbonklobby_playerentry_name") || row;
                let name = (nameEl.textContent || "").replace(/[\u{1F1E6}-\u{1F1FF}]/gu, "").trim();
                if(!name) return;
                let action = prompt("Trapi's Client player action for " + name + ":\n1 = View Profile\n2 = Private Chat\n3 = Toggle Trusted\n4 = Toggle Problem Player\n5 = Edit Note", "1");
                if(action === "1") window.bonkHost.showPlayerProfile(name);
                if(action === "2") window.bonkHost.connectPrivateChat ? window.bonkHost.connectPrivateChat(name, true) : window.bonkHost.startPrivateChatPrompt();
                if(action === "3") { window.bonkHost.togglePlayerTag(name,"Trusted"); window.bonkHost.showPlayerProfile(name); }
                if(action === "4") { window.bonkHost.togglePlayerTag(name,"Problem Player"); window.bonkHost.showPlayerProfile(name); }
                if(action === "5") window.bonkHost.editPlayerNotePrompt(name);
            }, 50);
        } catch(err) {}
    }, true);

    // Make collapse close every known section.
    const oldCollapse = window.bonkHost.collapseAllHostMenuTabs;
    window.bonkHost.collapseAllHostMenuTabs = () => {
        try { oldCollapse && oldCollapse(); } catch(e) {}
        ["hostPlayerMenuSocialDropdown","hostPlayerMenuDatabasePanel","trapisClientInfoPopup"].forEach(id => { let el=document.getElementById(id); if(el) id === "trapisClientInfoPopup" ? el.remove() : el.style.display="none"; });
        let st = document.getElementById("hostPlayerMenuSocialToggle"); if(st) st.textContent = "SOCIAL ▼";
    };

    window.bonkHost.clientStatus && window.bonkHost.clientStatus("v8.4 database and UI tools loaded.");
})();


window.bonkHost.bindV67Buttons = () => {
	const bindings = {
		hostPlayerMenuStatusButton: () => window.bonkHost.showHostStatus(),
		hostPlayerMenuVersionInfoButton: () => window.bonkHost.showVersionInfo(),
		hostPlayerMenuExportSettingsButton: () => window.bonkHost.exportSettings(),
		hostPlayerMenuImportSettingsButton: () => window.bonkHost.importSettings(),
		hostPlayerMenuGlobalHistoryButton: () => {
			let ex = window.bonkHost.extraFeatures || {};
			ex.globalPlayerHistory = !ex.globalPlayerHistory;
			ex.globalHistoryLastScores = null;
			if(window.bonkHost.updateExtraFeatureButtons) window.bonkHost.updateExtraFeatureButtons();
			if(window.bonkHost.menuFunctions) window.bonkHost.menuFunctions.showStatusMessage("* Global Player History: " + (ex.globalPlayerHistory ? "ON" : "OFF"), "#b53030", false);
		},
		hostPlayerMenuCareerStatsButton: () => {
			let ex = window.bonkHost.extraFeatures || {};
			ex.careerStatsEnabled = ex.careerStatsEnabled === false;
			if(window.bonkHost.updateExtraFeatureButtons) window.bonkHost.updateExtraFeatureButtons();
			if(window.bonkHost.menuFunctions) window.bonkHost.menuFunctions.showStatusMessage("* Career Stats: " + (ex.careerStatsEnabled !== false ? "ON" : "OFF"), "#b53030", false);
		},
		hostPlayerMenuPlayerCardButton: () => window.bonkHost.showPlayerCard(),
		hostPlayerMenuMyCareerStatsButton: () => window.bonkHost.showMyCareerStats(),
		hostPlayerMenuMyProfileButton: () => window.bonkHost.showMyProfile(),
		hostPlayerMenuSetExactXpButton: () => window.bonkHost.setManualExactXp && window.bonkHost.setManualExactXp(),
		hostPlayerMenuXpCapButton: () => window.bonkHost.showXpCapStatus && window.bonkHost.showXpCapStatus(),
		hostPlayerMenuPlayerLookupButton: () => window.bonkHost.showPlayerLookup(),
		hostPlayerMenuRecentPlayersButton: () => window.bonkHost.showRecentPlayers(),
		hostPlayerMenuTopPlayedWithButton: () => window.bonkHost.showTopPlayedWith(),
		hostPlayerMenuTopWinrateButton: () => window.bonkHost.showTopWinrate(),
		hostPlayerMenuLinkAccountsButton: () => window.bonkHost.linkAccountsPrompt(),
		hostPlayerMenuAccountLinksButton: () => window.bonkHost.showAccountLinks(),
		hostPlayerMenuDatabaseHealthButton: () => window.bonkHost.showDatabaseHealth()
	};
	for(let id of Object.keys(bindings)) {
		let el = document.getElementById(id);
		if(!el || el.dataset.hostModV67Bound === "1") continue;
		el.dataset.hostModV67Bound = "1";
		el.addEventListener("click", bindings[id]);
	}
};
window.bonkHost.bindV67Buttons();



window.bonkHost.countryCodeToFlag = (code) => {
	if(!code || typeof code !== "string") return "";
	let cc = code.trim().toUpperCase();
	if(cc.length > 2 && cc.includes("-")) cc = cc.split("-").pop();
	if(cc.length > 2) cc = cc.slice(0, 2);
	if(!/^[A-Z]{2}$/.test(cc)) return "";
	return cc.replace(/./g, ch => String.fromCodePoint(127397 + ch.charCodeAt(0)));
};

window.bonkHost.getPlayerCountryCode = (player) => {
	if(!player) return "";
	let keys = ["country", "countryCode", "country_code", "cc", "region", "location", "loc", "iso", "flag"];
	for(let key of keys) {
		let value = player[key];
		if(typeof value === "string" && value.trim()) return value.trim();
		if(value && typeof value === "object") {
			for(let nested of ["code", "country", "countryCode", "cc", "iso"]) {
				if(typeof value[nested] === "string" && value[nested].trim()) return value[nested].trim();
			}
		}
	}
	return "";
};

window.bonkHost.getPlayerFlag = (player) => {
	return window.bonkHost.countryCodeToFlag(window.bonkHost.getPlayerCountryCode(player));
};

window.bonkHost.findPlayerByName = (name) => {
	let clean = (name || "").replace(/[\u{1F1E6}-\u{1F1FF}]/gu, "").trim();
	return (window.bonkHost.players || []).find(p => p && p.userName === clean) || null;
};

window.bonkHost.applyFlagToPlayerEntry = (entry, player) => {
	if(!entry || !player) return;
	let nameEl = entry.getElementsByClassName("newbonklobby_playerentry_name")[0] || entry.childNodes[1];
	if(!nameEl) return;
	let flag = window.bonkHost.getPlayerFlag(player);
	if(flag) {
		nameEl.classList.add("bonkHostFlagName");
		nameEl.dataset.bonkHostFlag = flag;
	}
	else {
		nameEl.classList.remove("bonkHostFlagName");
		delete nameEl.dataset.bonkHostFlag;
	}
};

window.bonkHost.applyCountryFlagsToLobby = () => {
	if(!window.bonkHost.players) return;
	let entries = [...document.getElementsByClassName("newbonklobby_playerentry")];
	for(let entry of entries) {
		let nameEl = entry.getElementsByClassName("newbonklobby_playerentry_name")[0] || entry.childNodes[1];
		let player = nameEl ? window.bonkHost.findPlayerByName(nameEl.textContent) : null;
		if(player) window.bonkHost.applyFlagToPlayerEntry(entry, player);
	}
};



window.bonkHost.showHelpMessages = (title, lines) => {
	if(!window.bonkHost.menuFunctions || !window.bonkHost.menuFunctions.showStatusMessage) return;
	window.bonkHost.menuFunctions.showStatusMessage("HOST MOD HELP - " + title, "#b53030", false);
	for(let line of lines) {
		window.bonkHost.menuFunctions.showStatusMessage("• " + line, "#b53030", false);
	}
};

window.bonkHost.hostModHelp = {
	welcome: [
		"Use the Host Mod menu to manage players, teams, playlists, stats, and settings.",
		"Open a section, then click its buttons to toggle or run that feature.",
		"Help rows explain the menu without changing game settings.",
		"Main Menu keeps all sections compact while features are added one at a time."
	],
	team: [
		"Shuffle R/B splits active players between Red and Blue using session win data.",
		"Auto Balance keeps teams even without reshuffling existing players.",
		"Swap Red/Blue flips red players to blue and blue players to red.",
		"All Spec moves everyone to spectator.",
		"Captains Mode picks top session players as captains and builds teams."
	],
	host: [
		"Teamlock controls whether players can switch teams.",
		"Freejoin lets players join while a game is running.",
		"Keep Scores keeps score data across restarts/Quickplay.",
		"Cheat Detection shows lag/input graphs for suspicious behavior.",
		"Guest AK auto-kicks guest or non-registered accounts."
	],
	playlists: [
		"Open Playlists opens Bonk's playlist/map menu.",
		"Quickplay rotates maps when a player/team reaches the selected win target.",
		"Leader and You show current map win progress.",
		"Wins/QP changes the win target: 1, 3, 5, or 9.",
		"Shuffle Queue shuffles the loaded playlist queue.",
		"Previous/Next Map manually moves through the queue."
	],
	stats: [
		"Match Stats tracks session wins, streaks, and red/blue winrate.",
		"Stats reset when the script/page reloads.",
		"Use the horizontal scroll if the stat text is longer than the menu."
	],
	players: [
		"Auto Host transfers host to a chosen player or a trusted non-guest before leaving.",
		"Set Auto Host chooses the preferred player name.",
		"Recent Joins shows the latest players who entered.",
		"Player Notes lets you save local notes for player names.",
		"Player Card shows a quick summary of a player, their team, notes, and session stats."
	],
	settings: [
		"Auto Team Join automatically places new players onto the team that needs them.",
		"Auto Join Next automatically moves you from spectator into the next round.",
		"Host Mod Status checks whether the injector, UI, and major systems loaded.",
		"Version Info shows the current build and major update notes.",
		"Export/Import Settings lets you copy your Host Mod setup between installs.",
		"Smart Shuffle tries to avoid recently played maps when shuffling Quickplay.",
		"Rejoin Protection reserves a recently disconnected player's spot for about 1 minute.",
		"Patch Notes Compact hides older patch notes to keep the menu cleaner.",
		"Country Flags display automatically beside names when Bonk provides country data.",
		"Player Cards save wins, games, winrate, joins, and last seen locally in your browser."
	]
};

window.bonkHost.bindHelpButtons = () => {
	const helpBindings = {
		hostPlayerMenuHelpWelcome: ["BASIC", window.bonkHost.hostModHelp.welcome],
		hostPlayerMenuHelpTeamTools: ["TEAM TOOLS", window.bonkHost.hostModHelp.team],
		hostPlayerMenuHelpHostTools: ["HOST TOOLS", window.bonkHost.hostModHelp.host],
		hostPlayerMenuHelpPlaylists: ["PLAYLISTS", window.bonkHost.hostModHelp.playlists],
		hostPlayerMenuHelpStats: ["MATCH STATS", window.bonkHost.hostModHelp.stats],
		hostPlayerMenuHelpPlayers: ["PLAYER TOOLS", window.bonkHost.hostModHelp.players],
		hostPlayerMenuHelpSettings: ["SETTINGS", window.bonkHost.hostModHelp.settings]
	};
	for(let id of Object.keys(helpBindings)) {
		let el = document.getElementById(id);
		if(!el || el.dataset.hostModHelpBound === "1") continue;
		el.dataset.hostModHelpBound = "1";
		el.addEventListener("click", () => {
			window.bonkHost.showHelpMessages(helpBindings[id][0], helpBindings[id][1]);
		});
	}
};
window.bonkHost.bindHelpButtons();

// v5.9: keep country flags automatically refreshed in the lobby/side panel.
if(!window.bonkHost.countryFlagsAutoRefreshStarted) {
	window.bonkHost.countryFlagsAutoRefreshStarted = true;
	setInterval(() => {
		try {
			if(window.bonkHost.applyCountryFlagsToLobby) window.bonkHost.applyCountryFlagsToLobby();
		}
		catch(e) {}
	}, 1500);
}

window.bonkHost.getQuickplayCardName = (card) => {
	if(!card) return "UNKNOWN";
	let raw = (card.innerText || card.textContent || "").trim().replace(/\s+/g, " ");
	return raw || "UNKNOWN MAP";
};

window.bonkHost.recordCurrentMapName = (card) => {
	let ex = window.bonkHost.extraFeatures;
	let name = window.bonkHost.getQuickplayCardName(card);
	if(name && name !== ex.mapHistoryNames[0]) {
		ex.mapHistoryNames.unshift(name);
		if(ex.mapHistoryNames.length > 10) ex.mapHistoryNames.pop();
	}
	window.bonkHost.updateExtraFeatureButtons();
};

window.bonkHost.recordSessionWin = (winnerId, winAmount = 1) => {
	let ex = window.bonkHost.extraFeatures;
	let name = window.bonkHost.getQuickplayLeaderName ? window.bonkHost.getQuickplayLeaderName(parseInt(winnerId)) : (window.bonkHost.getPlayerNameById(parseInt(winnerId)) || "UNKNOWN");
	ex.stats.games += winAmount;
	ex.stats.wins[name] = (ex.stats.wins[name] || 0) + winAmount;
	if(ex.stats.streakName === name) ex.stats.streak += winAmount;
	else {
		ex.stats.streakName = name;
		ex.stats.streak = winAmount;
	}
	if(parseInt(winnerId) === 2) ex.stats.redWins += winAmount;
	if(parseInt(winnerId) === 3) ex.stats.blueWins += winAmount;
	window.bonkHost.updateExtraFeatureButtons();
};

window.bonkHost.updateRecentJoin = (name) => {
	let ex = window.bonkHost.extraFeatures;
	ex.recentJoins = [name].concat(ex.recentJoins.filter(n => n !== name)).slice(0, 10);
	if(window.bonkHost.recordPlayerJoinHistory) window.bonkHost.recordPlayerJoinHistory(name);
	window.bonkHost.updateExtraFeatureButtons();
};

window.bonkHost.showPlayerNoteIfAny = (name) => {
	let note = window.bonkHost.extraFeatures.playerNotes[name];
	if(note && window.bonkHost.menuFunctions) {
		window.bonkHost.menuFunctions.showStatusMessage("* NOTE: " + name + " - " + note, "#b53030", false);
	}
};

window.bonkHost.savePlayerNote = () => {
	let name = prompt("Player name to note:");
	if(!name) return;
	let note = prompt("Note for " + name + ":", window.bonkHost.extraFeatures.playerNotes[name] || "");
	if(note === null) return;
	if(note.trim() === "") delete window.bonkHost.extraFeatures.playerNotes[name];
	else window.bonkHost.extraFeatures.playerNotes[name] = note.trim();
	localStorage.setItem("bonkHostPlayerNotes", JSON.stringify(window.bonkHost.extraFeatures.playerNotes));
	if(window.bonkHost.menuFunctions) window.bonkHost.menuFunctions.showStatusMessage("* Saved note for " + name, "#b53030", false);
};

window.bonkHost.setAutoHostTarget = () => {
	let name = prompt("Auto-host target player name:");
	if(!name) return;
	window.bonkHost.extraFeatures.autoHostTargetName = name;
	window.bonkHost.updateExtraFeatureButtons();
};

window.bonkHost.getTeamPower = (team) => {
	let wins = window.bonkHost.extraFeatures.stats.wins || {};
	let total = 0;
	for(let i = 0; i < window.bonkHost.players.length; i++) {
		let p = window.bonkHost.players[i];
		if(p && p.team === team) total += (wins[p.userName] || 0) + 1;
	}
	return total;
};

window.bonkHost.assignNewJoinToBalancedTeam = (playerID) => {
	if(!isHost() || !window.bonkHost.extraFeatures) return;
	let player = window.bonkHost.players[playerID];
	if(!player) return;

	let gs = null;
	try { gs = window.bonkHost.toolFunctions.getGameSettings(); } catch(e) {}
	if(!gs || !gs.tea) return;

	// Hidden team join helper:
	// In team modes, every new player is automatically placed onto the team
	// that needs them most. Player count is prioritized first, then session
	// winrate/power is used as the tie-breaker so stronger teams do not keep stacking.
	let teams = [2, 3]; // Red, Blue
	let counts = {2: 0, 3: 0};
	let power = {2: 0, 3: 0};
	let wins = (window.bonkHost.extraFeatures && window.bonkHost.extraFeatures.stats && window.bonkHost.extraFeatures.stats.wins) ? window.bonkHost.extraFeatures.stats.wins : {};

	for(let i = 0; i < window.bonkHost.players.length; i++) {
		if(i === playerID) continue;
		let p = window.bonkHost.players[i];
		if(!p || !teams.includes(p.team)) continue;
		counts[p.team]++;
		power[p.team] += (wins[p.userName] || 0) + 1;
	}

	let targetTeam;
	if(counts[2] < counts[3]) targetTeam = 2;
	else if(counts[3] < counts[2]) targetTeam = 3;
	else targetTeam = power[2] <= power[3] ? 2 : 3;

	try {
		window.bonkHost.toolFunctions.networkEngine.changeOtherTeam(playerID, targetTeam);
	}
	catch(e) {
		try {
			window.bonkHost.stateFunctions.hostHandlePlayerJoined(playerID, window.bonkHost.players.length, targetTeam);
		}
		catch(err) {}
	}
};

window.bonkHost.updateRejoinProtectionSnapshot = () => {
	if(!window.bonkHost.extraFeatures || !window.bonkHost.players) return;
	let ex = window.bonkHost.extraFeatures;
	let currentNames = window.bonkHost.players.filter(p => p && p.userName).map(p => p.userName);

	if(ex.rejoinProtection && Array.isArray(ex.lastPlayerNames)) {
		for(let name of ex.lastPlayerNames) {
			if(!currentNames.includes(name)) {
				if(window.bonkHost.recordPlayerLeaveHistory) window.bonkHost.recordPlayerLeaveHistory(name);
				ex.reservedNames[name] = Date.now() + 60000;
				setTimeout(() => {
					if(window.bonkHost.extraFeatures && window.bonkHost.extraFeatures.reservedNames[name] && window.bonkHost.extraFeatures.reservedNames[name] <= Date.now()) {
						delete window.bonkHost.extraFeatures.reservedNames[name];
					}
				}, 61000);
			}
		}
	}

	ex.lastPlayerNames = currentNames;
};

window.bonkHost.getActiveRejoinProtectedName = () => {
	let ex = window.bonkHost.extraFeatures;
	if(!ex || !ex.reservedNames) return null;
	let now = Date.now();
	for(let name of Object.keys(ex.reservedNames)) {
		if(ex.reservedNames[name] > now) return name;
		delete ex.reservedNames[name];
	}
	return null;
};

window.bonkHost.getCurrentPlayerCount = () => {
	return window.bonkHost.players ? window.bonkHost.players.filter(p => p).length : 0;
};

window.bonkHost.getRoomCapacityGuess = () => {
	let count = window.bonkHost.getCurrentPlayerCount();
	try {
		let gs = window.bonkHost.toolFunctions.getGameSettings();
		for(let key of ["maxPlayers", "maxplayers", "players", "playerLimit", "pl", "pc", "ps"]) {
			if(gs && typeof gs[key] === "number" && gs[key] >= count) return gs[key];
		}
	}
	catch(e) {}
	// Most rooms using this workflow are 6-player rooms. This keeps the warning from firing at 4/6 or 5/6.
	return 6;
};

window.bonkHost.handleRejoinProtectionJoin = (playerName) => {
	let ex = window.bonkHost.extraFeatures;
	if(!ex || !ex.rejoinProtection) return;
	let protectedName = window.bonkHost.getActiveRejoinProtectedName();
	if(!protectedName || protectedName === playerName) return;

	let count = window.bonkHost.getCurrentPlayerCount();
	let cap = window.bonkHost.getRoomCapacityGuess();

	if(count >= cap && window.bonkHost.sendAutoKickChat) {
		window.bonkHost.sendAutoKickChat(protectedName + " is join protected for 1 minute.");
	}
};

window.bonkHost.tryAutoHostTransfer = () => {
	let ex = window.bonkHost.extraFeatures;
	if(!ex.autoHost || !isHost()) return;
	let targetId = -1;
	if(ex.autoHostTargetName) {
		targetId = window.bonkHost.players.findIndex(p => p && p.userName === ex.autoHostTargetName);
	}
	if(targetId === -1) {
		let myId = window.bonkHost.toolFunctions.networkEngine.getLSID();
		targetId = window.bonkHost.players.findIndex((p, i) => p && i !== myId && !p.guest);
	}
	if(targetId !== -1) {
		try { window.bonkHost.toolFunctions.networkEngine.sendHostChange(targetId); } catch(e) {}
	}
};

window.addEventListener("beforeunload", () => {
	try { window.bonkHost.tryAutoHostTransfer(); } catch(e) {}
});

window.bonkHost.balanceBySessionWinrate = () => {
	if(!isHost()) return;
	let active = window.bonkHost.getActivePlayerIDs();
	if(active.length === 0) return;
	let wins = window.bonkHost.extraFeatures.stats.wins || {};

	// Universal team shuffle: stronger/session-winning players are split across red and blue.
	active.sort((a,b) => (wins[window.bonkHost.getPlayerNameById(b)] || 0) - (wins[window.bonkHost.getPlayerNameById(a)] || 0));

	let redTotal = 0, blueTotal = 0;
	for(let id of active) {
		let score = wins[window.bonkHost.getPlayerNameById(id)] || 0;
		if(redTotal <= blueTotal) {
			window.bonkHost.toolFunctions.networkEngine.changeOtherTeam(id, 2);
			redTotal += score + 1;
		}
		else {
			window.bonkHost.toolFunctions.networkEngine.changeOtherTeam(id, 3);
			blueTotal += score + 1;
		}
	}
	if(window.bonkHost.menuFunctions) window.bonkHost.menuFunctions.showStatusMessage("* Shuffled R/B by session winrate", "#b53030", false);
};

window.bonkHost.runCaptainsMode = () => {
	let active = window.bonkHost.getActivePlayerIDs();
	if(active.length < 2) {
		if(window.bonkHost.menuFunctions) window.bonkHost.menuFunctions.showStatusMessage("* Captains mode needs at least 2 active players", "#b53030", false);
		return;
	}
	let wins = window.bonkHost.extraFeatures.stats.wins || {};
	active.sort((a,b) => (wins[window.bonkHost.getPlayerNameById(b)] || 0) - (wins[window.bonkHost.getPlayerNameById(a)] || 0));
	let redCaptain = active[0], blueCaptain = active[1];
	window.bonkHost.toolFunctions.networkEngine.changeOtherTeam(redCaptain, 2);
	window.bonkHost.toolFunctions.networkEngine.changeOtherTeam(blueCaptain, 3);
	for(let i = 2; i < active.length; i++) {
		window.bonkHost.toolFunctions.networkEngine.changeOtherTeam(active[i], i % 2 === 0 ? 2 : 3);
	}
	if(window.bonkHost.menuFunctions) window.bonkHost.menuFunctions.showStatusMessage("* Captains set: " + window.bonkHost.getPlayerNameById(redCaptain) + " (RED) vs " + window.bonkHost.getPlayerNameById(blueCaptain) + " (BLUE)", "#b53030", false);
};

window.bonkHost.getQuickplayLeaderName = (leaderId) => {
	let teamNames = {
		0: "SPEC",
		1: "FFA",
		2: "RED",
		3: "BLUE",
		4: "GREEN",
		5: "YELLOW"
	};

	if(window.bonkHost.players && window.bonkHost.players[leaderId] && window.bonkHost.players[leaderId].userName) {
		return window.bonkHost.players[leaderId].userName;
	}

	return teamNames[leaderId] || "UNKNOWN";
};

window.bonkHost.updateQuickplayLeaderStatus = () => {
	let el = document.getElementById("hostPlayerMenuQpLeaderStatus");
	if(!el) return;

	let qp = window.bonkHost.quickplay;
	let entries = Object.entries(qp.mapWins || {}).filter(entry => entry[1] > 0);

	if(entries.length === 0) {
		el.textContent = "LEADER: NONE (0/" + qp.roundsPer + ")";
		return;
	}

	entries.sort((a, b) => b[1] - a[1]);

	let leaderWins = entries[0][1];
	let tied = entries.filter(entry => entry[1] === leaderWins);

	if(tied.length > 1) {
		el.textContent = "LEADER: TIED (" + leaderWins + "/" + qp.roundsPer + ")";
		return;
	}

	let leaderId = parseInt(entries[0][0]);
	let leaderName = window.bonkHost.getQuickplayLeaderName(leaderId);

	el.textContent = "LEADER: " + leaderName + " (" + leaderWins + "/" + qp.roundsPer + ")";
};

window.bonkHost.updateQuickplayYourWinsStatus = () => {
	let el = document.getElementById("hostPlayerMenuQpYourWinsStatus");
	if(!el) return;

	let qp = window.bonkHost.quickplay;
	let myId = null;

	try {
		if(window.bonkHost.toolFunctions && window.bonkHost.toolFunctions.networkEngine) {
			myId = window.bonkHost.toolFunctions.networkEngine.getLSID();
		}
	}
	catch(e) {}

	let myWins = (myId !== null && qp.mapWins && qp.mapWins[myId]) ? qp.mapWins[myId] : 0;
	el.textContent = "YOU: " + myWins + "/" + qp.roundsPer;
};

window.bonkHost.updateQuickplayButtons = () => {
	let qp = window.bonkHost.quickplay;
	let qpBtn = document.getElementById("hostPlayerMenuQuickplayButton");
	let roundsBtn = document.getElementById("hostPlayerMenuQpRoundsButton");
	if(qpBtn) qpBtn.textContent = "QUICKPLAY: " + qp.state.toUpperCase();
	if(roundsBtn) {
		if(qp.roundsPer === 25) roundsBtn.textContent = "WINS: 1V1 MODE (25)";
		else if(qp.roundsPer === 9) roundsBtn.textContent = "WINS: TEAM MODE (9)";
		else roundsBtn.textContent = "WINS/QP: " + qp.roundsPer;
	}
	window.bonkHost.updateQuickplayLeaderStatus();
	window.bonkHost.updateQuickplayYourWinsStatus();
	if(window.bonkHost.updateExtraFeatureButtons) window.bonkHost.updateExtraFeatureButtons();
};

window.bonkHost.getQuickplayScores = () => {
	try {
		if(window.bonkHost.toolFunctions && window.bonkHost.toolFunctions.getGameSettings && window.bonkHost.toolFunctions.getGameSettings().ga === "f" && window[BIGVAR] && window[BIGVAR].bonkHost && window[BIGVAR].bonkHost.footballState) {
			return (window[BIGVAR].bonkHost.footballState.scores || []).map(s => (typeof s === "number" ? s : null));
		}
		if(window[BIGVAR] && window[BIGVAR].bonkHost && window[BIGVAR].bonkHost.state && Array.isArray(window[BIGVAR].bonkHost.state.scores)) {
			return window[BIGVAR].bonkHost.state.scores.map(s => (typeof s === "number" ? s : null));
		}
	}
	catch(e) {}
	return null;
};

window.bonkHost.getQuickplayLeaderWins = () => {
	let wins = Object.values(window.bonkHost.quickplay.mapWins || {});
	return wins.length ? Math.max(...wins) : 0;
};

window.bonkHost.resetQuickplayMapWins = () => {
	let qp = window.bonkHost.quickplay;
	qp.mapWins = {};
	qp.roundCounter = 0;
	qp.lastScores = window.bonkHost.getQuickplayScores();
	window.bonkHost.updateQuickplayButtons();
if(window.bonkHost.updateExtraFeatureButtons) window.bonkHost.updateExtraFeatureButtons();
};

window.bonkHost.recordQuickplayMapWin = () => {
	let qp = window.bonkHost.quickplay;
	let currentScores = window.bonkHost.getQuickplayScores();
	if(!currentScores) return false;

	if(!qp.lastScores) {
		qp.lastScores = currentScores.slice();
		return false;
	}

	let changed = false;
	for(let i = 0; i < currentScores.length; i++) {
		let before = qp.lastScores[i];
		let after = currentScores[i];
		if(typeof before !== "number") before = 0;
		if(typeof after !== "number") continue;
		let diff = after - before;
		if(diff > 0) {
			qp.mapWins[i] = (qp.mapWins[i] || 0) + diff;
			if(window.bonkHost.recordSessionWin) window.bonkHost.recordSessionWin(i, diff);
			changed = true;
		}
	}

	qp.lastScores = currentScores.slice();
	qp.roundCounter = window.bonkHost.getQuickplayLeaderWins();
	window.bonkHost.updateQuickplayButtons();
	return changed;
};

window.bonkHost.setQuickplaySupportToggles = (state) => {
	let freejoinBtn = document.getElementById("hostPlayerMenuFreejoin");
	let keepScoresBtn = document.getElementById("hostPlayerMenuKeepScores");

	if(state === "on") {
		window.bonkHost.freejoin = true;
		if(freejoinBtn) freejoinBtn.checked = true;
		if(keepScoresBtn) keepScoresBtn.checked = true;
	}
	else if(state === "off") {
		if(keepScoresBtn) keepScoresBtn.checked = false;
	}

	if(window.bonkHost.updateHostToolButtons) window.bonkHost.updateHostToolButtons();
};

window.bonkHost.getQuickplayMapCards = () => {
	let container = document.getElementById("maploadwindowmapscontainer");
	if(!container) return [];
	return [...container.getElementsByClassName("maploadwindowmapdiv")].filter(card => {
		if(card.id === "maploadwindowplaylistnew") return false;
		if(card.classList.contains("maploadwindowplaylistdiv")) return false;
		if(card.style.pointerEvents === "none") return false;
		return true;
	});
};

window.bonkHost.captureQuickplayQueue = (silent = false) => {
	let cards = window.bonkHost.getQuickplayMapCards();
	if(cards.length === 0) {
		if(!silent && window.bonkHost.menuFunctions) {
			window.bonkHost.menuFunctions.showStatusMessage("* Open a playlist, click into it, then enable Quickplay", "#b53030", false);
		}
		return false;
	}
	window.bonkHost.quickplay.queue = cards;
	if(window.bonkHost.quickplay.index >= cards.length) window.bonkHost.quickplay.index = -1;
	if(!silent && window.bonkHost.menuFunctions) {
		window.bonkHost.menuFunctions.showStatusMessage("* Quickplay queue loaded: " + cards.length + " maps", "#b53030", false);
	}
	return true;
};

window.bonkHost.shuffleQuickplayQueue = () => {
	let qp = window.bonkHost.quickplay;
	if(qp.queue.length === 0 && !window.bonkHost.captureQuickplayQueue()) return;
	let history = (window.bonkHost.extraFeatures && window.bonkHost.extraFeatures.mapHistoryNames) ? window.bonkHost.extraFeatures.mapHistoryNames.slice(0, 5) : [];
	let queue = window.bonkHost.shuffleArray(qp.queue);
	if(window.bonkHost.extraFeatures && window.bonkHost.extraFeatures.smartShuffle) {
		queue.sort((a, b) => {
			let an = window.bonkHost.getQuickplayCardName(a);
			let bn = window.bonkHost.getQuickplayCardName(b);
			return (history.includes(an) ? 1 : 0) - (history.includes(bn) ? 1 : 0);
		});
	}
	qp.queue = queue;
	qp.index = -1;
	qp.roundCounter = 0;
	if(window.bonkHost.menuFunctions) {
		window.bonkHost.menuFunctions.showStatusMessage("* " + ((window.bonkHost.extraFeatures && window.bonkHost.extraFeatures.smartShuffle) ? "Smart shuffled" : "Shuffled") + " Quickplay queue", "#b53030", false);
	}
	window.bonkHost.updateExtraFeatureButtons && window.bonkHost.updateExtraFeatureButtons();
};

window.bonkHost.setQuickplayState = (state) => {
	let qp = window.bonkHost.quickplay;
	if(state === "on" && qp.queue.length === 0 && !window.bonkHost.captureQuickplayQueue()) return;
	qp.state = state;
	if(state === "on") {
		window.bonkHost.setQuickplaySupportToggles("on");
		window.bonkHost.resetQuickplayMapWins();
	}
	else if(state === "off") {
		qp.roundCounter = 0;
		qp.startedGame = false;
		qp.awaitingNaturalRoundEnd = false;
		window.bonkHost.setQuickplaySupportToggles("off");
		qp.mapWins = {};
		qp.lastScores = null;
	}
	// Paused keeps Freejoin and Keep Scores as-is.
	window.bonkHost.updateQuickplayButtons();
	if(window.bonkHost.menuFunctions) {
		window.bonkHost.menuFunctions.showStatusMessage("* Quickplay " + state.toUpperCase(), "#b53030", false);
	}
};

window.bonkHost.loadQuickplayMap = (direction = 1, startAfterLoad = true) => {
	let qp = window.bonkHost.quickplay;
	if(!isHost()) {
		window.bonkHost.menuFunctions.showStatusMessage("* Must be room host to use Quickplay", "#b53030", false);
		return;
	}
	if(qp.queue.length === 0 && !window.bonkHost.captureQuickplayQueue()) return;
	if(qp.queue.length === 0) return;
	qp.index = (qp.index + direction + qp.queue.length) % qp.queue.length;
	let card = qp.queue[qp.index];
	if(!card || !document.body.contains(card)) {
		qp.queue = [];
		if(!window.bonkHost.captureQuickplayQueue()) return;
		qp.index = (direction > 0 ? 0 : qp.queue.length - 1);
		card = qp.queue[qp.index];
	}

	qp.busy = true;
	qp.awaitingNaturalRoundEnd = false;
	qp.startedGame = false;
	if(window.bonkHost.recordCurrentMapName) window.bonkHost.recordCurrentMapName(card);
	window.bonkHost.resetQuickplayMapWins();

	// Use a real click event and give the playlist/map loader time to apply
	// the selected map before starting. This avoids the map only loading in the
	// map window while the next game starts with the previous map.
	card.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true, view: window }));

	setTimeout(() => {
		qp.busy = false;
		if(startAfterLoad && qp.state === "on" && isHost()) {
			qp.startedGame = true;
			window.bonkHost.startGame();
		}
	}, 1200);
};

window.bonkHost.quickplayHandleRoundPacket = () => {
	let qp = window.bonkHost.quickplay;
	if(qp.state !== "on" || qp.busy || !isHost()) return;

	// Ignore duplicate round-end packets.
	if(Date.now() - (qp.lastRoundPacketTime || 0) < 1200) return;
	qp.lastRoundPacketTime = Date.now();
	qp.startedGame = false;

	// Wait a moment so Bonk's state/scores update first, then count whichever
	// player/team actually gained a point on this map. This prevents Quickplay
	// from advancing just because 3 total rounds happened.
	setTimeout(() => {
		if(qp.state !== "on" || qp.busy || !isHost()) return;
		let changed = window.bonkHost.recordQuickplayMapWin();
		if(!changed) return;

		if(window.bonkHost.getQuickplayLeaderWins() >= qp.roundsPer) {
			window.bonkHost.resetQuickplayMapWins();
			setTimeout(() => {
				if(qp.state === "on" && isHost()) window.bonkHost.loadQuickplayMap(1, true);
			}, 900);
		}
	}, 150);

	// If the Quickplay win target has not been reached, do nothing.
	// Bonk continues into the next round naturally.
};

window.bonkHost.quickplayHandleRoundEnd = () => {
	// Disabled on purpose. The old menu-show based trigger fired when using
	// lobby hotkeys like Alt+L, which caused unwanted restarts. Quickplay now
	// advances from the actual round-end websocket packet only.
	return;
};

window.bonkHost.patchQuickplayWebSocket = () => {
	if(window.bonkHost.quickplaySocketPatched) return;
	window.bonkHost.quickplaySocketPatched = true;
	const originalSend = WebSocket.prototype.send;
	WebSocket.prototype.send = function(data) {
		try {
			if(window.bonkHost && window.bonkHost.extraFeatures) window.bonkHost.extraFeatures.activeSocket = this;
			let observedPacket = window.bonkHost && window.bonkHost.parseSocketPacket ? window.bonkHost.parseSocketPacket(data) : null;
			if(observedPacket && window.bonkHost.handleObservedPacket) window.bonkHost.handleObservedPacket("out", observedPacket);
			if(typeof data === "string" && window.bonkHost && window.bonkHost.quickplay && isHost()) {
				let qp = window.bonkHost.quickplay;
				if(data.startsWith("42[47,") && qp.state === "on") {
					window.bonkHost.quickplayHandleRoundPacket();
				}
				else if(data.startsWith("42[5,") && qp.state === "on") {
					let packet = JSON.parse(data.substring(2));
					if(packet && packet[1] && packet[1].gs) {
						// Smooth Quickplay loophole: keep the visible rounds setting alone,
						// but quietly raise only the outgoing start packet. This prevents
						// Bonk from ending the full match while Quickplay counts rounds
						// internally and lets normal rounds continue without forced restarts.
						packet[1].gs.wl = Math.max(packet[1].gs.wl || 0, 999);
						data = "42" + JSON.stringify(packet);
					}
				}
			}
		}
		catch(e) {
			console.log("Quickplay websocket patch error", e);
		}
		return originalSend.call(this, data);
	};
};
window.bonkHost.patchQuickplayWebSocket();

document.getElementById("hostPlayerMenuQuickplayButton").addEventListener("click", () => {
	let state = window.bonkHost.quickplay.state;
	window.bonkHost.setQuickplayState(state === "off" ? "on" : state === "on" ? "paused" : "off");
});

document.getElementById("hostPlayerMenuQpRoundsButton").addEventListener("click", () => {
	let qp = window.bonkHost.quickplay;
	let options = [1, 3, 5, 9];
	let currentIndex = options.indexOf(qp.roundsPer);
	qp.roundsPer = options[(currentIndex + 1) % options.length];
	if(window.bonkHost.resetQuickplayMapWins) window.bonkHost.resetQuickplayMapWins();
	if(window.bonkHost.updateQuickplayButtons) window.bonkHost.updateQuickplayButtons();
	if(window.bonkHost.menuFunctions) {
		window.bonkHost.menuFunctions.showStatusMessage("* Wins/QP set to " + qp.roundsPer, "#b53030", false);
	}
});

document.getElementById("hostPlayerMenuQpShuffleButton").addEventListener("click", () => {
	window.bonkHost.shuffleQuickplayQueue();
});


document.getElementById("hostPlayerMenuSettingsSmartShuffleButton").addEventListener("click", () => {
	window.bonkHost.extraFeatures.smartShuffle = !window.bonkHost.extraFeatures.smartShuffle;
	window.bonkHost.updateExtraFeatureButtons();
	if(window.bonkHost.quickplay.queue.length) window.bonkHost.shuffleQuickplayQueue();
});

document.getElementById("hostPlayerMenuAutoHostButton").addEventListener("click", () => {
	window.bonkHost.extraFeatures.autoHost = !window.bonkHost.extraFeatures.autoHost;
	window.bonkHost.updateExtraFeatureButtons();
});

document.getElementById("hostPlayerMenuSetAutoHostButton").addEventListener("click", () => {
	window.bonkHost.setAutoHostTarget();
});

document.getElementById("hostPlayerMenuSettingsRejoinProtectionButton").addEventListener("click", () => {
	window.bonkHost.extraFeatures.rejoinProtection = !window.bonkHost.extraFeatures.rejoinProtection;
	window.bonkHost.updateExtraFeatureButtons();
});

document.getElementById("hostPlayerMenuAutoTeamJoinButton").addEventListener("click", () => {
	window.bonkHost.extraFeatures.autoNewJoinBalance = !window.bonkHost.extraFeatures.autoNewJoinBalance;
	window.bonkHost.updateExtraFeatureButtons();
	if(window.bonkHost.menuFunctions) {
		window.bonkHost.menuFunctions.showStatusMessage("* Auto Team Join " + (window.bonkHost.extraFeatures.autoNewJoinBalance ? "ON" : "OFF"), "#b53030", false);
	}
});

document.getElementById("hostPlayerMenuAutoJoinNextButton").addEventListener("click", () => {
	window.bonkHost.extraFeatures.autoJoinNext = !window.bonkHost.extraFeatures.autoJoinNext;
	window.bonkHost.updateExtraFeatureButtons();
	if(window.bonkHost.menuFunctions) {
		window.bonkHost.menuFunctions.showStatusMessage("* Auto Join Next " + (window.bonkHost.extraFeatures.autoJoinNext ? "ON" : "OFF"), "#b53030", false);
	}
	if(window.bonkHost.extraFeatures.autoJoinNext && window.bonkHost.tryAutoJoinNext) {
		window.bonkHost.tryAutoJoinNext("manual");
	}
});

document.getElementById("hostPlayerMenuShowFlagsButton").addEventListener("click", () => {
	window.bonkHost.extraFeatures.showFlags = true;
	window.bonkHost.updateExtraFeatureButtons();
	if(window.bonkHost.applyCountryFlagsToLobby) window.bonkHost.applyCountryFlagsToLobby();
	if(window.bonkHost.menuFunctions) {
		window.bonkHost.menuFunctions.showStatusMessage("* Country flags display automatically when Bonk provides country data.", "#b53030", false);
	}
});




document.getElementById("hostPlayerMenuPatchNotesCompactButton").addEventListener("click", () => {
	window.bonkHost.extraFeatures.patchNotesCompact = !window.bonkHost.extraFeatures.patchNotesCompact;
	window.bonkHost.updateExtraFeatureButtons();
});

document.getElementById("hostPlayerMenuPlayerNotesButton").addEventListener("click", () => {
	window.bonkHost.savePlayerNote();
});

document.getElementById("hostPlayerMenuSmartBalanceButton").addEventListener("click", () => {
	window.bonkHost.extraFeatures.autoNewJoinBalance = !window.bonkHost.extraFeatures.autoNewJoinBalance;
	window.bonkHost.updateExtraFeatureButtons();
	if(window.bonkHost.menuFunctions) {
		window.bonkHost.menuFunctions.showStatusMessage("* Auto Team Join " + (window.bonkHost.extraFeatures.autoNewJoinBalance ? "ON" : "OFF"), "#b53030", false);
	}
});

document.getElementById("hostPlayerMenuCaptainsButton").addEventListener("click", () => {
	window.bonkHost.extraFeatures.captainsMode = !window.bonkHost.extraFeatures.captainsMode;
	window.bonkHost.updateExtraFeatureButtons();
	if(window.bonkHost.extraFeatures.captainsMode) window.bonkHost.runCaptainsMode();
});

document.getElementById("hostPlayerMenuQpPrevButton").addEventListener("click", () => {
	window.bonkHost.resetQuickplayMapWins();
	window.bonkHost.loadQuickplayMap(-1, window.bonkHost.quickplay.state === "on");
});

document.getElementById("hostPlayerMenuQpNextButton").addEventListener("click", () => {
	window.bonkHost.resetQuickplayMapWins();
	window.bonkHost.loadQuickplayMap(1, window.bonkHost.quickplay.state === "on");
});

window.bonkHost.updateQuickplayButtons();

document.getElementById("hostPlayerMenuShuffleRBButton").addEventListener("click", () => {
	window.bonkHost.shuffleRedBlue();
});

document.getElementById("hostPlayerMenuAutoBalanceButton").addEventListener("click", () => {
	window.bonkHost.autoBalanceRedBlue();
});

document.getElementById("hostPlayerMenuSwapTeamsButton").addEventListener("click", () => {
	window.bonkHost.swapRedBlue();
});

document.getElementById("hostPlayerMenuAllSpecButton").addEventListener("click", () => {
	window.bonkHost.moveAllSpec();
});

document.getElementById("hostPlayerMenuAkToggleButton").addEventListener("click", () => {
	window.bonkHost.autoKickGuests = !window.bonkHost.autoKickGuests;
	window.bonkHost.updateAkButton();
	window.bonkHost.menuFunctions.showStatusMessage(
		"* Guest Autokick " + (window.bonkHost.autoKickGuests ? "ON" : "OFF"),
		"#b53030",
		false
	);
});

/*document.getElementById('newbonklobby').appendChild(document.getElementById('teamshufflebutton'));
document.getElementById('newbonklobby').appendChild(document.getElementById('teamshufflebox'));
document.getElementById('teamshufflebutton').addEventListener('click', () => {
	document.getElementById('teamshufflebox').style.visibility = document.getElementById('teamshufflebox').style.visibility == "hidden" ? "visible" : "hidden";
});
*/
document.getElementById("newbonklobby_specbutton").addEventListener("dblclick", () => {moveEveryone(0)});
document.getElementById("newbonklobby_ffabutton").addEventListener("dblclick", () => {moveEveryone(1)});
document.getElementById("newbonklobby_bluebutton").addEventListener("dblclick", () => {moveEveryone(3)});
document.getElementById("newbonklobby_redbutton").addEventListener("dblclick", () => {moveEveryone(2)});
document.getElementById("newbonklobby_greenbutton").addEventListener("dblclick", () => {moveEveryone(4)});
document.getElementById("newbonklobby_yellowbutton").addEventListener("dblclick", () => {moveEveryone(5)});

document.getElementById("newbonklobby_settingsbox").appendChild(document.getElementById("newbonklobby_hostprevmap"));
document.getElementById("newbonklobby_settingsbox").appendChild(document.getElementById("newbonklobby_hostnextmap"));

const updateMapHistoryButtons = () => {
	if(window.bonkHost.toolFunctions.getGameSettings().ga === "b") {
		document.getElementById("newbonklobby_hostprevmap").style.display = "";
		document.getElementById("newbonklobby_hostnextmap").style.display = "";
		if(isHost()) {
			document.getElementById("newbonklobby_hostprevmap").classList.toggle("brownButtonDisabled", mapHistoryIndex >= mapHistory.length - 1);
			document.getElementById("newbonklobby_hostnextmap").classList.toggle("brownButtonDisabled", mapHistoryIndex === 0);

			// Make sure they are the topmost elements
			document.getElementById("newbonklobby_settingsbox").appendChild(document.getElementById("newbonklobby_hostprevmap"));
			document.getElementById("newbonklobby_settingsbox").appendChild(document.getElementById("newbonklobby_hostnextmap"));
		}
		else {
			document.getElementById("newbonklobby_hostprevmap").classList.add("brownButtonDisabled");
			document.getElementById("newbonklobby_hostnextmap").classList.add("brownButtonDisabled");
		}
	}
	else {
		document.getElementById("newbonklobby_hostprevmap").style.display = "none";
		document.getElementById("newbonklobby_hostnextmap").style.display = "none";
	}
}

document.getElementById("newbonklobby_hostprevmap").addEventListener("click", () => {
	if(!isHost() || mapHistoryIndex >= mapHistory.length - 1) return;
	mapHistoryIndex++;
	let gs = window.bonkHost.toolFunctions.getGameSettings();
	gs.map = mapHistory[mapHistoryIndex];
	window.bonkHost.menuFunctions.setGameSettings(gs);
	window.bonkHost.menuFunctions.updateGameSettings();
	window.bonkHost.toolFunctions.networkEngine.sendMapAdd(gs.map);
	updateMapHistoryButtons();
});

document.getElementById("newbonklobby_hostnextmap").addEventListener("click", () => {
	if(!isHost() || mapHistoryIndex == 0) return;
	mapHistoryIndex--;
	let gs = window.bonkHost.toolFunctions.getGameSettings();
	gs.map = mapHistory[mapHistoryIndex];
	window.bonkHost.menuFunctions.setGameSettings(gs);
	window.bonkHost.menuFunctions.updateGameSettings();
	window.bonkHost.toolFunctions.networkEngine.sendMapAdd(gs.map);
	updateMapHistoryButtons();
});

document.getElementById("newbonklobby_roundsinput").addEventListener("focus", e => {
	e.target.value = "";
});
document.getElementById("newbonklobby_roundsinput").addEventListener("blur", e => {
	if(e.target.value == "") {
		e.target.value = window.bonkHost.toolFunctions.getGameSettings().wl;
	}
});

const moveEveryone = (team) => {
	if(!isHost()) return;
	for(let id of window.bonkHost.toolFunctions.networkEngine.getConnectedPlayers()) {
		window.bonkHost.toolFunctions.networkEngine.changeOtherTeam(id, team);
	}
}

window.bonkHost.getActivePlayerIDs = () => {
	let playerIDs = [];
	for(let i = 0; i < window.bonkHost.players.length; i++) {
		let player = window.bonkHost.players[i];

		// Only include players who are not spectators.
		if(player && player.team !== 0) {
			playerIDs.push(i);
		}
	}
	return playerIDs;
}

window.bonkHost.shuffleArray = (list) => {
	let nl = list.slice();
	for(let i = nl.length - 1; i > 0; i--) {
		let j = Math.floor(Math.random() * (i + 1));
		[nl[i], nl[j]] = [nl[j], nl[i]];
	}
	return nl;
}

window.bonkHost.shuffleRedBlue = () => {
	// Universal Shuffle R/B now balances by session winrate instead of pure random order.
	if(window.bonkHost.balanceBySessionWinrate) {
		window.bonkHost.balanceBySessionWinrate();
		return;
	}
}

window.bonkHost.autoBalanceRedBlue = () => {
	if(!isHost()) {
		window.bonkHost.menuFunctions.showStatusMessage("* Must be room host to use Auto Balance", "#b53030", false);
		return;
	}

	window.bonkHost.extraFeatures.autoNewJoinBalance = !window.bonkHost.extraFeatures.autoNewJoinBalance;
	if(window.bonkHost.updateExtraFeatureButtons) window.bonkHost.updateExtraFeatureButtons();

	window.bonkHost.menuFunctions.showStatusMessage(
		"* Auto Balance will " + (window.bonkHost.extraFeatures.autoNewJoinBalance ? "place new joins on the weaker/smaller team" : "no longer move new joins"),
		"#b53030",
		false
	);
}

window.bonkHost.swapRedBlue = () => {
	if(!isHost()) {
		window.bonkHost.menuFunctions.showStatusMessage("* Must be room host to use Swap Red/Blue", "#b53030", false);
		return;
	}

	for(let i = 0; i < window.bonkHost.players.length; i++) {
		let player = window.bonkHost.players[i];
		if(!player) continue;

		if(player.team === 2) {
			window.bonkHost.toolFunctions.networkEngine.changeOtherTeam(i, 3);
		}
		else if(player.team === 3) {
			window.bonkHost.toolFunctions.networkEngine.changeOtherTeam(i, 2);
		}
	}

	window.bonkHost.menuFunctions.showStatusMessage("* Swapped Red and Blue teams", "#b53030", false);
}

window.bonkHost.moveAllSpec = () => {
	if(!isHost()) {
		window.bonkHost.menuFunctions.showStatusMessage("* Must be room host to use All Spec", "#b53030", false);
		return;
	}

	for(let id of window.bonkHost.toolFunctions.networkEngine.getConnectedPlayers()) {
		window.bonkHost.toolFunctions.networkEngine.changeOtherTeam(id, 0);
	}

	window.bonkHost.menuFunctions.showStatusMessage("* Moved everyone to spectator", "#b53030", false);
}

window.bonkHost.setHostToolButtonText = (id, label, enabled) => {
	let btn = document.getElementById(id);
	if(!btn) return;
	let warning = btn.querySelector("#hostPlayerMenuRespawningRequiredWarning");
	btn.childNodes[0].textContent = label + ": " + (enabled ? "ON" : "OFF");
	btn.checked = !!enabled;
	if(warning) btn.appendChild(warning);
}

window.bonkHost.updateHostToolButtons = () => {
	window.bonkHost.setHostToolButtonText("hostPlayerMenuTeamlock", "TEAMLOCK", document.getElementById("hostPlayerMenuTeamlock")?.checked);
	window.bonkHost.setHostToolButtonText("hostPlayerMenuFreejoin", "FREEJOIN", window.bonkHost.freejoin);
	window.bonkHost.setHostToolButtonText("hostPlayerMenuKeepScores", "KEEP SCORES", document.getElementById("hostPlayerMenuKeepScores")?.checked);
	window.bonkHost.setHostToolButtonText("hostPlayerMenuCheatDetectionCheckbox", "CHEAT DETECTION", window.bonkHost.cheatDetection);
	window.bonkHost.setHostToolButtonText("hostPlayerMenuKeepPositions", "KEEP POSITIONS", document.getElementById("hostPlayerMenuKeepPositions")?.checked);
	window.bonkHost.updateAkButton();
}

window.bonkHost.updateAkButton = () => {
	let btn = document.getElementById("hostPlayerMenuAkToggleButton");
	if(!btn) return;
	btn.textContent = "GUEST AK: " + (window.bonkHost.autoKickGuests ? "ON" : "OFF");
	btn.checked = !!window.bonkHost.autoKickGuests;
}


const BIGVAR = newStr.match(/[A-Za-z0-9$_]+\[[0-9]{6}\]/)[0].split('[')[0];

const isHost = () => {
	return window.bonkHost.toolFunctions.networkEngine.getLSID() === window.bonkHost.toolFunctions.networkEngine.hostID && !window.bonkHost.toolFunctions.getGameSettings().q;
}

window.bonkHost.wrap = () => {
	// Event for when game finishes loading. Using a setInterval isn't optimal and we might miss some, but it's good enough for now
	const gameLoadedWaiter = setInterval(() => {
		// I hope a better way of doing this exists
		if(window.bonkHost.menuFunctions !== undefined && Object.keys(window.bonkHost.menuFunctions).length >= 27) {
			clearInterval(gameLoadedWaiter);
		}
		else return;

		// Wrap menuFunctions
		for(const i of Object.keys(window.bonkHost.menuFunctions)) {
			if(typeof window.bonkHost.menuFunctions[i] !== "function") continue;
			const ogFunc = window.bonkHost.menuFunctions[i];
			window.bonkHost.menuFunctions[i] = function() {
				let response = ogFunc.apply(window.bonkHost.menuFunctions, arguments);
				switch(i) {
					case "returnFromTesting":
					case "show":
						window.bonkHost.playerManagement.canBeVisible = true;
						window.bonkHost.menuFunctions.visible = true;
						setTimeout(() => {
							if(window.bonkHost.menuFunctions && window.bonkHost.menuFunctions.updatePlayers) {
								window.bonkHost.menuFunctions.updatePlayers();
							}
							window.bonkHost.playerManagement.show();
						}, 50);
						if(window[BIGVAR].bonkHost.state) {
							if(Math.max(...window[BIGVAR].bonkHost.state.scores) >= window.bonkHost.toolFunctions.getGameSettings().wl) {
								for(let i = 0; i < window[BIGVAR].bonkHost.state.scores.length; i++) {
									if(window[BIGVAR].bonkHost.state.scores === null) continue;
									window[BIGVAR].bonkHost.state.scores[i] = 0;
								}
							}
						}
						break;
					case "hide":
						window.bonkHost.menuFunctions.visible = true;
						window.bonkHost.playerManagement.canBeVisible = true;
						window.bonkHost.playerManagement.show();
						//Intentional fallthrough to update host status

					case "updateGameSettings":
						window.bonkHost.handleHostChange(window.bonkHost.toolFunctions.networkEngine.hostID === window.bonkHost.toolFunctions.networkEngine.getLSID());
						break;
					case "handleHostChange":
						if(isHost()) {
							window.bonkHost.stateFunctions.chatStatus("* You are now the host of this game")
						}
						//Intentional fallthrough to update host status
					case "handleHostLeft":
						window.bonkHost.handleHostChange(arguments[1] === window.bonkHost.toolFunctions.networkEngine.getLSID());
						break;
					case "handlePlayerJoin":
						window.bonkHost.handlePlayerJoin(arguments[1]);
						break;
					case "updatePlayers":
						while(document.getElementById("hostPlayerMenuBox").firstChild) {
							document.getElementById("hostPlayerMenuBox").removeChild(document.getElementById("hostPlayerMenuBox").firstChild);
						}
						[...document.getElementsByClassName("newbonklobby_playerentry")].sort((a, b) => {
							return window.bonkHost.players.filter(p=>p).findIndex(p => a.childNodes[1].textContent === p.userName) - window.bonkHost.players.filter(p=>p).findIndex(p => b.childNodes[1].textContent == p.userName);
						}).filter(p => {
							// Idk if this is needed but it doesn't hurt to have it
							return window.bonkHost.players.filter(pp=>pp).findIndex(pp => p.childNodes[1].textContent === pp.userName) !== -1;
						}).forEach(p => {
							if(p.classList.contains("bonkhost_playerentry")) return;
							window.bonkHost.playerManagement.addPlayer(p);
						})
						window.bonkHost.updatePlayers();
						if(window.bonkHost.applyCountryFlagsToLobby) window.bonkHost.applyCountryFlagsToLobby();
						if(window.bonkHost.updateRejoinProtectionSnapshot) window.bonkHost.updateRejoinProtectionSnapshot();
						break;
					case "updatePings":
						let pings = window.bonkHost.players.filter(e=>e).map(e=>e.ping);
						for(let i = 0; i < [...document.getElementById("hostPlayerMenuBox").children].length; i++) {
							document.getElementById("hostPlayerMenuBox").children[i].getElementsByClassName("newbonklobby_playerentry_pingtext")[0].textContent = pings[i] + "ms";
						}
						break;
				}
				return response;
			}
		}

		for(const i of Object.keys(window.bonkHost.toolFunctions.networkEngine)) {
			if(typeof window.bonkHost.toolFunctions.networkEngine[i] !== "function") continue;
			const ogFunc = window.bonkHost.toolFunctions.networkEngine[i];
			window.bonkHost.toolFunctions.networkEngine[i] = function() {
				let response = ogFunc.apply(window.bonkHost.toolFunctions.networkEngine, arguments);
				switch(i) {
					case "changeOtherTeam":
						window.bonkHost.playerManagement.movePlayer(arguments[1], arguments[0]);
						break;
					case "changeOwnTeam":
						window.bonkHost.playerManagement.movePlayer(arguments[0]);
						break;
					case "sendMapAdd":
						if(arguments[0].m.dbid !== mapHistory[mapHistoryIndex]?.m.dbid || arguments[0].m.dbv !== mapHistory[mapHistoryIndex]?.m.dbv) {
							mapHistory.splice(0, mapHistoryIndex);
							mapHistoryIndex = 0;
							mapHistory.unshift(arguments[0]);
						}
						updateMapHistoryButtons();
						break;
					case "destroy":
						window.bonkHost.playerManagement.hide();
						window.bonkHost.playerSus = [];
						break;
				}
				return response;
			}
		}

		window.bonkHost.toolFunctions.networkEngine.on("teamLockChange", lock => {
			document.getElementById("hostPlayerMenuTeamlock").checked = lock;
			window.bonkHost.updateHostToolButtons();
		});

		let mapUpdateTimeout;
		window.bonkHost.toolFunctions.networkEngine.on("status", code => {
			if(code === "rate_limit_sma") {
				// A second of delay is hopefully enough. This is needed because map history can easily get ratelimited, so we want to update players with the current map before the game begins.
				clearTimeout(mapUpdateTimeout);
				mapUpdateTimeout = setTimeout(() => {
					if(!isHost()) return;
					window.bonkHost.toolFunctions.networkEngine.sendMapAdd(window.bonkHost.toolFunctions.getGameSettings().map);
				}, 1000);
			}
		});

		for(const i of Object.keys(window.bonkHost.toolFunctions)) {
			if(typeof window.bonkHost.toolFunctions[i] !== "function") continue;
			const ogFunc = window.bonkHost.toolFunctions[i];
			window.bonkHost.toolFunctions[i] = function() {
				let response = ogFunc.apply(window.bonkHost.toolFunctions, arguments);
				switch(i) {
					case "recvInputs":
						if(arguments[2] !== "node") break;
						let playerId = arguments[0];
						let packet = arguments[1];

						if(!window.bonkHost.playerSus[playerId]) {
							window.bonkHost.playerSus[playerId] = {
								lagHistory: [],
								lagginessHistory: [],
								cheatWarning: false
							}
						}

						if(packet.type === "commands") {
							window.bonkHost.playerSus[playerId].cheatWarning = true;
						}

						if(window.bonkHost.cheatDetection) {
							let ping = (window.bonkHost.players[playerId].ping + window.bonkHost.players[window.bonkHost.toolFunctions.networkEngine.getLSID()].ping) / 2;

							if(isNaN(packet.f - window.bonkHost.fig)) {
								return;
							}

							window.bonkHost.playerSus[playerId].lagHistory.push(Math.floor(1000*(1/30)*(packet.f - window.bonkHost.fig)) + ping);
							if(window.bonkHost.playerSus[playerId].lagHistory.length > 20) {
								window.bonkHost.playerSus[playerId].lagHistory.shift();
							}

							let avgPingDiff = (window.bonkHost.playerSus[playerId].lagHistory.length > 2) ?
								((
									window.bonkHost.playerSus[playerId].lagHistory.reduce((a, b) => a+b) -
									Math.max.apply(Math, window.bonkHost.playerSus[playerId].lagHistory) -
									Math.min.apply(Math, window.bonkHost.playerSus[playerId].lagHistory)
								) / window.bonkHost.playerSus[playerId].lagHistory.length - 2) : 0

							if(isNaN(avgPingDiff)) return;
							window.bonkHost.playerSus[playerId].lagginessHistory.push(avgPingDiff);

							window.bonkHost.drawLagginess(playerId);
						}
						break;
				}
				return response;
			}
		}

		for(const i of Object.keys(window.bonkHost.stateFunctions)) {
			if(typeof window.bonkHost.stateFunctions[i] !== "function") continue;
			const ogFunc = window.bonkHost.stateFunctions[i];
			window.bonkHost.stateFunctions[i] = function() {
				let response = ogFunc.apply(window.bonkHost.stateFunctions, arguments);
				switch(i) {
					case "go":
					case "goInProgress":
						window.bonkHost.handleHostChange(false);
						window.bonkHost.playerManagement.canBeVisible = true;
						window.bonkHost.menuFunctions.visible = true;
						window.bonkHost.menuFunctions.updatePlayers();
						window.bonkHost.playerManagement.show();
						document.getElementById("hostPlayerMenuTeamlock").checked = window.bonkHost.toolFunctions.getGameSettings().tl;
						window.bonkHost.updateHostToolButtons();
						break;
				}
				return response;
			}
		}
	}, 50);
}


const chatHandler = e => {
	if(e.keyCode === 13) {
		if(e.target.value.length > 0) {
			if(e.target.value[0] === "/") {
				let command = e.target.value.split(" ")[0].substring(1);
				let args = e.target.value.split(" ").slice(1);
				newArgs = [];
				for(let i = 0; i < args.length; i++) {
					if(args[i][0] === '"' && args[i][args[i].length - 1] !== '"') {
						let str = args[i];
						for(let j = i + 1; j < args.length; j++) {
							str += " " + args[j];
							if(args[j][args[j].length - 1] === '"') {
								i = j;
								break;
							}
						}
						newArgs.push(str.substring(1, str.length - 1));
					}
					else if(args[i][0] === '"' && args[i][args[i].length - 1] === '"') {
						newArgs.push(args[i].substring(1, args[i].length - 1));
					}
					else {
						newArgs.push(args[i]);
					}
				}
				args = newArgs;
				// Save without reference
				let oldMsg = e.target.value + "";
				e.target.value = "";
				if(command == "hhelp") {
					window.bonkHost.menuFunctions.showStatusMessage("/balance * -100 to 100 -- Balances everyone","#b53030",false);
					window.bonkHost.menuFunctions.showStatusMessage("/balanceall -100 to 100 -- Balances everyone","#b53030",false);
					window.bonkHost.menuFunctions.showStatusMessage("/scoreboard -- Shows the scoreboard from the last game","#b53030",false);
					window.bonkHost.menuFunctions.showStatusMessage("/start -- Starts the game","#b53030",false);
					window.bonkHost.menuFunctions.showStatusMessage("/freejoin on/off -- Lets people join during the game","#b53030",false);
					window.bonkHost.menuFunctions.showStatusMessage('/host "user name" -- Gives host to the player',"#b53030",false);
					window.bonkHost.menuFunctions.showStatusMessage('/ban "user name" -- Kicks the player and prevents them from joining with the same account',"#b53030",false);
					window.bonkHost.menuFunctions.showStatusMessage('/unban "user name" -- Unbans the player but doesn\'t remove kicked status',"#b53030",false);
					window.bonkHost.menuFunctions.showStatusMessage('/bans -- Lists banned players',"#b53030",false);
					window.bonkHost.menuFunctions.showStatusMessage('/resetpos -- Resets host menu position',"#b53030",false);
				}
				else if(command == "start") {
					if(!isHost()) {
						window.bonkHost.menuFunctions.showStatusMessage("* Must be room host to use this command", "#b53030", false);
						return;
					}
					window.bonkHost.startGame();
				}
				else if(command == "freejoin") {
					if(args.length == 0) {
						window.bonkHost.freejoin = !window.bonkHost.freejoin;
					}
					else if(["true", "on", "yes", "enable"].includes(args[0])) {
						window.bonkHost.freejoin = true;
					}
					else if(["false", "off", "no", "disable"].includes(args[0])) {
						window.bonkHost.freejoin = false;
					}
					window.bonkHost.menuFunctions.showStatusMessage("* Freejoin " + (window.bonkHost.freejoin ? "on" : "off"), "#b53030", false);
					document.getElementById('hostPlayerMenuFreejoin').checked = window.bonkHost.freejoin;
					window.bonkHost.updateHostToolButtons();
				}
				else if(command == "host") {
					if(args.length === 0) {
						window.bonkHost.menuFunctions.showStatusMessage(`* Usage: /${command} "user name"`, "#b53030", false);
						return;
					}
					if(!isHost()) {
						window.bonkHost.menuFunctions.showStatusMessage("* Must be room host to use this command", "#b53030", false);
						return;
					}
					let id = window.bonkHost.players.findIndex(e => {return e && e.userName === args[0]});
					if(id !== -1) {
						window.bonkHost.toolFunctions.networkEngine.sendHostChange(id);
					}
					else {
						window.bonkHost.menuFunctions.showStatusMessage("* Giving host failed, username " + args[0] + " not found in this room", "#b53030", false);
					}
				}
				else if(command == "ban") {
					if(args.length === 0) {
						window.bonkHost.menuFunctions.showStatusMessage(`* Usage: /${command} "user name"`, "#b53030", false);
						return;
					}
					window.bonkHost.ban(args[0]);
				}
				else if(command == "unban") {
					if(args.length === 0) {
						window.bonkHost.menuFunctions.showStatusMessage(`* Usage: /${command} "user name"`, "#b53030", false);
						return;
					}
					if(!window.bonkHost.bans.includes(args[0])) {
						window.bonkHost.menuFunctions.showStatusMessage(`* Username ${args[0]} not found in ban list`, "#b53030", false);
						return;
					}
					window.bonkHost.menuFunctions.showStatusMessage(`* Removing ${args[0]} from ban list`, "#b53030", false);
					window.bonkHost.bans.splice(window.bonkHost.bans.indexOf(args[0]), 1);
				}
				else if(command == "bans") {
					window.bonkHost.menuFunctions.showStatusMessage("* List of banned players:\n" + window.bonkHost.bans.join("\n"), "#b53030", false);
				}
				else if((command == "balance" && args[0] === "*") || command == "balanceall") {
					if(args.length === 0) {
						window.bonkHost.menuFunctions.showStatusMessage(`* Usage: /${command} "user name"`, "#b53030", false);
						return;
					}
					if(!isHost()) {
						window.bonkHost.menuFunctions.showStatusMessage("* Must be room host to use this command", "#b53030", false);
						return;
					}
					let amount = parseInt(args[args.length - 1]);
					if(amount < -100 || amount > 100 || isNaN(amount)) {
						window.bonkHost.menuFunctions.showStatusMessage("* Balance must be between -100 and 100", "#b53030", false);
						return;
					}
					for(let i = 0; i < window.bonkHost.players.length; i++) {
						window.bonkHost.gameInfo[2].bal[i] = amount;
						window.bonkHost.toolFunctions.networkEngine.sendBalance(e, amount);
					};
					if(amount != 0) {
						window.bonkHost.menuFunctions.showStatusMessage("* Buff/nerf changed for all players", "#b53030", false);
					}
					else {
						window.bonkHost.menuFunctions.showStatusMessage("* Buff/nerf reset for all players", "#b53030", false);
					}
					if (window.bonkHost.menuFunctions) {
						window.bonkHost.menuFunctions.updateGameSettings();
						window.bonkHost.menuFunctions.updatePlayers();
					}
				}
				else if(command == "scoreboard") {
					if(window.bonkHost.toolFunctions.getGameSettings().ga === "b") {
						const players = document.getElementById("ingamewinner_scores_left").textContent.slice(0, -1).split(":\r\n");
						const scores = document.getElementById("ingamewinner_scores_right").textContent.split("\r\n");
						const longestScore = scores.reduce((a, b) => {return a.length > b.length ? a : b}).length;
						window.bonkHost.menuFunctions.showStatusMessage("* Scoreboard from the last game:", "#b53030", true);

						for(let i = 0; i < players.length; i++) {
							// \u2007 is a figure space (witdh of one digit)
							window.bonkHost.menuFunctions.showStatusMessage(`${scores[i].padStart((longestScore), "\u2007")}\t${players[i]}`, "#b53030", false);
						}
					}
					else if(window.bonkHost.toolFunctions.getGameSettings().ga === "f" && window[BIGVAR].bonkHost.footballState) {
						const scores = window[BIGVAR].bonkHost.footballState.scores.map(s => s.toString());
						const longestScore = (scores[2].length > scores[3].length ? scores[2] : scores[3]).length;
						window.bonkHost.menuFunctions.showStatusMessage(`${scores[3].padStart((longestScore), "\u2007")}\tBLUE TEAM`, "#b53030", false);
						window.bonkHost.menuFunctions.showStatusMessage(`${scores[2].padStart((longestScore), "\u2007")}\tRED TEAM`, "#b53030", false);
					}
				}
				else if(command == "ak") {
    if(args.length == 0) {
        window.bonkHost.autoKickGuests = !window.bonkHost.autoKickGuests;
    }
    else if(["true","on","yes","enable"].includes(args[0].toLowerCase())) {
        window.bonkHost.autoKickGuests = true;
    }
    else if(["false","off","no","disable"].includes(args[0].toLowerCase())) {
        window.bonkHost.autoKickGuests = false;
    }

    window.bonkHost.updateAkButton();
    window.bonkHost.menuFunctions.showStatusMessage(
        "* Guest Autokick " + (window.bonkHost.autoKickGuests ? "ON" : "OFF"),
        "#b53030",
        false
    );
}
                else if(command == "resetpos") {
					document.getElementById("hostPlayerMenu").style.removeProperty("top");
					document.getElementById("hostPlayerMenu").style.removeProperty("bottom");
					document.getElementById("hostPlayerMenu").style.removeProperty("left");
					document.getElementById("hostPlayerMenu").style.removeProperty("right");
					let ls = JSON.parse(localStorage.getItem("bonkHost"));
					ls.position = undefined;
					localStorage.setItem("bonkHost", JSON.stringify(ls));
				}
				else {
					e.target.value = oldMsg;
				}
			}
		}
	}
}

document.getElementById("newbonklobby_chat_input").addEventListener("keydown", chatHandler, true);
document.getElementById("ingamechatinputtext").addEventListener("keydown", chatHandler, true);

const chatObserver = new MutationObserver(e => {
	for(let mutation of e) {
		if(mutation.type == "childList") {
			for(let node of mutation.addedNodes) {
				if(node.textContent === "* Accepted commands are listed above ") {
					window.bonkHost.menuFunctions.showStatusMessage(`/hhelp - commands from host mod`, "#b53030", false);
				}
				else if(node.textContent[0] === "*" && node.textContent.endsWith((" has left the game "))) {
					let userName = node.textContent.slice(2).slice(0, -19);
					if(window.bonkHost.extraFeatures && window.bonkHost.extraFeatures.rejoinProtection) {
						window.bonkHost.extraFeatures.reservedNames[userName] = Date.now() + 60000;
						if(window.bonkHost.menuFunctions) window.bonkHost.menuFunctions.showStatusMessage("* Reserved slot for " + userName + " for 60s", "#b53030", false);
					}
					if(window.bonkHost.playerHistory[userName].guest) return;
					let banButton = document.createElement("span");
					banButton.className = "newbonklobby_mapsuggest_high newbonklobby_chat_link";
					banButton.textContent = "[Click to ban]";
					banButton.style = "color: #b53030 !important;";
					banButton.onclick = () => {
						if(banButton.textContent === "[Click to ban]") {
							window.bonkHost.ban(userName);
							banButton.textContent = "[Click to unban]";
						}
						else {
							window.bonkHost.bans.splice(window.bonkHost.bans.indexOf(userName), 1);
							window.bonkHost.menuFunctions.showStatusMessage(`* Removing ${userName} from ban list`, "#b53030", false);
							banButton.textContent = "[Click to ban]";
						}
					}
					node.appendChild(banButton);
				}
			}
		}
	}
});

chatObserver.observe(document.getElementById("newbonklobby_chat_content"), {attributes: false, childList: true, subtree: false});

const mapSuggestionModeRegex = newStr.match(/([A-Za-z0-9\$_]{3}\[[0-9]{1,4}\]\[[A-Za-z0-9\$_]{3}(\[[0-9]{1,4}\]){2}\]\([A-Za-z0-9\$_]{3}\[[0-9]{1,4}\]\);){7}if\([A-Za-z0-9\$_]{3}\[[0-9]{1,4}\]\[[A-Za-z0-9\$_]{3}(\[[0-9]{1,4}\]){2}\] > 250/)[0];

let SUGGESTION_MODE_BUTTON = `
let args = arguments;
if(!!window.bonkHost.bonkModesObject[args[0].m.mo]) {
	let space = document.createElement("span");
	space.classList.add("newbonklobby_mapsuggest_high");
	space.appendChild(document.createTextNode(" "));


	let smb = document.createElement("span");
	smb.classList.add("newbonklobby_mapsuggest_high");
	smb.classList.add("newbonklobby_chat_link");
	smb.style.color="#ff0000";
	smb.onclick = e => {
		smb.parentNode.getElementsByClassName("newbonklobby_mapsuggest_high newbonklobby_chat_link")[0].click();
		window.bonkHost.bonkSetMode(args[0].m.mo);
	};
	${mapSuggestionModeRegex.split("]")[0] + "]"}.appendChild(space);
	smb.appendChild(document.createTextNode("[" + window.bonkHost.bonkModesObject[args[0].m.mo].lobbyName + "]"));
	${mapSuggestionModeRegex.split("]")[0] + "]"}.appendChild(smb);
}
`;

let APPEND_SUGGESTION_MODE_BUTTON = `
	A7b[31].appendChild(space);
	smb.appendChild(document.createTextNode("[" + r6t[47].modes[A7b[4][I$2[12].suggestID].m.mo].lobbyName + "]"));
	A7b[31].appendChild(smb);
}
`;

let modeStuff = newStr.match(/[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]=class [A-Za-z0-9\$_]{3}\{constructor.{1,400}this\[[A-Za-z0-9\$_]{3}(\[[0-9]{1,3}\]){2}\]=2;/)[0].split("=")[0];

let modesObject =`${modeStuff}.modes`;

window.bonkHost.drawLagginess = (playerId) => {
	if(!window.bonkHost.playerSus[playerId] || !window.bonkHost.playerSus[playerId].lagginessHistory || !window.bonkHost.players[playerId] || window.bonkHost.toolFunctions.networkEngine.getLSID() === playerId) return false;
	let graph = document.getElementById("hostPlayerMenuCheatBox").children[window.bonkHost.players.filter(p=>p).indexOf(window.bonkHost.players[playerId])];
	if(graph.width !== graph.clientWidth && document.getElementById('hostPlayerMenu').style.visibility != "hidden") {
		graph.width = graph.clientWidth;
	}
	while (window.bonkHost.playerSus[playerId].lagginessHistory.length > Math.max(graph.clientWidth, 300)) {
		window.bonkHost.playerSus[playerId].lagginessHistory.shift();
	}
	let ctx = graph.getContext("2d");
	ctx.scale(1, -1);
	ctx.beginPath();
	ctx.clearRect(0, 0, graph.width, graph.height);
	ctx.strokeStyle = "#0f0";
	let lagginessHistory = window.bonkHost.playerSus[playerId].lagginessHistory.slice(-graph.clientWidth);
	let max = Math.max(0, Math.max.apply(Math, lagginessHistory));
	let min = Math.min(-60, Math.min.apply(Math, lagginessHistory)) - max;
	ctx.moveTo(0, (lagginessHistory[0] - max) / min * graph.height);
	for(let i = 1; i < lagginessHistory.length; i++) {
		ctx.lineTo(i, (lagginessHistory[i] - max) / min * graph.height);
	}
	ctx.stroke();
	for(let i = 1; i < 11; i++) {
		if (min - Math.abs(max) < -5 - i * graph.height) {
			ctx.beginPath();
			ctx.strokeStyle = "#f00";
			ctx.moveTo(0, (-5 - i*graph.height - max) / min * graph.height);
			ctx.lineTo(300, (-5 - i*graph.height - max) / min * graph.height);
			ctx.stroke();
		}
		else {
			break;
		}
	}
	if(window.bonkHost.playerSus[playerId].cheatWarning) {
		ctx.font = "16px monospace";
		ctx.fillText("⚠️", 0, 16);
	}
	if(window.bonkHost.playerSus[playerId].lagginessHistory === undefined || !window.bonkHost.players[playerId] || window.bonkHost.toolFunctions.networkEngine.getLSID() === playerId) {
		ctx.clearRect(0, 0, graph.width, graph.height);
	}
	return true;
}

window.bonkHost.modeDropdownCreated = false;
window.bonkHost.createModeDropdown = () => {
	if (window.bonkHost.modeDropdownCreated) return;
	window.bonkHost.modeDropdownCreated = true;
	const dropdown = document.createElement("div");
	dropdown.classList = "newbonklobby_settings_button brownButton brownButton_classic buttonShadow";
	const mds = dropdown.style;
	mds.color = "#ffffff";
	mds.position = "absolute";
	mds.right = "15px";
	mds.bottom = "55px";
	mds.display = "flex";
	mds.textAlign = "center";
	mds.flexDirection = "column-reverse";

	document.getElementById("newbonklobby_modebutton").remove();
	let title = document.createElement("div");
	title.classList = "dropdown_classic";
	title.innerText = "Mode";
	title.id = "newbonklobby_modebutton";
	title.style.position = "unset";
	dropdown.appendChild(title);

	const options = [];
	let dropdownOpen = false;

	function toggleVisibility(e) {
		dropdownOpen = !dropdownOpen;
		for (const o of options) {
			o.style.visibility = dropdownOpen ? "" : "hidden";
		}
		e.stopImmediatePropagation();
	}

	for (const mode of Object.keys(window.bonkHost.bonkModesObject)) {
		const option = document.createElement("div");
		option.classList = "dropdown-option dropdown_classic";
		option.style.display = "block";
		option.style.visibility = "hidden";
		option.style.fontSize = "15px";
		option.innerText = window.bonkHost.bonkModesObject[mode].lobbyName;
		option.onclick = (e) => {
			window.bonkHost.bonkSetMode(mode);
			toggleVisibility(e);
		};
		options.push(option);
		dropdown.appendChild(option);
	}

	title.addEventListener("click", toggleVisibility);

	document.getElementById("newbonklobby_settingsbox").appendChild(dropdown);
};

let stateCreationString = newStr.match(/[A-Za-z]\[...(\[[0-9]{1,4}\]){2}\]\(\[\{/)[0];
let stateCreationStringIndex = stateCreationString.match(/[0-9]{1,4}/g);
stateCreationStringIndex = stateCreationStringIndex[stateCreationStringIndex.length - 1];
let stateCreation = newStr.match(`[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]=[A-Za-z0-9\$_]{3}\\[[0-9]{1,4}\\]\\[[A-Za-z0-9\$_]{3}\\[[0-9]{1,4}\\]\\[${stateCreationStringIndex}\\]\\].+?(?=;);`)[0];
stateCreationString = stateCreation.split(']')[0] + "]";

const SET_STATE = `
if (
	${BIGVAR}.bonkHost.state &&
	window.bonkHost.keepState &&
	window.bonkHost.toolFunctions.getGameSettings().map.s.re &&
	window.bonkHost.toolFunctions.getGameSettings().ga === "b" &&
	${BIGVAR}.bonkHost.state.mm.dbid == window.bonkHost.toolFunctions.getGameSettings().map.m.dbid &&
	${BIGVAR}.bonkHost.state.mm.dbv == window.bonkHost.toolFunctions.getGameSettings().map.m.dbv
	) {
	for(let i = 0; i < ${BIGVAR}.bonkHost.state.discs.length; i++) {
		if(${BIGVAR}.bonkHost.state.discs[i] != undefined) {
			if(${BIGVAR}.bonkHost.state.discs[i].team !== window.bonkHost.players[i].team) {
				let discInfo = JSON.parse(JSON.stringify(${stateCreationString}.discs[i]));
				${stateCreationString}.discs[i] = ${BIGVAR}.bonkHost.state.discs[i];
				${stateCreationString}.discs[i].sx = discInfo.sx;
				${stateCreationString}.discs[i].sy = discInfo.sy;
				${stateCreationString}.discs[i].sxv = discInfo.svx;
				${stateCreationString}.discs[i].syv = discInfo.syv;
				${stateCreationString}.discs[i].spawnTeamInfo = discInfo.spawnTeamInfo;
				${stateCreationString}.discs[i].team = discInfo.team;
			}
			else {
				${stateCreationString}.discs[i] = ${BIGVAR}.bonkHost.state.discs[i];
			}
			if(window.bonkHost.toolFunctions.getGameSettings().mo=='sp') {
				${stateCreationString}.discs[i].a1a -= Math.min(2*30, 2*30 - ${BIGVAR}.bonkHost.state.ftu)*3;
			}
		}
	}
	for(let i = 0; i < ${BIGVAR}.bonkHost.state.discDeaths.length; i++) {
		if(${BIGVAR}.bonkHost.state.discDeaths[i] != undefined) {
			${stateCreationString}.discDeaths[i] = ${BIGVAR}.bonkHost.state.discDeaths[i];
		}
	}
	${stateCreationString}.physics=${BIGVAR}.bonkHost.state.physics;
	${stateCreationString}.seed=${BIGVAR}.bonkHost.state.seed;
	${stateCreationString}.rc=${BIGVAR}.bonkHost.state.rc;
	${stateCreationString}.rl=${BIGVAR}.bonkHost.state.rl - Math.min(60, 60 - ${BIGVAR}.bonkHost.state.ftu);
	${stateCreationString}.ftu=60;
	${stateCreationString}.shk=${BIGVAR}.bonkHost.state.shk;
	${stateCreationString}.projectiles=${BIGVAR}.bonkHost.state.projectiles;
	${stateCreationString}.capZones=${BIGVAR}.bonkHost.state.capZones;
	window.bonkHost.keepState=false;
};
if(${stateCreationString}.scores.length > 0 && document.getElementById('hostPlayerMenuKeepScores').checked) {
	if(window.bonkHost.toolFunctions.getGameSettings().ga === "b" && ${BIGVAR}.bonkHost.state !== undefined) {
		${stateCreationString}.scores = ${BIGVAR}.bonkHost.state.scores;
	}
	else if(window.bonkHost.toolFunctions.getGameSettings().ga === "f" && ${BIGVAR}.bonkHost.footballState !== undefined) {
		${stateCreationString}.scores = ${BIGVAR}.bonkHost.footballState.scores;
	}
}
`;

// Step function beginning
// First 2 score indexes are null if teams are on
const STEP_BEGIN = `
{
	let state = arguments[0];
	let gs = arguments[4];
	let updatePlayers = false;
	if(${BIGVAR}.bonkHost.state !== undefined) {
		if(gs.tea) {
			let teams = state.players.filter(p=>p).map(p=>p.team);
			for(let i = 2; i < 7; i++) {
				if(state.scores[i] && state.scores[i] === 0 && !teams.includes(i)) {
					state.scores[i] = null;
				}
			}
			state.scores = [null, null, ...state.scores.slice(2, 6)];
		}
		if(document.getElementById("gamerenderer").style.visibility === "inherit" && ${BIGVAR}.bonkHost.state.players.map(p => p?.team).join() != state.players.map(p => p?.team).join()) {
			updatePlayers = true;
		}
	}
	else {
		updatePlayers = true;
	}
	${BIGVAR}.bonkHost.state = state;
	if(updatePlayers && window.bonkHost.inGame && !window.bonkHost.toolFunctions.getGameSettings().q) {
		window.bonkHost.menuFunctions.updatePlayers();
	}
}
`;

const FOOTBALL_STEP_BEGIN = `
{
	let state = arguments[0];
	let updatePlayers = false;
	if(${BIGVAR}.bonkHost.footballState !== undefined) {
		if(document.getElementById("gamerenderer").style.visibility === "inherit" && ${BIGVAR}.bonkHost.footballState.players.map(p => p?.team).join() != state.players.map(p => p?.team).join()) {
			updatePlayers = true;
		}
	}
	else {
		updatePlayers = true;
	}
	${BIGVAR}.bonkHost.footballState = state;
	if(updatePlayers) {
		window.bonkHost.menuFunctions.updatePlayers();
	}
}
`;

document.getElementById('hostPlayerMenuFreejoin').addEventListener('click', (e) => {
	if(e.currentTarget.classList.contains("brownButtonDisabled")) return;
	window.bonkHost.freejoin = !window.bonkHost.freejoin;
	document.getElementById('hostPlayerMenuFreejoin').checked = window.bonkHost.freejoin;
	window.bonkHost.updateHostToolButtons();
});

document.getElementById('hostPlayerMenuTeamlock').addEventListener('click', (e) => {
	if(e.currentTarget.classList.contains("brownButtonDisabled")) return;
	document.getElementById('newbonklobby_teamlockbutton').onclick();
	let btn = document.getElementById('hostPlayerMenuTeamlock');
	btn.checked = !btn.checked;
	window.bonkHost.updateHostToolButtons();
});

document.getElementById('hostPlayerMenuKeepScores').addEventListener('click', (e) => {
	if(e.currentTarget.classList.contains("brownButtonDisabled")) return;
	e.currentTarget.checked = !e.currentTarget.checked;
	window.bonkHost.updateHostToolButtons();
});

document.getElementById('hostPlayerMenuKeepPositions').addEventListener('click', (e) => {
	if(e.currentTarget.classList.contains("brownButtonDisabled") || e.currentTarget.style.pointerEvents === "none") return;
	e.currentTarget.checked = !e.currentTarget.checked;
	window.bonkHost.updateHostToolButtons();
});

document.getElementById('hostPlayerMenuCheatDetectionCheckbox').addEventListener('click', (e) => {
	if(e.currentTarget.classList.contains("brownButtonDisabled")) return;
	window.bonkHost.cheatDetection = !window.bonkHost.cheatDetection;
	e.currentTarget.checked = window.bonkHost.cheatDetection;
	if(window.bonkHost.cheatDetection) {
		document.getElementById('hostPlayerCheatDetection').style.left = "95%";
		document.getElementById('hostPlayerCheatDetection').style.visibility = "visible";
	}
	else {
		document.getElementById('hostPlayerCheatDetection').style.left = "0";
		document.getElementById('hostPlayerCheatDetection').style.visibility = "hidden";
	}
	window.bonkHost.updateHostToolButtons();
	window.bonkHost.menuFunctions.updatePlayers();
});

window.bonkHost.updateHostToolButtons();

window.bonkHost.sendAutoKickChat = (msg) => {
    let input = document.getElementById("newbonklobby_chat_input") || document.getElementById("ingamechatinputtext");
    if(!input) return;

    input.value = msg;
    input.dispatchEvent(new KeyboardEvent("keydown", {
        key: "Enter",
        code: "Enter",
        keyCode: 13,
        which: 13,
        bubbles: true
    }));
};

window.bonkHost.handlePlayerJoined = (playerID, playerName, guest) => {
    if(window.bonkHost.updateRecentJoin) window.bonkHost.updateRecentJoin(playerName);
    if(window.bonkHost.showPlayerNoteIfAny) window.bonkHost.showPlayerNoteIfAny(playerName);

    if(window.bonkHost.extraFeatures && window.bonkHost.extraFeatures.rejoinProtection && window.bonkHost.extraFeatures.reservedNames[playerName]) {
        delete window.bonkHost.extraFeatures.reservedNames[playerName];
    }
    else if(window.bonkHost.handleRejoinProtectionJoin) {
        setTimeout(() => window.bonkHost.handleRejoinProtectionJoin(playerName), 250);
    }

    if(!isHost()) return;

    if(!guest && window.bonkHost.bans.includes(playerName)) {
        window.bonkHost.toolFunctions.networkEngine.banPlayer(playerID);
        return;
    }

    if(window.bonkHost.autoKickGuests && guest) {
        window.bonkHost.sendAutoKickChat("Join on your main bitchass");

        let countdown = 5;

        let interval = setInterval(() => {
            if(!window.bonkHost.players[playerID]) {
                clearInterval(interval);
                return;
            }

            window.bonkHost.sendAutoKickChat(`Kicking guest in ${countdown}...`);
            countdown--;

            if(countdown < 0) {
                clearInterval(interval);
                if(window.bonkHost.players[playerID]) {
                window.bonkHost.sendAutoKickChat(`/kick "${playerName}"`);
            }
            }
        }, 1000);
    }

    if(window.bonkHost.freejoin) {
        let team = 1;

        if(window.bonkHost.toolFunctions.getGameSettings().tea) {
            let teams = window.bonkHost.players
                .slice(0, -1)
                .filter(p => p && p.team > 1)
                .map(p => p.team);

            if(teams.every(t => t == teams[0])) {
                team = teams[0];
            } else {
                return;
            }
        }

        window.bonkHost.stateFunctions.hostHandlePlayerJoined(
            playerID,
            window.bonkHost.players.length,
            team
        );
    }

    if(window.bonkHost.assignNewJoinToBalancedTeam) {
        setTimeout(() => window.bonkHost.assignNewJoinToBalancedTeam(playerID), 250);
    }
}
window.bonkHost.ban = (playerName) => {
	if(window.bonkHost.bans.includes(playerName)) {
		window.bonkHost.menuFunctions.showStatusMessage("* " + playerName + " is already banned", "#b53030", false);
		return;
	}
	let id = window.bonkHost.players.findIndex(e => {return e && e.userName === playerName});
	if(id !== -1 && isHost()) {
		if(window.bonkHost.players[id].guest) {
			window.bonkHost.menuFunctions.showStatusMessage("* Banning guests doesn't work, so they'll get kicked instead", "#b53030", false);
		}
		else {
			window.bonkHost.bans.push(playerName);
		}
		window.bonkHost.toolFunctions.networkEngine.banPlayer(id);
	}
	else if(id !== -1) {
		window.bonkHost.menuFunctions.showStatusMessage(`* You're not room host, but ${playerName} will be added to ban list`, "#b53030", false);
		window.bonkHost.bans.push(playerName);
	}
	else {
		window.bonkHost.menuFunctions.showStatusMessage(`* Username ${playerName} not found in this room, but they'll be added to ban list`, "#b53030", false);
		window.bonkHost.bans.push(playerName);
	}
}


window.bonkHost.playerManagement.addPlayer = (playerEntry) => {
	for(let player of window.bonkHost.players.filter(p=>p)) {
		window.bonkHost.playerHistory[player.userName] = player;
	}
	while(window.bonkHost.playerManagement.getPlayer(playerEntry)) {
		window.bonkHost.playerManagement.removePlayer(playerEntry);
	}
	let newPlayerEntry = playerEntry.cloneNode(true);
	newPlayerEntry.classList.remove('newbonklobby_playerentry_half');
	newPlayerEntry.getElementsByClassName("newbonklobby_playerentry_ping")[0].remove();
	newPlayerEntry.getElementsByClassName("newbonklobby_playerentry_host")[0].remove();
	if(isHost()) {
		playerEntry.addEventListener('click', (e) => {
			let menu = document.getElementsByClassName("newbonklobby_playerentry_menu")[0];
			banButton = document.createElement("div");
			banButton.classList = "newbonklobby_playerentry_menu_button brownButton brownButton_classic buttonShadow";
			banButton.innerHTML = "BAN";
			banButton.onclick = () => {
				if(banButton.innerHTML == "BAN") {
					banButton.innerHTML = "SURE?";
					return;
				}
				window.bonkHost.ban(playerEntry.children[1].textContent);
				menu.style.display = "none";
			};
			for(let child of menu.childNodes) {
				if(child.textContent == "KICK") {
					menu.insertBefore(banButton, child.nextSibling);
					break;
				}
			}
		});
	}
	newPlayerEntry.onclick = e => {
		playerEntry.click();
		let menu = document.getElementsByClassName("newbonklobby_playerentry_menu")[0];
		document.getElementById("hostPlayerMenuBox").parentNode.appendChild(menu);
		newPlayerEntry.playerElement=playerEntry;
		menu.style.removeProperty("left");
		menu.style.right=0;
		menu.style.top=([...playerEntry.parentNode.children].indexOf(playerEntry))*43+"px";
	}
	newPlayerEntry.onmouseenter = playerEntry.onmouseenter;

	// Make spectators transparent
	let playerId = window.bonkHost.players.findIndex(p => p && (p.userName === newPlayerEntry.childNodes[1].textContent));
	if(window.bonkHost.applyFlagToPlayerEntry && playerId !== -1) {
		window.bonkHost.applyFlagToPlayerEntry(newPlayerEntry, window.bonkHost.players[playerId]);
		window.bonkHost.applyFlagToPlayerEntry(playerEntry, window.bonkHost.players[playerId]);
	}
	if(
		!(window.bonkHost.toolFunctions.getGameSettings().ga === "b" && window[BIGVAR].bonkHost.state?.players[playerId]) &&
		!(window.bonkHost.toolFunctions.getGameSettings().ga === "f" && window[BIGVAR].bonkHost.footballState?.players[playerId])
	) {
		newPlayerEntry.style.filter = "opacity(0.4)";
	}

	// Listen for skin render
	let observer = new MutationObserver((mutations) => {
		mutations.forEach((mutation) => {
			// New child
			mutation.addedNodes.forEach((node) => {
				newPlayerEntry.firstChild.appendChild(node.cloneNode(true));
				observer.disconnect();
			});
		});
	});
	observer.observe(playerEntry.children[0], { childList: true });
	hostPlayerMenuBox.appendChild(newPlayerEntry);
	for(let i = 0; i < [...document.getElementById("hostPlayerMenuCheatBox").children].length; i++) {
		let graph = document.getElementById("hostPlayerMenuCheatBox").children[i];
		let playerId = window.bonkHost.players.indexOf(window.bonkHost.players.filter(p=>p)[i]);
		if(!window.bonkHost.drawLagginess(playerId)) {
			let ctx = graph.getContext("2d");
			ctx.scale(1, -1);
			ctx.clearRect(0, 0, graph.width, graph.height);
		}
	}
}

window.bonkHost.playerManagement.removePlayer = (playerEntry) => {
	let foundPlayerEntry = window.bonkHost.playerManagement.getPlayer(playerEntry);
	if(foundPlayerEntry) {
		hostPlayerMenuBox.removeChild(foundPlayerEntry);
	}
}

window.bonkHost.playerManagement.show = () => {
	let gameVisible = document.getElementById("gamerenderer") && document.getElementById("gamerenderer").style.visibility == "inherit";
	let lobbyVisible = document.getElementById("newbonklobby") && document.getElementById("newbonklobby").style.visibility != "hidden";
	if(!window.bonkHost.playerManagement.canBeVisible || (!gameVisible && !lobbyVisible)) return;
	if(parent.document.getElementById('adboxverticalleftCurse') != null)
		parent.document.getElementById('adboxverticalleftCurse').style.display = "none";
	let currentSettings = window.bonkHost.toolFunctions.getGameSettings();
	let respawningEnabled = currentSettings && currentSettings.map && currentSettings.map.s && currentSettings.map.s.re;
	document.getElementById("hostPlayerMenuKeepPositions").style.filter = (respawningEnabled ? "" : "brightness(0.5)");
	document.getElementById("hostPlayerMenuKeepPositions").style.pointerEvents = (respawningEnabled ? "" : "none");
	document.getElementById("hostPlayerMenuRespawningRequiredWarning").style.display = (respawningEnabled ? "none" : "none");
	window.bonkHost.updateHostToolButtons();
	document.getElementById('hostPlayerMenu').style.display = "unset";
	window.bonkHost.menuFunctions.updatePlayers();
	window.bonkHost.inGame = true;
	if(window.bonkHost.updateMainMenuActionVisibility) window.bonkHost.updateMainMenuActionVisibility();
	for(let graph of [...document.getElementById("hostPlayerMenuCheatBox").children]) {
		let ctx = graph.getContext("2d");
		ctx.scale(1, -1);
		ctx.clearRect(0, 0, graph.width, graph.height);
	}
}

window.bonkHost.playerManagement.hide = () => {
	window.bonkHost.inGame = false;
	if(window.bonkHost.updateMainMenuActionVisibility) window.bonkHost.updateMainMenuActionVisibility();
	document.getElementById('hostPlayerMenu').style.display = "none";
	if(parent.document.getElementById('adboxverticalleftCurse') != null)
		parent.document.getElementById('adboxverticalleftCurse').style.removeProperty("display");
}

// Can be called in other situations too
window.bonkHost.handleHostChange = (host) => {
	document.getElementById("hostPlayerMenuRestartButton").classList.toggle("brownButtonDisabled", !host);
	document.getElementById("hostPlayerMenuTeamToolsToggle").classList.toggle("brownButtonDisabled", !host);
	document.getElementById("hostPlayerMenuHostToolsToggle").classList.toggle("brownButtonDisabled", !host);
	document.getElementById("hostPlayerMenuShuffleRBButton").classList.toggle("brownButtonDisabled", !host);
	document.getElementById("hostPlayerMenuAutoBalanceButton").classList.toggle("brownButtonDisabled", !host);
	document.getElementById("hostPlayerMenuSwapTeamsButton").classList.toggle("brownButtonDisabled", !host);
	document.getElementById("hostPlayerMenuAllSpecButton").classList.toggle("brownButtonDisabled", !host);
	document.getElementById("hostPlayerMenuAkToggleButton").classList.toggle("brownButtonDisabled", !host);
	document.getElementById("hostPlayerMenuTeamlock").classList.toggle("brownButtonDisabled", !host);
	document.getElementById("hostPlayerMenuFreejoin").classList.toggle("brownButtonDisabled", !host);
	document.getElementById("hostPlayerMenuKeepScores").classList.toggle("brownButtonDisabled", !host);
	document.getElementById("hostPlayerMenuCheatDetectionCheckbox").classList.toggle("brownButtonDisabled", !host);
	document.getElementById("hostPlayerMenuKeepPositions").classList.toggle("brownButtonDisabled", !host);
	document.getElementById("hostPlayerMenuQuickplayButton").classList.toggle("brownButtonDisabled", !host);
	document.getElementById("hostPlayerMenuQpRoundsButton").classList.toggle("brownButtonDisabled", !host);
	document.getElementById("hostPlayerMenuQpShuffleButton").classList.toggle("brownButtonDisabled", !host);
	document.getElementById("hostPlayerMenuQpPrevButton").classList.toggle("brownButtonDisabled", !host);
	document.getElementById("hostPlayerMenuQpNextButton").classList.toggle("brownButtonDisabled", !host);
	window.bonkHost.updateHostToolButtons();
	if(window.bonkHost.updateQuickplayButtons) window.bonkHost.updateQuickplayButtons();
	updateMapHistoryButtons();
	if(!host) {
		mapHistory = [];
		mapHistoryIndex = 0;
	}
}

window.bonkHost.playerManagement.collapse = (saveToLocalStorage = true) => {
	if(document.getElementById('hostPlayerMenu').style.visibility != "hidden") {
		document.getElementById('hostPlayerMenuControls').style.display = "none";
		document.getElementById('hostPlayerMenuControls').visibility = "hidden";
		document.getElementById('hostPlayerMenu').style.minWidth = 0;
		document.getElementById('hostPlayerMenu').style.minHeight = 0;
		document.getElementById('hostPlayerMenu').style.width = 0;
		document.getElementById('hostPlayerMenu').style.height = 0;
		document.getElementById('hostPlayerMenu').style.visibility = "hidden";
		document.getElementById('hostPlayerMenuCollapse').textContent = "+";
	}
	else {
		document.getElementById('hostPlayerMenu').style.visibility = "visible";
		document.getElementById('hostPlayerMenu').style.removeProperty("min-width");
		document.getElementById('hostPlayerMenu').style.removeProperty("min-height");
		document.getElementById('hostPlayerMenu').style.removeProperty("width");
		document.getElementById('hostPlayerMenu').style.removeProperty("height");
		document.getElementById('hostPlayerMenu').style.visibility = "visible";
		document.getElementById('hostPlayerMenuCollapse').textContent = "-";
		setTimeout(() => {document.getElementById('hostPlayerMenuControls').style.removeProperty("display");}, 100);
	}
	if(saveToLocalStorage) {
		let ls = JSON.parse(localStorage.getItem("bonkHost"));
		ls.collapse = (document.getElementById('hostPlayerMenu').style.visibility == "hidden");
		localStorage.setItem("bonkHost", JSON.stringify(ls));
	}
}

if(JSON.parse(localStorage.getItem("bonkHost")).collapse) {
	window.bonkHost.playerManagement.collapse(false);
}

{
	let position = JSON.parse(localStorage.getItem("bonkHost")).position;
	if(position !== undefined) {
		document.getElementById("hostPlayerMenu").style.left = position.left;
		document.getElementById("hostPlayerMenu").style.right = position.right;
		document.getElementById("hostPlayerMenu").style.top = position.top;
		document.getElementById("hostPlayerMenu").style.bottom = position.bottom;
	}
}

// Host menu mover

let startGrabPoint = {x:0,y:0};
let grabbing = false;
const hostMenuMover = e => {
	if(e.x - startGrabPoint.x < document.body.clientWidth / 2) {
		document.getElementById("hostPlayerMenu").style.left = e.x + startGrabPoint.x - document.getElementById("hostPlayerMenu").clientWidth;
		document.getElementById("hostPlayerMenu").style.right = "unset";
	}
	else {
		document.getElementById("hostPlayerMenu").style.right = document.body.clientWidth - e.x - startGrabPoint.x;
		document.getElementById("hostPlayerMenu").style.left = "unset";
	}

	if(e.y - startGrabPoint.y < document.body.clientHeight / 2) {
		document.getElementById("hostPlayerMenu").style.top = e.y - startGrabPoint.y;
		document.getElementById("hostPlayerMenu").style.bottom = "unset";
	}
	else {
		document.getElementById("hostPlayerMenu").style.bottom = document.body.clientHeight - e.y + startGrabPoint.y - document.getElementById("hostPlayerMenu").clientHeight;
		document.getElementById("hostPlayerMenu").style.top = "unset";
	}
}

window.bonkHost.playerManagement.startGrab = e => {
	grabbing = true;
	document.getElementById("hostPlayerMenuGrab").style.cursor = "grabbing"
	document.getElementById("hostPlayerMenu").style.transition = "unset";
	startGrabPoint.x = e.offsetX;
	startGrabPoint.y = e.offsetY;
	document.body.addEventListener("pointermove", hostMenuMover);
}

window.bonkHost.playerManagement.endGrab = e => {
	if(!grabbing) return;
	grabbing = false;
	document.getElementById("hostPlayerMenuGrab").style.cursor = "grab";
	document.getElementById("hostPlayerMenu").style.removeProperty("transition");
	document.body.removeEventListener("pointermove", hostMenuMover);
	let ls = JSON.parse(localStorage.getItem("bonkHost"));
	ls.position = {
		left: document.getElementById("hostPlayerMenu").style.left,
		right: document.getElementById("hostPlayerMenu").style.right,
		top: document.getElementById("hostPlayerMenu").style.top,
		bottom: document.getElementById("hostPlayerMenu").style.bottom
	};
	localStorage.setItem("bonkHost", JSON.stringify(ls));
}

document.getElementById("hostPlayerMenuGrab").addEventListener("pointerdown", window.bonkHost.playerManagement.startGrab);
document.body.addEventListener("pointerup", window.bonkHost.playerManagement.endGrab);

// Helper functions

window.bonkHost.playerManagement.getPlayer = (playerEntry, exact = false) => {
	if (exact) {
		let child = [...hostPlayerMenuBox.children].indexOf(playerEntry);
		if(child) return hostPlayerMenuBox.children[child];
	}
	for(let child of hostPlayerMenuBox.children) {
		if(child.children[1].textContent == playerEntry.children[1].textContent) {
			return child;
		}
	}
	return false;
}

window.bonkHost.playerManagement.movePlayer = (team, playerID = window.bonkHost.toolFunctions.networkEngine.getLSID()) => {
	if(!window.bonkHost.players[playerID] || !window.bonkHost.inGame || !isHost() || window.bonkHost.toolFunctions.getGameSettings().q) return;
	window.bonkHost.menuFunctions.visible = true;
	if(team > 0)
		window.bonkHost.stateFunctions.hostHandlePlayerJoined(playerID, window.bonkHost.players.length, team);
	else
		window.bonkHost.stateFunctions.hostHandlePlayerLeft(playerID);
	window.bonkHost.menuFunctions.updatePlayers();
}

window.bonkHost.startGame = () => {
	if(window.bonkHost.quickplay && window.bonkHost.quickplay.state === "on") {
		window.bonkHost.quickplay.startedGame = true;
	}
	window.bonkHost.keepState = document.getElementById("hostPlayerMenuKeepPositions").checked;
	for(let callback of Object.keys(window.bonkHost.bonkCallbacks)) {
    	window.bonkHost.bonkCallbacks[callback]("startGame");
	}
}

// Fisher–Yates shuffle
const shuffle = (array) => {
	let currentIndex = array.length, randomIndex;

	// While there remain elements to shuffle.
	while (currentIndex != 0) {
		// Pick a remaining element.
		randomIndex = Math.floor(Math.random() * currentIndex);
		currentIndex--;

		// And swap it with the current element.
		[array[currentIndex], array[randomIndex]] = [
		array[randomIndex], array[currentIndex]];
	}
	return array;
}

window.bonkHost.shufflePlayers = () => {
	document.getElementById("teamshufflebox").style.visibility = "hidden";

	//Players per team
	let ppt = document.getElementById("teamshuffleppt").value;
	let teams = 0;
	for(let team of ["red", "blue", "yellow", "green"]) {
		if(document.getElementById("team_shuffle_"+team).checked) {
			teams++;
		}
	}
	if(ppt === "AUTO") {
		ppt = Math.ceil(window.bonkHost.players.length / teams);
	}
	let shuffledPlayers = shuffle(window.bonkHost.players);
	for(let team of ["red", "blue", "yellow", "green"]) {
		if(document.getElementById("team_shuffle_"+team).checked) {
			for(let i = 0; i < ppt; i++) {
			window.bonkHost.toolFunctions.networkEngine.changeOtherTeam(window.bonkHost.players.indexOf(shuffledPlayers.pop()), ["red", "blue", "yellow", "green"].indexOf(team)+2);
			}
		}
	}
}

let mapSelectionObserver = new MutationObserver(mutations => {
	for(let mutation of mutations) {
		for(let e of mutation.addedNodes) {
			let mode = e.getElementsByClassName('maploadwindowtextmode')[0];
			if(mode === undefined) mode = e.getElementsByClassName('maploadwindowtextmode_picks')[0];
			if(mode === undefined) return;
			if(mode.textContent !== "Any Mode") {
				mode.classList.add('brownButton');
				mode.classList.add('brownButton_classic');
				mode.classList.add('buttonShadow');
				mode.style.padding = "2px";
				mode.style.width = "90px";
			}
			mode.addEventListener("click", e => {
				if(!document.getElementById('newbonklobby_modebutton').classList.contains("brownButtonDisabled")) {
					window.bonkHost.bonkSetMode(Object.entries(window.bonkHost.bonkModesObject).filter(e => {return e[1].lobbyName === mode.textContent})[0][0]);
				}
			});
		}
	}
});
mapSelectionObserver.observe(document.getElementById('maploadwindowmapscontainer'), {attributes: false, childList: true, subtree: false});

/*Autocomplete*/

const autocomplete = e => {
	if (e.keyCode === 9) {
		e.preventDefault();
		e.stopPropagation();
		let chatText = e.target.value.split(' ');
		let length = 0;
		for (let i = 0; i < chatText.length; i++) {
			length += chatText[i].length + 1;
			if (length <= e.target.selectionStart || chatText[i] === "")
				continue;
			let foundAutocompletes = [];
			let foundAutocompletesOffsets = [];
			for (let j = 0; j < window.bonkCommands.length; j++) {
				if (window.bonkCommands[j].toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, '\\$&').match("^" + chatText[i].toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))) {
					foundAutocompletes.push(window.bonkCommands[j]);
					foundAutocompletesOffsets.push(0);
				}
			}
			if (foundAutocompletes.length === 0) {
				if(chatText[0] !== "/unban") {
					for (let j = 0; j < window.bonkHost.players.filter(p=>p).length; j++) {
						for (let k = i; k >= 0; k--) {
							if (window.bonkHost.players.filter(p=>p).map(p=>p.userName)[j].toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, '\\$&').match("^" + chatText.slice(k, i + 1).join(" ").toLowerCase().replace(/"/g, "").replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))) {
								foundAutocompletes.push(window.bonkHost.players.filter(p=>p).map(p=>p.userName)[j]);
								foundAutocompletesOffsets.push(k);
							}
						}
					}
				}
				else {
					for (let j = 0; j < window.bonkHost.bans.length; j++) {
						for (let k = i; k >= 0; k--) {
							if (window.bonkHost.bans[j].toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, '\\$&').match("^" + chatText.slice(k, i + 1).join(" ").toLowerCase().replace(/"/g, "").replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))) {
								foundAutocompletes.push(window.bonkHost.bans[j]);
								foundAutocompletesOffsets.push(k);
							}
						}
					}
				}
			}
			if (foundAutocompletes.length === 1) {
				let oldlen = chatText.slice(foundAutocompletesOffsets[0], i + 1).join(" ").length;
				for (let j = i; j > foundAutocompletesOffsets[0]; j--) {
					chatText.splice(j, 1);
				}
				if (chatText[0][0] === "/" && i > 0 && foundAutocompletes[0].includes(" ")) {
					chatText[foundAutocompletesOffsets[0]] = `"${foundAutocompletes[0]}" `;
				} else {
					chatText[foundAutocompletesOffsets[0]] = foundAutocompletes[0] + (foundAutocompletesOffsets[0] === (chatText.length - 1) && (chatText[0][0] === "/") && (chatText[foundAutocompletesOffsets[0] + 1] !== "") ? " " : "");
				}
				if(chatText[foundAutocompletesOffsets[0] + 1] === "") {
					chatText.splice(foundAutocompletesOffsets[0] + 1, 1);
				}
				e.target.value = chatText.join(' ');
				e.target.selectionStart = length - oldlen + chatText[foundAutocompletesOffsets[0]].length + ((foundAutocompletesOffsets[0] === chatText.length - 1) && (chatText[0][0] !== "/") ? 0 : 1);
				e.target.selectionEnd = length - oldlen + chatText[foundAutocompletesOffsets[0]].length + ((foundAutocompletesOffsets[0] === (chatText.length - 1)) && (chatText[0][0] !== "/") ? 1 : 0);
				return;
			} else if (foundAutocompletes.length > 1) {
				let maxAutocomplete = "";
				let char = "";
				for (let j = 0; j >= 0; j++) {
					if(char === undefined) break;
					maxAutocomplete += char;
					char = "";
					for (let k = 0; k < foundAutocompletes.length; k++) {
						if (char === "") char = foundAutocompletes[k][j];
						else if (foundAutocompletes[k][j] !== char) {
							j = -Infinity;
							break;
						}
					}
				}
				if(maxAutocomplete === "") return;
				let oldlen = chatText[i].length;
				let quotes = (chatText[0][0] === "/" && foundAutocompletes.some(r => r.includes(" ")));
				if (quotes) {
					chatText[i] = `"${maxAutocomplete}"`;
				} else {
					chatText[i] = maxAutocomplete;
				}
				e.target.value = chatText.join(' ');
				e.target.selectionStart = length - oldlen + chatText[i].length - quotes * 2;
				e.target.selectionEnd = length - oldlen + chatText[i].length - quotes * 2;
				return;
			}
		}
	}
};
document.getElementById("newbonklobby_chat_input").addEventListener("keydown", autocomplete, true);
document.getElementById("ingamechatinputtext").addEventListener("keydown", autocomplete, true);

(() => {
	let selectedPlayer = null;
	let start = [], end = [];
	const wheelSize = 150;
	let oldAngle = null;
	const innerWheelRadius = (wheelSize/26.458)/2*8.5;
	window.bonkHost.updatePlayers = () => {
		[...document.getElementsByClassName("newbonklobby_playerentry")].filter(e => {
			return ["newbonklobby_playerbox_leftelementcontainer",
					"newbonklobby_playerbox_rightelementcontainer",
					"newbonklobby_playerbox_elementcontainer",
					"newbonklobby_specbox_elementcontainer",
					"hostPlayerMenuBox"].includes(e.parentNode.id);
		}).forEach(e => {
			e.addEventListener("mousedown", mouse => {
				if(isHost() || (e.children[1].textContent === window.bonkHost.players[window.bonkHost.toolFunctions.networkEngine.getLSID()].userName && !window.bonkHost.inGame)) {
					selectedPlayer = e;
					start = [mouse.clientY, mouse.clientX];
				}
			});
			e.addEventListener("click", mouse => {
				if(!isHost() && e.children[1].textContent === window.bonkHost.players[window.bonkHost.toolFunctions.networkEngine.getLSID()].userName && window.bonkHost.inGame) {
					mouse.stopImmediatePropagation();
				}
			}, true);
		});
	}
	document.addEventListener("mousemove", mouse => {
		if(selectedPlayer) {
			document.body.style.pointerEvents = "none";
			let wheelType = (!window.bonkHost.toolFunctions.getGameSettings().tea) ? "selectionWheel" : "selectionWheelTeams";
			document.getElementById(wheelType).style.display = "block";
			document.getElementById(wheelType).style.top = start[0]-document.getElementById(wheelType).getBoundingClientRect().height/2+"px";
			document.getElementById(wheelType).style.left = start[1]-document.getElementById(wheelType).getBoundingClientRect().width/2+"px";
			end = [mouse.clientY, mouse.clientX];
			if(Math.sqrt((end[0]-start[0])**2 + (end[1]-start[1])**2) >= innerWheelRadius) {
				if(!window.bonkHost.toolFunctions.getGameSettings().tea) {
					let angle = end[1] < start[1];
					if(angle) {
						document.getElementById("selectionWheel").children[0].children[0].children[0].style.opacity = 1;
						document.getElementById("selectionWheel").children[0].children[1].children[0].style.opacity = 0.5;
					}
					else {
						document.getElementById("selectionWheel").children[0].children[0].children[0].style.opacity = 0.5;
						document.getElementById("selectionWheel").children[0].children[1].children[0].style.opacity = 1;
					}
					selectedPlayer.onmouseenter(angle);
					oldAngle = angle;
				}
				else {
					let angle = Math.atan((end[0]-start[0])/(end[1]-start[1]))/Math.PI*180+360/5/2;
					if(end[1] < start[1]) {
						angle += 180;
					}
					else if(end[0] < start[0]) {
						angle += 360;
					}
					angle = Math.floor((angle%360)/(360/5));
					if(oldAngle !== angle) {
						for(let child of [...document.getElementById("selectionWheelTeams").children[0].children[0].children]) {
							child.style.opacity = 0.5;
						}
					}
					document.getElementById("selectionWheelTeams").children[0].children[0].children[angle].style.opacity = 1;
					selectedPlayer.onmouseenter(angle);
					oldAngle = angle;
				}
			}
			else {
				if(oldAngle !== null) {
					for(let child of [...document.getElementById("selectionWheelTeams").children[0].children[0].children]) {
						child.style.opacity = 0.5;
					}
					for(let child of [...document.getElementById("selectionWheel").children[0].children]) {
						child.children[0].style.opacity = 0.5;
					}
				}
				selectedPlayer.onmouseenter(null);
				oldAngle = null;
			}
		}
	});
	const changeTeam = mouse => {
		document.getElementById("selectionWheel").style.display = "none";
		document.getElementById("selectionWheelTeams").style.display = "none";
		if(!selectedPlayer) return;
		document.body.style.removeProperty("pointer-events");
		end = [mouse.clientY, mouse.clientX];
		const sendTeamChange = (i, team) => {
			if(i == window.bonkHost.toolFunctions.networkEngine.getLSID()) {
				window.bonkHost.toolFunctions.networkEngine.changeOwnTeam(team);
			}
			else {
				window.bonkHost.toolFunctions.networkEngine.changeOtherTeam(i, team);
			}
		}
		if(Math.sqrt((end[0]-start[0])**2 + (end[1]-start[1])**2) >= innerWheelRadius) {
			if(!window.bonkHost.toolFunctions.getGameSettings().tea) {
			for(let child of [...document.getElementById("selectionWheel").children[0].children]) {
				child.children[0].style.opacity = 0.5;
			}
			sendTeamChange(window.bonkHost.players.findIndex(i => {return i && i.userName === selectedPlayer.children[1].textContent}), end[1]<start[1] ? 1 : 0);
			}
			else {
				let angle = Math.atan((end[0]-start[0])/(end[1]-start[1]))/Math.PI*180+360/5/2;
				if(end[1] < start[1]) {
					angle += 180;
				}
				else if(end[0] < start[0]) {
					angle += 360;
				}
				angle = Math.floor((angle%360)/(360/5));
				sendTeamChange(window.bonkHost.players.findIndex(i => {return i && i.userName === selectedPlayer.children[1].textContent}), [0, 5, 4, 3, 2][angle]);
			}
		}
		else {
			selectedPlayer.click();
		}
		selectedPlayer = null;
	}
	document.addEventListener("mouseup", changeTeam);
	document.addEventListener("mouseenter", mouse => {
		let selectedPlayer = (mouse.buttons !== 0 ? selectedPlayer : null);
		if(!selectedPlayer) {
			changeTeam(mouse);
		}
	});
})();
let patchCounter = 0;
const patch = (a, b) => {
	let c = newStr;
	newStr = newStr.replace(a, b);
	/*console.log(`Patch ${patchCounter++}: ${c === newStr ? 'fail' : 'success'}`);
	if(c === newStr) {
		console.log(`Failed to patch ${a} with ${b}`);
	}*/
	return c !== newStr;
}

//Remove round limit
document.getElementById('newbonklobby_roundsinput').removeAttribute("maxlength");
patch(/[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]\[[0-9]{1,3}\]\[[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]\[[0-9]{1,3}\]\]=Math\[[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]\[[0-9]{1,3}\]\]\(Math\[[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]\[[0-9]{1,3}\]\]\(1,[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]\[[0-9]{1,3}\]\[[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]\[[0-9]{1,3}\]\]\),9\);/, '');
const roundVal = newStr.match(/[A-Za-z0-9\$_]{3}\[[0-9]{1,4}\]=parseInt\([A-Za-z0-9\$_]{3}(\[0\]){2}\[[A-Za-z0-9\$_]{3}(\[[0-9]{1,4}\]){2}\]\)\;/)[0];
const roundValVar = roundVal.split('=')[0];
patch(roundVal, `${roundValVar}=parseInt(document.getElementById('newbonklobby_roundsinput').value);if(isNaN(${roundValVar}) || ${roundValVar} <= 0){return;}`);

//Mode selection menu. Custom patch
let lastTarget = newStr.match(/editorCanTarget:(true|false)\};/g)
lastTarget = lastTarget[lastTarget.length - 1];
newStr = newStr.split(lastTarget);

newStr[newStr.length - 1] = `
window.bonkHost.bonkModesObject=${modesObject};
window.bonkHost.bonkSetMode = m => {
	if(m === "f") {
		window.bonkHost.gameInfo[2].ga = "f";
		window.bonkHost.gameInfo[2].tea=true;
		window.bonkHost.toolFunctions.networkEngine.sendTeamSettingsChange(window.bonkHost.gameInfo[2].tea);
	}
	else {
		window.bonkHost.gameInfo[2].ga = "b";
	}
	window.bonkHost.gameInfo[2].mo = m;
	window.bonkHost.menuFunctions.updatePlayers();
	window.bonkHost.toolFunctions.networkEngine.sendGAMO(window.bonkHost.gameInfo[2].ga, window.bonkHost.gameInfo[2].mo);
	window.bonkHost.menuFunctions.updateGameSettings();
}
window.bonkHost.createModeDropdown();
` + newStr[newStr.length - 1];

newStr = newStr.join(lastTarget);

//Add mode button to map suggestion message
patch(mapSuggestionModeRegex, mapSuggestionModeRegex.split("if")[0] + SUGGESTION_MODE_BUTTON + "if" + mapSuggestionModeRegex.split("if")[1]);

//Internal var
patch(`function ${BIGVAR}(){}`, `function ${BIGVAR}(){}${BIGVAR}.bonkHost={};`);

/////////////
//Host menu//
/////////////

//Save latest state
const stateRegex = newStr.match(/[A-Za-z]\[[A-Za-z0-9\$_]{3}(\[[0-9]{1,3}\]){2}\]={discs/)[0];
patch(stateRegex, STEP_BEGIN + stateRegex);

//Football state
let footballStateRegex = newStr.match(/=\[\];if\(\![A-Za-z0-9\$_]\[[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]\[[0-9]{1,3}\]\]\)\{/)[0];
footballStateRegex = footballStateRegex.split(";");
patch(footballStateRegex.join(";"), footballStateRegex[0] + ";" + FOOTBALL_STEP_BEGIN + footballStateRegex[1]);

//Apply latest state
const stateSetRegex = newStr.match(/\* 999\),[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\],null,[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\],true\);/)[0];
patch(stateSetRegex, stateSetRegex + SET_STATE);

//Handle player joins and rebans
let playerJoinedCustom = newStr.match(/\[\d\],ping:105\};/)[0];
patch(playerJoinedCustom, playerJoinedCustom+`window.bonkHost.handlePlayerJoined(...arguments);`);

//Joined room
let connectionMatch = newStr.match(/reconnection:false\}\);/)[0];
patch(connectionMatch, connectionMatch + `window.bonkHost.bans=[];`);


//Get some useful objects
let menuRegex = newStr.match(/== 13\){...\(\);}}/)[0];
patch(menuRegex, menuRegex + "window.bonkHost.menuFunctions = this; window.bonkHost.wrap();");
let toolRegex = newStr.match(/=new [A-Za-z0-9\$_]{1,3}\(this,[A-Za-z0-9\$_]{1,3}\[0\]\[0\],[A-Za-z0-9\$_]{1,3}\[0\]\[1\]\);/);
patch(toolRegex, toolRegex + "window.bonkHost.toolFunctions = this;");
let infoRegex = newStr.match(/[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\]=\{id:-1,element:null\};/)[0];
patch(infoRegex, infoRegex + "window.bonkHost.gameInfo = arguments;");
patch("newbonklobby_votewindow_close", "window.bonkHost.players = arguments[1];" + "newbonklobby_votewindow_close");
// state handling
patch("{a:0.0};", "{a:0.0};" + `window.bonkHost.stateFunctions = this;`);

//Big class
let bigClass = newStr.match(/[A-Z]\[[A-Za-z0-9\$_]{1,3}\[[0-9]+\]\[[0-9]+\]\]\([A-Za-z0-9\$_]{1,3}\[0\]\[0\]\);[A-Za-z0-9\$_]{1,3}\[[0-9]+\]\[[A-Za-z0-9\$_]{1,3}\[[0-9]+\]\[[0-9]+\]\]\([A-Za-z0-9\$_]{1,3}\[[0-9]+\],{m:/)[0][0];
patch(`function ${bigClass}(){}`, `function ${bigClass}(){};window.bonkHost.bigClass=${bigClass};`);

//Function for all callbacks
let callbacks = [...newStr.match(/[A-Za-z0-9\$_]{3}\(\.\.\./g)];
for(let callback of callbacks) {
	patch(`function ${callback}`, `window.bonkHost.bonkCallbacks["${callback.split("(")[0]}"] = ${callback.split("(")[0]};` + `function ${callback}`);
}

//Frames in game
let fig = newStr.match(/\[[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\] \- 30\]/)[0].split(' ')[0].slice(1);
patch(`${fig}++;`, `${fig}++;window.bonkHost.fig=${fig};`);

// v8.3: Client menu organization, shortcut buttons, and player-context private chat.
(function () {
	try {
		const V83 = "8.3";
		const $ = (id) => document.getElementById(id);

		// Naming cleanup: keep existing IDs so old code keeps working, but make labels client-style.
		window.bonkHost.applyClientMenuLabelsV83 = () => {
			const labels = {
				hostPlayerMenuHostToolsToggle: "ROOM TOOLS ▼",
				hostPlayerMenuPlaylistsToggle: "MAPS / PLAYLISTS ▼",
				hostPlayerMenuStatsToggle: "STATS ▼",
				hostPlayerMenuPlayersToggle: "PLAYER ACTIONS ▼",
				hostPlayerMenuDatabaseToggle: "DATABASE ▼",
				hostPlayerMenuPatchNotesToggle: "CHANGELOG ▼",
				hostPlayerMenuHelpToggle: "GUIDE ▼"
			};
			for(const [id, label] of Object.entries(labels)) {
				let el = $(id);
				if(el && !el.textContent.includes("▲")) el.textContent = label;
			}
		};

		// Better compact formatting. This fixes most jumbled rows without needing a bigger overhaul.
		window.bonkHost.injectClientOrganizerCssV83 = () => {
			if($("trapisClientV83Css")) return;
			let st = document.createElement("style");
			st.id = "trapisClientV83Css";
			st.textContent = `
				#hostPlayerMenu {
					min-width: 220px !important;
					max-width: 275px !important;
					width: 235px !important;
				}
				#hostPlayerMenuControls {
					max-height: 72vh !important;
					overflow-y: auto !important;
					overflow-x: hidden !important;
					scrollbar-width: thin;
				}
				#hostPlayerMenuControls .newbonklobby_settings_button {
					box-sizing: border-box !important;
					width: 100% !important;
					height: auto !important;
					min-height: 30px !important;
					line-height: 14px !important;
					padding: 6px 8px !important;
					white-space: normal !important;
					overflow-wrap: anywhere !important;
					text-align: center !important;
					display: flex !important;
					align-items: center !important;
					justify-content: center !important;
				}
				#hostPlayerMenuControls .hostPlayerMenuDropdownItem {
					font-size: 12px !important;
					line-height: 14px !important;
					justify-content: flex-start !important;
					text-align: left !important;
					text-transform: uppercase !important;
				}
				#hostPlayerMenuPatchNotesDropdown .hostPlayerMenuDropdownItem,
				#hostPlayerMenuHelpDropdown .hostPlayerMenuDropdownItem,
				#hostPlayerMenuRoadmapButton,
				#hostPlayerMenuDatabasePanelBody {
					text-transform: none !important;
					white-space: pre-wrap !important;
					word-break: normal !important;
					overflow-wrap: anywhere !important;
					line-height: 15px !important;
					font-size: 12px !important;
				}
				#hostPlayerMenuMainDropdown,
				#hostPlayerMenuTeamToolsDropdown,
				#hostPlayerMenuHostToolsDropdown,
				#hostPlayerMenuPlaylistsDropdown,
				#hostPlayerMenuStatsDropdown,
				#hostPlayerMenuPlayersDropdown,
				#hostPlayerMenuDatabaseDropdown,
				#hostPlayerMenuSocialDropdown,
				#hostPlayerMenuSettingsDropdown,
				#hostPlayerMenuHelpDropdown,
				#hostPlayerMenuPatchNotesDropdown {
					width: 100% !important;
					box-sizing: border-box !important;
				}
				.trapiClientSectionNote {
					background:#2f2927 !important;
					color:#fff !important;
					font-size:11px !important;
					line-height:13px !important;
					padding:6px 8px !important;
					opacity:0.92;
					text-transform:none !important;
					justify-content:flex-start !important;
					text-align:left !important;
				}
			`;
			document.head.appendChild(st);
		};

		window.bonkHost.makeV83Button = (id, label, onClick) => {
			let existing = $(id);
			if(existing) return existing;
			let d = document.createElement("div");
			d.id = id;
			d.className = "newbonklobby_settings_button brownButton brownButton_classic buttonShadow hostPlayerMenuDropdownItem";
			d.textContent = label;
			if(onClick) d.addEventListener("click", onClick);
			return d;
		};

		window.bonkHost.runChatCommandV83 = (cmd) => {
			try {
				if(window.bonkHost.processChatCommand) {
					let parts = cmd.replace(/^\//, "").split(" ");
					let command = parts.shift();
					window.bonkHost.processChatCommand(command, parts);
					return true;
				}
			} catch(e) {}
			try {
				let inp = $("newbonklobby_chat_input") || $("ingamechatinputtext");
				if(inp) {
					inp.value = cmd;
					inp.dispatchEvent(new KeyboardEvent("keydown", {key:"Enter", keyCode:13, which:13, bubbles:true}));
					return true;
				}
			} catch(e) {}
			window.bonkHost.clientStatus && window.bonkHost.clientStatus("Command shortcut unavailable here: " + cmd);
			return false;
		};

		window.bonkHost.promptCommandV83 = (template, promptText) => {
			let value = prompt(promptText);
			if(value === null) return;
			value = String(value).trim();
			if(!value) return;
			window.bonkHost.runChatCommandV83(template.replace("{value}", value));
		};

		window.bonkHost.addCommandShortcutButtonsV83 = () => {
			const players = $("hostPlayerMenuPlayersDropdown");
			if(players && !$("hostPlayerMenuKickShortcutButton")) {
				players.appendChild(window.bonkHost.makeV83Button("hostPlayerMenuKickShortcutButton", "KICK PLAYER", () => window.bonkHost.promptCommandV83("/kick {value}", "Kick which player?")));
				players.appendChild(window.bonkHost.makeV83Button("hostPlayerMenuBanShortcutButton", "BAN PLAYER", () => window.bonkHost.promptCommandV83("/ban {value}", "Ban which player?")));
				players.appendChild(window.bonkHost.makeV83Button("hostPlayerMenuMuteShortcutButton", "MUTE PLAYER", () => window.bonkHost.promptCommandV83("/mute {value}", "Mute which player?")));
				players.appendChild(window.bonkHost.makeV83Button("hostPlayerMenuUnmuteShortcutButton", "UNMUTE PLAYER", () => window.bonkHost.promptCommandV83("/unmute {value}", "Unmute which player?")));
			}
			const room = $("hostPlayerMenuHostToolsDropdown");
			if(room && !$("hostPlayerMenuRoomNameShortcutButton")) {
				room.appendChild(window.bonkHost.makeV83Button("hostPlayerMenuRoomNameShortcutButton", "SET ROOM NAME", () => window.bonkHost.promptCommandV83("/roomname {value}", "New room name?")));
				room.appendChild(window.bonkHost.makeV83Button("hostPlayerMenuRoomPassShortcutButton", "SET ROOM PASS", () => window.bonkHost.promptCommandV83("/roompass {value}", "New room password?")));
				room.appendChild(window.bonkHost.makeV83Button("hostPlayerMenuClearRoomPassShortcutButton", "CLEAR ROOM PASS", () => window.bonkHost.runChatCommandV83("/clearroompass")));
				room.appendChild(window.bonkHost.makeV83Button("hostPlayerMenuStartShortcutButton", "START GAME", () => window.bonkHost.runChatCommandV83("/start")));
			}
			const stats = $("hostPlayerMenuStatsDropdown");
			if(stats && !$("hostPlayerMenuScoreboardShortcutButton")) {
				stats.appendChild(window.bonkHost.makeV83Button("hostPlayerMenuScoreboardShortcutButton", "SHOW SCOREBOARD", () => window.bonkHost.runChatCommandV83("/scoreboard")));
			}
			const social = $("hostPlayerMenuSocialDropdown");
			if(social && !$("hostPlayerMenuPrivateChatQuickButton")) {
				social.insertBefore(window.bonkHost.makeV83Button("hostPlayerMenuPrivateChatQuickButton", "CHAT WITH PLAYER", () => window.bonkHost.startPrivateChatPrompt && window.bonkHost.startPrivateChatPrompt()), social.firstChild);
			}
		};

		// Accordion behavior: when one category opens, close the other categories so the menu stays clean.
		window.bonkHost.closeClientDropdownsV83 = (keepDropdownId) => {
			const pairs = [
				["hostPlayerMenuTeamToolsDropdown", "hostPlayerMenuTeamToolsToggle", "TEAM TOOLS ▼"],
				["hostPlayerMenuHostToolsDropdown", "hostPlayerMenuHostToolsToggle", "ROOM TOOLS ▼"],
				["hostPlayerMenuPlaylistsDropdown", "hostPlayerMenuPlaylistsToggle", "MAPS / PLAYLISTS ▼"],
				["hostPlayerMenuStatsDropdown", "hostPlayerMenuStatsToggle", "STATS ▼"],
				["hostPlayerMenuPlayersDropdown", "hostPlayerMenuPlayersToggle", "PLAYER ACTIONS ▼"],
				["hostPlayerMenuDatabaseDropdown", "hostPlayerMenuDatabaseToggle", "DATABASE ▼"],
				["hostPlayerMenuSocialDropdown", "hostPlayerMenuSocialToggle", "SOCIAL ▼"],
				["hostPlayerMenuSettingsDropdown", "hostPlayerMenuSettingsToggle", "SETTINGS ▼"],
				["hostPlayerMenuHelpDropdown", "hostPlayerMenuHelpToggle", "GUIDE ▼"],
				["hostPlayerMenuPatchNotesDropdown", "hostPlayerMenuPatchNotesToggle", "CHANGELOG ▼"]
			];
			for(const [dropId, toggleId, label] of pairs) {
				if(dropId === keepDropdownId) continue;
				let drop = $(dropId);
				let toggle = $(toggleId);
				if(drop) drop.style.display = "none";
				if(toggle) toggle.textContent = label;
			}
		};

		window.bonkHost.bindAccordionV83 = () => {
			const pairs = [
				["hostPlayerMenuTeamToolsToggle", "hostPlayerMenuTeamToolsDropdown"],
				["hostPlayerMenuHostToolsToggle", "hostPlayerMenuHostToolsDropdown"],
				["hostPlayerMenuPlaylistsToggle", "hostPlayerMenuPlaylistsDropdown"],
				["hostPlayerMenuStatsToggle", "hostPlayerMenuStatsDropdown"],
				["hostPlayerMenuPlayersToggle", "hostPlayerMenuPlayersDropdown"],
				["hostPlayerMenuDatabaseToggle", "hostPlayerMenuDatabaseDropdown"],
				["hostPlayerMenuSocialToggle", "hostPlayerMenuSocialDropdown"],
				["hostPlayerMenuSettingsToggle", "hostPlayerMenuSettingsDropdown"],
				["hostPlayerMenuHelpToggle", "hostPlayerMenuHelpDropdown"],
				["hostPlayerMenuPatchNotesToggle", "hostPlayerMenuPatchNotesDropdown"]
			];
			for(const [toggleId, dropId] of pairs) {
				let t = $(toggleId);
				if(!t || t.dataset.trapiAccordionV83 === "1") continue;
				t.dataset.trapiAccordionV83 = "1";
				t.addEventListener("click", () => window.bonkHost.closeClientDropdownsV83(dropId), true);
			}
		};

		const oldCollapseV83 = window.bonkHost.collapseAllHostMenuTabs;
		window.bonkHost.collapseAllHostMenuTabs = () => {
			try { oldCollapseV83 && oldCollapseV83(); } catch(e) {}
			window.bonkHost.closeClientDropdownsV83 && window.bonkHost.closeClientDropdownsV83("");
			let panel = $("hostPlayerMenuDatabasePanel");
			if(panel) panel.style.display = "none";
			let main = $("hostPlayerMenuMainDropdown");
			let mt = $("hostPlayerMenuMainToggle");
			if(main) main.style.display = "none";
			if(mt) mt.textContent = "MAIN MENU ▼";
		};

		window.bonkHost.addPrivateChatToPlayerMenuV83 = (playerName) => {
			setTimeout(() => {
				try {
					let menu = document.getElementsByClassName("newbonklobby_playerentry_menu")[0];
					if(!menu || !playerName) return;
					let old = menu.querySelector("#trapiPrivateChatPlayerButton");
					if(old) old.remove();
					let btn = document.createElement("div");
					btn.id = "trapiPrivateChatPlayerButton";
					btn.className = "newbonklobby_playerentry_menu_button brownButton brownButton_classic buttonShadow";
					btn.textContent = "CHAT";
					btn.title = "Private chat with this player if they have compatible client/Bonk Commands.";
					btn.onclick = (e) => {
						e.stopPropagation();
						if(window.bonkHost.connectPrivateChat) window.bonkHost.connectPrivateChat(playerName, true);
						if(menu) menu.style.display = "none";
					};
					menu.appendChild(btn);
				} catch(e) {}
			}, 80);
		};

		window.bonkHost.bindPlayerPrivateChatButtonsV83 = () => {
			if(window.bonkHost.playerPrivateChatContextBoundV83) return;
			window.bonkHost.playerPrivateChatContextBoundV83 = true;
			document.addEventListener("click", (e) => {
				try {
					let entry = e.target && e.target.closest && e.target.closest(".newbonklobby_playerentry");
					if(!entry) return;
					let nameNode = entry.children && entry.children[1];
					let playerName = nameNode ? nameNode.textContent.trim() : "";
					if(playerName) window.bonkHost.addPrivateChatToPlayerMenuV83(playerName);
				} catch(err) {}
			}, true);
		};

		window.bonkHost.organizeClientMenuV83 = () => {
			window.bonkHost.injectClientOrganizerCssV83();
			window.bonkHost.applyClientMenuLabelsV83();
			if(window.bonkHost.ensureSocialMenu) window.bonkHost.ensureSocialMenu();
			window.bonkHost.addCommandShortcutButtonsV83();
			window.bonkHost.bindAccordionV83();
			window.bonkHost.bindPlayerPrivateChatButtonsV83();
			let version = $("hostPlayerMenuClientVersion");
			if(version) version.textContent = "v" + V83;
		};

		window.bonkHost.organizeClientMenuV83();
		setTimeout(window.bonkHost.organizeClientMenuV83, 500);
		setTimeout(window.bonkHost.organizeClientMenuV83, 1800);
	} catch(e) {
		console.warn("Trapi's Client v8.3 menu organizer failed", e);
	}
})();

// v8.5 UI cleanup: docked popup-only display, accordion behavior, corrected total stat display.
(function(){
    try {
        const V85 = "8.5";
        const $ = (id) => document.getElementById(id);
        const safeText = (v) => {
            try { return (window.bonkHost.escapeClientText ? window.bonkHost.escapeClientText(String(v ?? "")) : String(v ?? "")); }
            catch(e) { return String(v ?? ""); }
        };
        const getMenuRect = () => {
            const menu = $("hostPlayerMenu");
            if(menu && menu.getBoundingClientRect) return menu.getBoundingClientRect();
            return { left: 10, top: 60, right: 245, width: 235 };
        };

        window.bonkHost.closeClientPopup = () => {
            let old = $("trapisClientInfoPopup");
            if(old) old.remove();
            window.bonkHost.activePopupTitleV85 = null;
        };

        window.bonkHost.positionClientPopupV85 = () => {
            const box = $("trapisClientInfoPopup");
            if(!box) return;
            const rect = getMenuRect();
            const gap = 8;
            const desiredLeft = Math.max(6, Math.round(rect.right + gap));
            const maxWidth = Math.max(240, window.innerWidth - desiredLeft - 12);
            box.style.left = desiredLeft + "px";
            box.style.top = Math.max(36, Math.round(rect.top)) + "px";
            box.style.width = Math.min(360, maxWidth) + "px";
            box.style.maxWidth = maxWidth + "px";
        };

        // Replaces the older floating popup with a docked panel next to the client.
        // Clicking the same opened tab again closes the popup.
        window.bonkHost.showClientPopup = (title, content) => {
            title = title || "TRAPI'S CLIENT";
            let old = $("trapisClientInfoPopup");
            if(old && window.bonkHost.activePopupTitleV85 === title) {
                window.bonkHost.closeClientPopup();
                return;
            }
            if(old) old.remove();

            let lines = Array.isArray(content) ? content : [String(content || "")];
            let box = document.createElement("div");
            box.id = "trapisClientInfoPopup";
            box.innerHTML =
                `<div id="trapisClientInfoPopupTop">` +
                `<span>${safeText(title)}</span>` +
                `<button id="trapisClientInfoPopupClose" title="Close">×</button>` +
                `</div><div id="trapisClientInfoPopupBody"></div>`;
            let body = box.querySelector("#trapisClientInfoPopupBody");
            body.textContent = lines.join("\n");
            (document.getElementById("pagecontainer") || document.body || document.documentElement).appendChild(box);
            let close = $("trapisClientInfoPopupClose");
            if(close) close.onclick = () => window.bonkHost.closeClientPopup();
            window.bonkHost.activePopupTitleV85 = title;
            window.bonkHost.positionClientPopupV85();
        };

        // Popup is now the only informational display for these client tools.
        // No duplicated top info panel. No duplicated chat dump.
        window.bonkHost.showDatabasePanel = (title, lines) => {
            try {
                const panel = $("hostPlayerMenuDatabasePanel");
                if(panel) panel.style.display = "none";
                window.bonkHost.showClientPopup(title || "PLAYER DATABASE", lines || []);
            } catch(e) { console.warn("Trapi's Client database panel failed", e); }
        };
        window.bonkHost.showHelpMessages = (title, lines) => {
            try { window.bonkHost.showClientPopup(title || "TRAPI'S CLIENT", lines || []); }
            catch(e) { alert((title || "TRAPI'S CLIENT") + "\n\n" + (lines || []).join("\n")); }
        };

        // Stronger close/reopen. The old launcher sometimes stayed hidden or had stale handlers.
        window.bonkHost.ensureClientLauncher = () => {
            let launcher = $("hostPlayerMenuLauncher");
            if(!launcher) {
                launcher = document.createElement("div");
                launcher.id = "hostPlayerMenuLauncher";
                launcher.className = "newbonklobby_settings_button brownButton brownButton_classic buttonShadow";
                launcher.textContent = "T";
                launcher.title = "Open Trapi's Client";
                (document.getElementById("pagecontainer") || document.body || document.documentElement).appendChild(launcher);
            }
            launcher.onclick = (e) => {
                try { e.preventDefault(); e.stopPropagation(); } catch(_e) {}
                window.bonkHost.openClient && window.bonkHost.openClient();
            };
            return launcher;
        };
        window.bonkHost.closeClient = () => {
            try {
                window.bonkHost.clientClosed = true;
                localStorage.setItem("trapisClientClosed", "true");
                let menu = $("hostPlayerMenu");
                let launcher = window.bonkHost.ensureClientLauncher();
                if(menu) menu.style.display = "none";
                if(launcher) launcher.style.display = "block";
                window.bonkHost.closeClientPopup && window.bonkHost.closeClientPopup();
            } catch(e) { console.warn("Trapi's Client close failed", e); }
        };
        window.bonkHost.openClient = () => {
            try {
                window.bonkHost.clientClosed = false;
                localStorage.setItem("trapisClientClosed", "false");
                let menu = $("hostPlayerMenu");
                let launcher = window.bonkHost.ensureClientLauncher();
                if(menu) menu.style.display = "block";
                if(launcher) launcher.style.display = "none";
                setTimeout(() => window.bonkHost.positionClientPopupV85 && window.bonkHost.positionClientPopupV85(), 50);
            } catch(e) { console.warn("Trapi's Client open failed", e); }
        };
        window.bonkHost.ensureClientLauncher();

        // Only one category may be open at a time, and action buttons collapse their category after opening a popup/tool.
        const dropdownPairs = [
            ["hostPlayerMenuTeamToolsDropdown", "hostPlayerMenuTeamToolsToggle", "TEAM TOOLS ▼"],
            ["hostPlayerMenuHostToolsDropdown", "hostPlayerMenuHostToolsToggle", "ROOM TOOLS ▼"],
            ["hostPlayerMenuPlaylistsDropdown", "hostPlayerMenuPlaylistsToggle", "MAPS / PLAYLISTS ▼"],
            ["hostPlayerMenuStatsDropdown", "hostPlayerMenuStatsToggle", "STATS ▼"],
            ["hostPlayerMenuPlayersDropdown", "hostPlayerMenuPlayersToggle", "PLAYER ACTIONS ▼"],
            ["hostPlayerMenuDatabaseDropdown", "hostPlayerMenuDatabaseToggle", "PLAYER DATABASE ▼"],
            ["hostPlayerMenuSocialDropdown", "hostPlayerMenuSocialToggle", "SOCIAL ▼"],
            ["hostPlayerMenuSettingsDropdown", "hostPlayerMenuSettingsToggle", "SETTINGS ▼"],
            ["hostPlayerMenuHelpDropdown", "hostPlayerMenuHelpToggle", "HELP ▼"],
            ["hostPlayerMenuPatchNotesDropdown", "hostPlayerMenuPatchNotesToggle", "PATCH NOTES ▼"]
        ];
        window.bonkHost.closeOtherClientDropdownsV85 = (keepId) => {
            for(const [dropId, togId, closedText] of dropdownPairs) {
                if(dropId === keepId) continue;
                const d = $(dropId), t = $(togId);
                if(d) d.style.display = "none";
                if(t) t.textContent = closedText;
            }
            window.bonkHost.updateHostPlayerMenuDropdownHeight && window.bonkHost.updateHostPlayerMenuDropdownHeight();
        };
        window.bonkHost.collapseAllHostMenuTabs = () => {
            for(const [dropId, togId, closedText] of dropdownPairs) {
                const d = $(dropId), t = $(togId);
                if(d) d.style.display = "none";
                if(t) t.textContent = closedText;
            }
            let main = $("hostPlayerMenuMainDropdown");
            let mainToggleEl = $("hostPlayerMenuMainToggle");
            if(main) main.style.display = "none";
            if(mainToggleEl) mainToggleEl.textContent = "MAIN MENU ▼";
            window.bonkHost.updateCollapseMenuButtonVisibility && window.bonkHost.updateCollapseMenuButtonVisibility();
            window.bonkHost.updateHostPlayerMenuDropdownHeight && window.bonkHost.updateHostPlayerMenuDropdownHeight();
        };

        // Capture click after the existing handlers run. Accordion + auto-collapse is handled here.
        document.addEventListener("click", (e) => {
            try {
                const target = e.target && e.target.closest ? e.target.closest(".hostPlayerMenuDropdownItem, #hostPlayerMenuControls > .newbonklobby_settings_button") : null;
                if(!target || !$("hostPlayerMenuControls") || !$("hostPlayerMenuControls").contains(target)) return;

                const id = target.id || "";
                const pair = dropdownPairs.find(([dropId, togId]) => togId === id);
                if(pair) {
                    setTimeout(() => {
                        const d = $(pair[0]);
                        if(d && window.getComputedStyle(d).display !== "none") window.bonkHost.closeOtherClientDropdownsV85(pair[0]);
                    }, 0);
                    return;
                }

                // After clicking an action inside a dropdown, collapse dropdowns so the left UI stays compact.
                if(target.classList.contains("hostPlayerMenuDropdownItem")) {
                    setTimeout(() => {
                        for(const [dropId, togId, closedText] of dropdownPairs) {
                            const d = $(dropId), t = $(togId);
                            if(d) d.style.display = "none";
                            if(t) t.textContent = closedText;
                        }
                        window.bonkHost.updateHostPlayerMenuDropdownHeight && window.bonkHost.updateHostPlayerMenuDropdownHeight();
                    }, 50);
                }
            } catch(_e) {}
        }, true);

        // Correct inflated or stale total display by making total equal to the visible stat buckets
        // when those buckets exist. This prevents old migration math from showing a nonsense total.
        window.bonkHost.getCorrectedStatsRecordV85 = (rec) => {
            rec = window.bonkHost.normalizeStatsBuckets ? window.bonkHost.normalizeStatsBuckets(rec || {}) : (rec || {});
            const keys = ["quickplay", "onevone", "team", "custom"];
            let games = 0, wins = 0, hasBuckets = false;
            for(const k of keys) {
                if(rec[k] && ((rec[k].games || 0) || (rec[k].wins || 0))) hasBuckets = true;
                games += (rec[k] && rec[k].games) || 0;
                wins += (rec[k] && rec[k].wins) || 0;
            }
            if(hasBuckets) {
                rec.total = rec.total || {games:0,wins:0};
                rec.total.games = games;
                rec.total.wins = wins;
                rec.games = games;
                rec.wins = wins;
            }
            return rec;
        };
        const oldStatLineV85 = window.bonkHost.statLine;
        window.bonkHost.statLine = (label, rec) => {
            if(label && /^(Total|All Games)/i.test(label)) rec = window.bonkHost.getCorrectedStatsRecordV85(rec);
            rec = rec || { wins: 0, games: 0 };
            return label + ": " + (rec.wins || 0) + "W / " + (rec.games || 0) + "G / " + window.bonkHost.formatWR(rec.wins || 0, rec.games || 0);
        };
        const oldMergeV85 = window.bonkHost.mergeHistoryRecords;
        window.bonkHost.mergeHistoryRecords = (names) => {
            const merged = oldMergeV85 ? oldMergeV85(names) : {};
            return window.bonkHost.getCorrectedStatsRecordV85(merged);
        };

        // Cleaner My Profile wording + corrected total games.
        window.bonkHost.showMyProfile = () => {
            let xp = window.bonkHost.getBonkXpProfile();
            let name = xp.displayName;
            let career = window.bonkHost.getCareerStatsRecord ? window.bonkHost.getCareerStatsRecord(window.bonkHost.getCanonicalPlayerName(name)) : {};
            career = window.bonkHost.getCorrectedStatsRecordV85(career);
            let profile = window.bonkHost.ensureClientProfile();
            let lines = [
                "Name: " + name,
                "Client Joined: " + new Date(xp.joinedDate).toLocaleDateString(),
                "Joined Version: " + (profile.joinedVersion || "8.1"),
                "Level: " + xp.level,
                "Total XP: " + xp.totalXp.toLocaleString(),
                "XP This Level: " + (xp.xpProgressWithinLevel || 0).toLocaleString(),
                "Tracked Round Wins This Level: " + (xp.trackedRoundWinsSinceLevelSync || 0),
                "XP Until Next Level: " + xp.remaining.toLocaleString(),
                "Round Wins Until Next Level: " + xp.roundWinsUntilNext,
                "XP CAPPED: " + (xp.xpCap && xp.xpCap.capped ? "YES" : "NO"),
                "XP Cap Reset: " + (xp.xpCap && xp.xpCap.capped ? window.bonkHost.formatDuration(xp.xpCap.cappedUntil - Date.now()) : "Not capped"),
                "",
                window.bonkHost.statLine("Total Games Played", career.total),
                window.bonkHost.statLine("Classic Quickplay", career.quickplay),
                window.bonkHost.statLine("1v1s", career.onevone),
                window.bonkHost.statLine("Team Games", career.team),
                window.bonkHost.statLine("Free For All", career.custom),
                "Current Streak: " + (career.currentStreak || 0),
                "Best Streak: " + (career.bestStreak || 0),
                "Last Seen: " + window.bonkHost.formatTimeAgo(career.lastSeen),
                "",
                "Stats now display using corrected bucket totals so old migration/duplicate total math does not inflate Total Games Played."
            ];
            window.bonkHost.showDatabasePanel("MY PROFILE", lines);
        };
        window.bonkHost.showMyCareerStats = window.bonkHost.showMyProfile;

        // Keep popup docked after dragging/resizing/toggling.
        window.addEventListener("resize", () => window.bonkHost.positionClientPopupV85 && window.bonkHost.positionClientPopupV85());
        setInterval(() => { try { window.bonkHost.positionClientPopupV85 && window.bonkHost.positionClientPopupV85(); } catch(e) {} }, 1200);

        const css = document.createElement("style");
        css.id = "trapisClientV85Css";
        css.textContent = `
            #hostPlayerMenu { min-width:205px !important; max-width:245px !important; width:225px !important; overflow:visible !important; }
            #hostPlayerMenuDatabasePanel { display:none !important; }
            #hostPlayerMenuBox { max-height:calc(47px * 2) !important; }
            #hostPlayerMenuControls .newbonklobby_settings_button {
                box-sizing:border-box !important;
                overflow:hidden !important;
                text-overflow:ellipsis !important;
                white-space:nowrap !important;
            }
            .hostPlayerMenuDropdownItem {
                min-height:27px !important;
                height:auto !important;
                line-height:15px !important;
                padding-top:6px !important;
                padding-bottom:6px !important;
                font-size:12px !important;
                white-space:normal !important;
            }
            #hostPlayerMenuPatchNotesDropdown .hostPlayerMenuDropdownItem,
            #hostPlayerMenuHelpDropdown .hostPlayerMenuDropdownItem {
                white-space:normal !important;
                text-transform:none !important;
                line-height:15px !important;
                min-height:34px !important;
            }
            #trapisClientInfoPopup {
                position:absolute !important;
                width:340px;
                max-height:58vh;
                overflow:hidden;
                z-index:2147483002;
                background:#2f2927;
                color:#fff;
                border:2px solid #6f4f42;
                border-radius:8px;
                box-shadow:0 8px 20px rgba(0,0,0,.35);
                font-family:futurept_b1,Arial,sans-serif;
            }
            #trapisClientInfoPopupTop {
                min-height:30px;
                display:flex;
                align-items:center;
                justify-content:space-between;
                gap:8px;
                padding:0 7px 0 10px;
                background:#6f4f42;
                font-size:13px;
                font-weight:800;
                text-transform:uppercase;
            }
            #trapisClientInfoPopupTop span {
                overflow:hidden;
                text-overflow:ellipsis;
                white-space:nowrap;
            }
            #trapisClientInfoPopupClose {
                flex:0 0 auto;
                width:24px;
                height:24px;
                border:0;
                border-radius:3px;
                background:#8b6f61;
                color:#fff;
                font-size:18px;
                line-height:20px;
                cursor:pointer;
                font-weight:900;
            }
            #trapisClientInfoPopupBody {
                padding:10px;
                overflow:auto;
                max-height:calc(58vh - 34px);
                white-space:pre-wrap;
                font-size:12px;
                line-height:16px;
            }
            #hostPlayerMenuLauncher {
                display:none;
                position:absolute !important;
                left:10px !important;
                top:60px !important;
                z-index:2147483000 !important;
                width:38px !important;
                height:38px !important;
                line-height:38px !important;
                cursor:pointer !important;
            }
        `;
        document.head.appendChild(css);

        const version = $("hostPlayerMenuClientVersion");
        if(version) version.textContent = "v" + V85;

        // Apply label cleanup if these older elements exist.
        const relabel = {
            hostPlayerMenuHostToolsToggle: "ROOM TOOLS ▼",
            hostPlayerMenuPlaylistsToggle: "MAPS / PLAYLISTS ▼",
            hostPlayerMenuStatsToggle: "STATS ▼",
            hostPlayerMenuPlayersToggle: "PLAYER ACTIONS ▼"
        };
        for(const [id, label] of Object.entries(relabel)) {
            const el = $(id);
            if(el && el.textContent.includes("▼")) el.textContent = label;
        }
    } catch(e) {
        console.warn("Trapi's Client v8.5 cleanup failed", e);
    }
})();



// v8.6 UI shell: Main Menu stays as the only dropdown; every category opens as a docked popup menu.
(function(){
    try {
        const V86 = "8.6";
        const $ = (id) => document.getElementById(id);
        const safeText = (v) => {
            try { return (window.bonkHost.escapeClientText ? window.bonkHost.escapeClientText(String(v ?? "")) : String(v ?? "")); }
            catch(e) { return String(v ?? ""); }
        };

        const categoryDefs = [
            {
                key: "team",
                title: "TEAM TOOLS",
                toggleId: "hostPlayerMenuTeamToolsToggle",
                dropdownId: "hostPlayerMenuTeamToolsDropdown",
                label: "TEAM TOOLS",
                desc: "Team movement, team shuffle, and team setup tools.",
                include: ["hostPlayerMenuShuffleRBButton","hostPlayerMenuAutoBalanceButton","hostPlayerMenuSwapTeamsButton","hostPlayerMenuAllSpecButton","hostPlayerMenuCaptainsButton"]
            },
            {
                key: "room",
                title: "ROOM TOOLS",
                toggleId: "hostPlayerMenuHostToolsToggle",
                dropdownId: "hostPlayerMenuHostToolsDropdown",
                label: "ROOM TOOLS",
                desc: "Host controls and room command shortcuts.",
                include: ["hostPlayerMenuTeamlock","hostPlayerMenuFreejoin","hostPlayerMenuKeepScores","hostPlayerMenuCheatDetectionCheckbox","hostPlayerMenuKeepPositions","hostPlayerMenuAkToggleButton","hostPlayerMenuRoomNameShortcutButton","hostPlayerMenuRoomPassShortcutButton","hostPlayerMenuClearRoomPassShortcutButton","hostPlayerMenuStartShortcutButton"]
            },
            {
                key: "maps",
                title: "MAPS / PLAYLISTS",
                toggleId: "hostPlayerMenuPlaylistsToggle",
                dropdownId: "hostPlayerMenuPlaylistsDropdown",
                label: "MAPS / PLAYLISTS",
                desc: "Playlist controls, quickplay controls, and map history.",
                include: ["hostPlayerMenuOpenPlaylistsButton","hostPlayerMenuQuickplayButton","hostPlayerMenuQpLeaderStatus","hostPlayerMenuQpYourWinsStatus","hostPlayerMenuQpRoundsButton","hostPlayerMenuQpShuffleButton","hostPlayerMenuMapHistoryStatus","hostPlayerMenuQpPrevButton","hostPlayerMenuQpNextButton"]
            },
            {
                key: "stats",
                title: "STATS",
                toggleId: "hostPlayerMenuStatsToggle",
                dropdownId: "hostPlayerMenuStatsDropdown",
                label: "STATS",
                desc: "Match stats, scoreboard tools, and winrate views.",
                include: ["hostPlayerMenuStatsOverview","hostPlayerMenuRedBlueWinrate","hostPlayerMenuSmartBalanceButton","hostPlayerMenuScoreboardShortcutButton"]
            },
            {
                key: "players",
                title: "PLAYER ACTIONS",
                toggleId: "hostPlayerMenuPlayersToggle",
                dropdownId: "hostPlayerMenuPlayersDropdown",
                label: "PLAYER ACTIONS",
                desc: "Player moderation and quick actions.",
                include: ["hostPlayerMenuAutoHostButton","hostPlayerMenuSetAutoHostButton","hostPlayerMenuRecentJoinsStatus","hostPlayerMenuPlayerNotesButton","hostPlayerMenuKickShortcutButton","hostPlayerMenuBanShortcutButton","hostPlayerMenuMuteShortcutButton","hostPlayerMenuUnmuteShortcutButton"]
            },
            {
                key: "database",
                title: "DATABASE",
                toggleId: "hostPlayerMenuDatabaseToggle",
                dropdownId: "hostPlayerMenuDatabaseDropdown",
                label: "DATABASE",
                desc: "Profiles, player history, rivals, chemistry, tags, and notes.",
                include: ["hostPlayerMenuMyProfileButton","hostPlayerMenuPlayerLookupButton","hostPlayerMenuRecentPlayersButton","hostPlayerMenuTopPlayedWithButton","hostPlayerMenuTopWinrateButton","hostPlayerMenuRivalsButton","hostPlayerMenuTeamChemistryButton","hostPlayerMenuEditFavoriteModeButton","hostPlayerMenuEditPlayerNoteButton","hostPlayerMenuTrustedButton","hostPlayerMenuProblemButton","hostPlayerMenuLinkAccountsButton","hostPlayerMenuAccountLinksButton","hostPlayerMenuDatabaseHealthButton","hostPlayerMenuSetExactXpButton","hostPlayerMenuXpCapButton"]
            },
            {
                key: "social",
                title: "SOCIAL",
                toggleId: "hostPlayerMenuSocialToggle",
                dropdownId: "hostPlayerMenuSocialDropdown",
                label: "SOCIAL",
                desc: "Private chat, compatible users, saved room watch, and social shortcuts.",
                include: ["hostPlayerMenuPrivateChatQuickButton","hostPlayerMenuPrivateChatButton","hostPlayerMenuPrivateChatUsersButton","hostPlayerMenuSaveRoomButton","hostPlayerMenuSavedRoomsButton","hostPlayerMenuClearSavedRoomsButton","hostPlayerMenuCommandShortcutsButton"]
            },
            {
                key: "settings",
                title: "SETTINGS",
                toggleId: "hostPlayerMenuSettingsToggle",
                dropdownId: "hostPlayerMenuSettingsDropdown",
                label: "SETTINGS",
                desc: "Client toggles, import/export, and system settings.",
                include: ["hostPlayerMenuAutoTeamJoinButton","hostPlayerMenuAutoJoinNextButton","hostPlayerMenuGlobalHistoryButton","hostPlayerMenuCareerStatsButton","hostPlayerMenuShowFlagsButton","hostPlayerMenuSettingsSmartShuffleButton","hostPlayerMenuSettingsRejoinProtectionButton","hostPlayerMenuExportSettingsButton","hostPlayerMenuImportSettingsButton","hostPlayerMenuStatusButton","hostPlayerMenuVersionInfoButton"]
            },
            {
                key: "guide",
                title: "GUIDE",
                toggleId: "hostPlayerMenuHelpToggle",
                dropdownId: "hostPlayerMenuHelpDropdown",
                label: "GUIDE",
                desc: "Help pages for each part of the client.",
                include: ["hostPlayerMenuHelpWelcome","hostPlayerMenuHelpTeamTools","hostPlayerMenuHelpHostTools","hostPlayerMenuHelpPlaylists","hostPlayerMenuHelpStats","hostPlayerMenuHelpPlayers","hostPlayerMenuHelpSettings"]
            },
            {
                key: "changelog",
                title: "CHANGELOG",
                toggleId: "hostPlayerMenuPatchNotesToggle",
                dropdownId: "hostPlayerMenuPatchNotesDropdown",
                label: "CHANGELOG",
                desc: "Patch notes and update history.",
                include: ["hostPlayerMenuPatchNotesSummary","hostPlayerMenuPatchNotesCompactButton","hostPlayerMenuPatchNotesV59","hostPlayerMenuPatchNotesV57","hostPlayerMenuPatchNotesV56","hostPlayerMenuPatchNotesV55","hostPlayerMenuPatchNotesV54","hostPlayerMenuPatchNotesV53","hostPlayerMenuPatchNotesQuickplay","hostPlayerMenuPatchNotesTools","hostPlayerMenuPatchNotesStats","hostPlayerMenuPatchNotesPlayers"]
            }
        ];
        const categoryByToggle = Object.fromEntries(categoryDefs.map(d => [d.toggleId, d]));

        window.bonkHost.closeAllCategoryDropdownsV86 = () => {
            for(const def of categoryDefs) {
                const d = $(def.dropdownId);
                const t = $(def.toggleId);
                if(d) d.style.display = "none";
                if(t) t.textContent = def.label + " ▼";
            }
            window.bonkHost.updateHostPlayerMenuDropdownHeight && window.bonkHost.updateHostPlayerMenuDropdownHeight();
        };

        window.bonkHost.showClientPopup = (title, content) => {
            title = title || "TRAPI'S CLIENT";
            let old = $("trapisClientInfoPopup");
            if(old) old.remove();
            let box = document.createElement("div");
            box.id = "trapisClientInfoPopup";
            box.innerHTML =
                `<div id="trapisClientInfoPopupTop"><span>${safeText(title)}</span><button id="trapisClientInfoPopupClose" title="Close">×</button></div>` +
                `<div id="trapisClientInfoPopupBody"></div>`;
            let body = box.querySelector("#trapisClientInfoPopupBody");
            if(content instanceof Node) body.appendChild(content);
            else if(Array.isArray(content)) body.textContent = content.join("\n");
            else body.textContent = String(content || "");
            (document.getElementById("pagecontainer") || document.body || document.documentElement).appendChild(box);
            let close = $("trapisClientInfoPopupClose");
            if(close) close.onclick = () => window.bonkHost.closeClientPopup && window.bonkHost.closeClientPopup();
            window.bonkHost.activePopupTitleV85 = title;
            window.bonkHost.positionClientPopupV85 && window.bonkHost.positionClientPopupV85();
        };

        window.bonkHost.closeClientPopup = () => {
            let old = $("trapisClientInfoPopup");
            if(old) old.remove();
            window.bonkHost.activeCategoryPopupV86 = null;
            window.bonkHost.activePopupTitleV85 = null;
        };

        window.bonkHost.makePopupMenuButtonV86 = (sourceEl, def) => {
            let btn = document.createElement("button");
            btn.className = "trapisPopupMenuButton";
            btn.textContent = (sourceEl.textContent || sourceEl.innerText || sourceEl.id || "ACTION").replace(/\s+/g," ").trim();
            btn.title = btn.textContent;
            btn.onclick = (ev) => {
                try { ev.preventDefault(); ev.stopPropagation(); } catch(e) {}
                try {
                    window.bonkHost.allowHiddenActionV86 = true;
                    sourceEl.click();
                } catch(e) {
                    console.warn("Trapi's Client popup action failed", e);
                    window.bonkHost.showClientPopup("ACTION FAILED", ["This button could not run from the popup.", String(e && e.message ? e.message : e)]);
                } finally {
                    setTimeout(() => { window.bonkHost.allowHiddenActionV86 = false; }, 0);
                }
                // Refresh labels for toggles after the action changes state.
                setTimeout(() => {
                    try {
                        if(window.bonkHost.activeCategoryPopupV86 === def.key) window.bonkHost.openCategoryPopupV86(def, true);
                    } catch(e) {}
                }, 120);
            };
            return btn;
        };

        window.bonkHost.openCategoryPopupV86 = (def, force) => {
            if(!def) return;
            if(!force && window.bonkHost.activeCategoryPopupV86 === def.key) {
                window.bonkHost.closeClientPopup && window.bonkHost.closeClientPopup();
                return;
            }
            window.bonkHost.closeAllCategoryDropdownsV86 && window.bonkHost.closeAllCategoryDropdownsV86();
            window.bonkHost.activeCategoryPopupV86 = def.key;

            let wrap = document.createElement("div");
            wrap.className = "trapisPopupMenuWrap";
            let desc = document.createElement("div");
            desc.className = "trapisPopupMenuDesc";
            desc.textContent = def.desc || "";
            wrap.appendChild(desc);

            let grid = document.createElement("div");
            grid.className = "trapisPopupMenuGrid";
            let seen = new Set();
            for(const id of def.include || []) {
                let src = $(id);
                if(!src || seen.has(id)) continue;
                seen.add(id);
                grid.appendChild(window.bonkHost.makePopupMenuButtonV86(src, def));
            }
            // Fallback: pull any new future buttons from the old hidden dropdown.
            let oldDrop = $(def.dropdownId);
            if(oldDrop) {
                [...oldDrop.querySelectorAll(".hostPlayerMenuDropdownItem, .newbonklobby_settings_button")].forEach(src => {
                    if(!src.id || seen.has(src.id)) return;
                    seen.add(src.id);
                    grid.appendChild(window.bonkHost.makePopupMenuButtonV86(src, def));
                });
            }
            if(!grid.children.length) {
                let empty = document.createElement("div");
                empty.className = "trapisPopupEmpty";
                empty.textContent = "No tools are available here yet.";
                grid.appendChild(empty);
            }
            wrap.appendChild(grid);
            window.bonkHost.showClientPopup(def.title, wrap);
        };

        // Stop old category dropdown behavior and route categories into popup menus.
        document.addEventListener("click", (e) => {
            try {
                if(window.bonkHost.allowHiddenActionV86) return;
                const target = e.target && e.target.closest ? e.target.closest(".newbonklobby_settings_button") : null;
                if(!target || !$("hostPlayerMenuControls") || !$("hostPlayerMenuControls").contains(target)) return;
                const def = categoryByToggle[target.id];
                if(!def) return;
                e.preventDefault();
                e.stopPropagation();
                e.stopImmediatePropagation();
                window.bonkHost.openCategoryPopupV86(def, false);
            } catch(err) { console.warn("Trapi's Client category popup click failed", err); }
        }, true);

        // Cleaner main-menu labels and no inline category dropdowns.
        window.bonkHost.applyV86Labels = () => {
            for(const def of categoryDefs) {
                const t = $(def.toggleId);
                if(t) t.textContent = def.label + " ▼";
                const d = $(def.dropdownId);
                if(d) d.style.display = "none";
            }
            const help = $("hostPlayerMenuHelpToggle"); if(help) help.textContent = "GUIDE ▼";
            const patch = $("hostPlayerMenuPatchNotesToggle"); if(patch) patch.textContent = "CHANGELOG ▼";
            const db = $("hostPlayerMenuDatabaseToggle"); if(db) db.textContent = "DATABASE ▼";
        };
        window.bonkHost.applyV86Labels();
        setInterval(() => { try { window.bonkHost.applyV86Labels(); } catch(e) {} }, 1500);

        // Make collapse close popups too, and keep Main Menu as the only dropdown.
        const oldCollapseV86 = window.bonkHost.collapseAllHostMenuTabs;
        window.bonkHost.collapseAllHostMenuTabs = () => {
            try { oldCollapseV86 && oldCollapseV86(); } catch(e) {}
            window.bonkHost.closeAllCategoryDropdownsV86 && window.bonkHost.closeAllCategoryDropdownsV86();
            window.bonkHost.closeClientPopup && window.bonkHost.closeClientPopup();
            const main = $("hostPlayerMenuMainDropdown");
            const mt = $("hostPlayerMenuMainToggle");
            if(main) main.style.display = "none";
            if(mt) mt.textContent = "MAIN MENU ▼";
        };

        // Recalculate display total from the mode buckets every time profile/history views render.
        window.bonkHost.getCorrectedStatsRecordV86 = (rec) => {
            rec = window.bonkHost.getCorrectedStatsRecordV85 ? window.bonkHost.getCorrectedStatsRecordV85(rec || {}) : (rec || {});
            const buckets = ["quickplay","onevone","team","custom"];
            let games = 0, wins = 0, found = false;
            for(const k of buckets) {
                if(rec[k] && (Number(rec[k].games || 0) || Number(rec[k].wins || 0))) found = true;
                games += Number((rec[k] && rec[k].games) || 0);
                wins += Number((rec[k] && rec[k].wins) || 0);
            }
            if(found) {
                rec.total = rec.total || {};
                rec.total.games = games;
                rec.total.wins = wins;
                rec.games = games;
                rec.wins = wins;
            }
            return rec;
        };
        const oldMergeV86 = window.bonkHost.mergeHistoryRecords;
        window.bonkHost.mergeHistoryRecords = (names) => window.bonkHost.getCorrectedStatsRecordV86(oldMergeV86 ? oldMergeV86(names) : {});

        const css = document.createElement("style");
        css.id = "trapisClientV86Css";
        css.textContent = `
            #hostPlayerMenuTeamToolsDropdown,
            #hostPlayerMenuHostToolsDropdown,
            #hostPlayerMenuPlaylistsDropdown,
            #hostPlayerMenuStatsDropdown,
            #hostPlayerMenuPlayersDropdown,
            #hostPlayerMenuDatabaseDropdown,
            #hostPlayerMenuSocialDropdown,
            #hostPlayerMenuSettingsDropdown,
            #hostPlayerMenuHelpDropdown,
            #hostPlayerMenuPatchNotesDropdown {
                display:none !important;
            }
            #hostPlayerMenuMainDropdown { display:none; }
            #hostPlayerMenuControls .newbonklobby_settings_button {
                height:29px !important;
                min-height:29px !important;
                line-height:29px !important;
                text-align:center !important;
            }
            #trapisClientInfoPopup { width:360px !important; max-height:65vh !important; }
            #trapisClientInfoPopupBody { max-height:calc(65vh - 34px) !important; }
            .trapisPopupMenuWrap { white-space:normal; }
            .trapisPopupMenuDesc {
                font-size:12px;
                line-height:16px;
                opacity:.9;
                margin-bottom:9px;
                padding-bottom:8px;
                border-bottom:1px solid rgba(255,255,255,.14);
            }
            .trapisPopupMenuGrid {
                display:grid;
                grid-template-columns:1fr 1fr;
                gap:7px;
            }
            .trapisPopupMenuButton {
                min-height:32px;
                border:0;
                border-radius:4px;
                background:#8b6f61;
                color:#fff;
                font-family:futurept_b1,Arial,sans-serif;
                font-size:11px;
                font-weight:800;
                line-height:14px;
                padding:6px 7px;
                cursor:pointer;
                text-transform:uppercase;
                text-align:center;
                box-shadow:0 2px 4px rgba(0,0,0,.22);
            }
            .trapisPopupMenuButton:hover { filter:brightness(1.08); }
            .trapisPopupEmpty { grid-column:1 / -1; opacity:.8; font-size:12px; }
        `;
        document.head.appendChild(css);
        const version = $("hostPlayerMenuClientVersion");
        if(version) version.textContent = "v" + V86;
    } catch(e) {
        console.warn("Trapi's Client v8.6 popup menu shell failed", e);
    }
})();

/* ===== Trapi's Client v8.7 - polished client shell / reduced redundancy ===== */
(() => {
    const V87 = "8.7";
    const $ = (id) => document.getElementById(id);
    const txt = (value) => String(value == null ? "" : value);
    const safe = (value) => {
        const div = document.createElement("div");
        div.textContent = txt(value);
        return div.innerHTML;
    };
    const clickIf = (id) => {
        const el = $(id);
        if(!el) return false;
        try {
            window.bonkHost.allowHiddenActionV86 = true;
            el.click();
            return true;
        } catch(e) {
            console.warn("Trapi's Client action failed:", id, e);
            return false;
        } finally {
            setTimeout(() => { window.bonkHost.allowHiddenActionV86 = false; }, 0);
        }
    };
    const getJson = (key, fallback) => {
        try {
            const raw = localStorage.getItem(key);
            return raw ? JSON.parse(raw) : fallback;
        } catch(e) { return fallback; }
    };
    const pct = (wins, games) => {
        games = Number(games || 0); wins = Number(wins || 0);
        return games ? Math.round((wins / games) * 1000) / 10 : 0;
    };
    const bar = (value, max, label) => {
        value = Number(value || 0); max = Number(max || 0);
        const pc = max ? Math.max(0, Math.min(100, (value / max) * 100)) : 0;
        return `<div class="trapisV87StatBar"><div class="trapisV87StatBarLabel">${safe(label || "")}<span>${Math.round(pc)}%</span></div><div class="trapisV87BarTrack"><div class="trapisV87BarFill" style="width:${pc}%"></div></div></div>`;
    };
    const playerDb = () => {
        const keys = ["trapisClientPlayerMeta","trapisClientPlayerDatabase","bonkHostPlayerDatabase","trapisClientPlayers","bonkHostPlayerHistory","trapisClientGlobalPlayerHistory"];
        for(const k of keys) {
            const data = getJson(k, null);
            if(data && typeof data === "object") return data;
        }
        return (window.bonkHost && window.bonkHost.playerHistory) || {};
    };
    const profileRecord = () => {
        try {
            if(window.bonkHost && window.bonkHost.getMyProfileRecord) return window.bonkHost.getMyProfileRecord();
            if(window.bonkHost && window.bonkHost.clientProfile) return window.bonkHost.clientProfile;
        } catch(e) {}
        return getJson("trapisClientProfile", {}) || {};
    };
    const corrected = (rec) => {
        if(window.bonkHost && window.bonkHost.getCorrectedStatsRecordV86) {
            try { return window.bonkHost.getCorrectedStatsRecordV86(rec || {}); } catch(e) {}
        }
        return rec || {};
    };
    const getMyStats = () => {
        let rec = corrected(profileRecord());
        const buckets = ["quickplay","onevone","team","custom"];
        let games = 0, wins = 0, found = false;
        for(const k of buckets) {
            const b = rec[k] || {};
            if(Number(b.games || 0) || Number(b.wins || 0)) found = true;
            games += Number(b.games || 0);
            wins += Number(b.wins || 0);
        }
        if(!found) {
            games = Number(rec.games || rec.totalGames || (rec.total && rec.total.games) || 0);
            wins = Number(rec.wins || rec.totalWins || (rec.total && rec.total.wins) || 0);
        }
        return { rec, games, wins, wr: pct(wins, games) };
    };
    const xpInfo = () => {
        const rec = profileRecord();
        const level = Number(rec.level || rec.currentLevel || localStorage.getItem("trapisClientLevel") || 1);
        const exact = Number(rec.exactXp || rec.totalXp || localStorage.getItem("trapisClientExactXp") || rec.xp || 0);
        const base = 100 * Math.max(0, level - 1) * Math.max(0, level - 1);
        const next = 100 * Math.max(1, level) * Math.max(1, level);
        const current = Math.max(0, exact - base);
        const need = Math.max(0, next - exact);
        return { level, exact, base, next, current, need, rounds: Math.ceil(need / 100) };
    };
    const makeButton = (label, action, sub) => {
        const b = document.createElement("button");
        b.className = "trapisV87Btn";
        b.innerHTML = `<span>${safe(label)}</span>${sub ? `<small>${safe(sub)}</small>` : ""}`;
        b.addEventListener("click", (e) => {
            e.preventDefault(); e.stopPropagation();
            try { action && action(); } catch(err) { console.warn("Trapi's Client button failed", err); }
        });
        return b;
    };
    const card = (title, html) => `<section class="trapisV87Card"><h3>${safe(title)}</h3>${html}</section>`;
    const statLine = (name, value) => `<div class="trapisV87StatLine"><span>${safe(name)}</span><b>${safe(value)}</b></div>`;
    const topPlayers = (limit=5) => {
        const db = playerDb();
        const arr = Object.entries(db).map(([name, r]) => {
            r = r || {};
            const games = Number(r.gamesTogether || r.games || (r.total && r.total.games) || 0);
            const wins = Number(r.wins || (r.total && r.total.wins) || 0);
            const last = Number(r.lastSeen || r.lastSeenAt || r.lastRoomAt || 0);
            return { name, r, games, wins, wr:pct(wins,games), last };
        }).filter(x => x.name && x.name !== "undefined");
        arr.sort((a,b) => (b.games - a.games) || (b.last - a.last));
        return arr.slice(0, limit);
    };
    const openPopup = (title, nodeOrHtml) => {
        if(window.bonkHost && window.bonkHost.showClientPopup) {
            const wrap = document.createElement("div");
            wrap.className = "trapisV87Page";
            if(typeof nodeOrHtml === "string") wrap.innerHTML = nodeOrHtml;
            else if(nodeOrHtml) wrap.appendChild(nodeOrHtml);
            window.bonkHost.showClientPopup(title, wrap);
        }
    };
    const makeGrid = () => {
        const g = document.createElement("div");
        g.className = "trapisV87Grid";
        return g;
    };
    const action = (label, id, sub) => makeButton(label, () => clickIf(id), sub);
    const advancedTools = () => {
        const g = makeGrid();
        [
            ["Status","hostPlayerMenuStatusButton"],["Version","hostPlayerMenuVersionInfoButton"],
            ["Export","hostPlayerMenuExportSettingsButton"],["Import","hostPlayerMenuImportSettingsButton"],
            ["Database Health","hostPlayerMenuDatabaseHealthButton"],["Patch Notes Mode","hostPlayerMenuPatchNotesCompactButton"]
        ].forEach(([l,id]) => g.appendChild(action(l,id)));
        return g;
    };
    const pages = {
        profile: () => {
            const s = getMyStats(), x = xpInfo();
            const html =
                card("Profile", statLine("Level", x.level) + statLine("Total XP", x.exact.toLocaleString()) + statLine("Total Games", s.games) + statLine("Win Rate", s.wr + "%")) +
                card("XP Progress", bar(x.current, x.next - x.base, "Level " + x.level + " progress") + statLine("XP Until Level Up", x.need.toLocaleString()) + statLine("Round Wins Needed", x.rounds)) +
                card("Career Split", 
                    statLine("Quickplay", ((s.rec.quickplay&&s.rec.quickplay.games)||0) + " games") +
                    statLine("1v1", ((s.rec.onevone&&s.rec.onevone.games)||0) + " games") +
                    statLine("Teams", ((s.rec.team&&s.rec.team.games)||0) + " games") +
                    statLine("Free For All", ((s.rec.custom&&s.rec.custom.games)||0) + " games")) +
                card("Profile Tools", "");
            const wrap = document.createElement("div");
            wrap.innerHTML = html;
            const g = makeGrid();
            g.appendChild(action("My Profile","hostPlayerMenuMyProfileButton"));
            g.appendChild(action("Set Exact XP","hostPlayerMenuSetExactXpButton"));
            g.appendChild(action("XP Cap Status","hostPlayerMenuXpCapButton"));
            g.appendChild(makeButton("Achievements", () => openPopup("ACHIEVEMENTS", card("Progress", statLine("Tracked Players", Object.keys(playerDb()).length) + statLine("Games Played", s.games) + statLine("Career Wins", s.wins) + statLine("Client Level", x.level)))));
            wrap.querySelector(".trapisV87Card:last-child").appendChild(g);
            return wrap;
        },
        database: () => {
            const top = topPlayers(6);
            const html = card("Database Snapshot", statLine("Players Tracked", Object.keys(playerDb()).length) + statLine("Top Contact", top[0] ? top[0].name + " (" + top[0].games + ")" : "None")) +
                card("Most Played With", top.map((p,i)=>`<div class="trapisV87List">${i+1}. ${safe(p.name)} — ${p.games} games — ${p.wr}% WR</div>`).join("") || "<p>No tracked players yet.</p>") +
                card("Database Tools", "");
            const wrap = document.createElement("div");
            wrap.innerHTML = html;
            const g = makeGrid();
            [
                ["Player Lookup","hostPlayerMenuPlayerLookupButton"],["Recent Players","hostPlayerMenuRecentPlayersButton"],
                ["Top Played With","hostPlayerMenuTopPlayedWithButton"],["Top Winrate","hostPlayerMenuTopWinrateButton"],
                ["Rivals","hostPlayerMenuRivalsButton"],["Team Chemistry","hostPlayerMenuTeamChemistryButton"],
                ["Edit Notes","hostPlayerMenuEditPlayerNoteButton"],["Favorite Mode","hostPlayerMenuEditFavoriteModeButton"],
                ["⭐ Trusted","hostPlayerMenuTrustedButton"],["⚠ Problem","hostPlayerMenuProblemButton"],
                ["Link Accounts","hostPlayerMenuLinkAccountsButton"],["Account Links","hostPlayerMenuAccountLinksButton"]
            ].forEach(([l,id]) => g.appendChild(action(l,id)));
            wrap.querySelector(".trapisV87Card:last-child").appendChild(g);
            return wrap;
        },
        social: () => {
            const top = topPlayers(4);
            const html = card("Social Layer", statLine("Known Players", Object.keys(playerDb()).length) + statLine("Recent Regulars", top.map(p=>p.name).join(", ") || "None")) +
                card("Social Tools", "");
            const wrap = document.createElement("div");
            wrap.innerHTML = html;
            const g = makeGrid();
            [
                ["Private Chat","hostPlayerMenuPrivateChatButton","DM compatible players"],
                ["Quick Private Chat","hostPlayerMenuPrivateChatQuickButton"],
                ["Compatible Users","hostPlayerMenuPrivateChatUsersButton"],
                ["Save Room Watch","hostPlayerMenuSaveRoomButton"],
                ["Saved Rooms","hostPlayerMenuSavedRoomsButton"],
                ["Clear Saved Rooms","hostPlayerMenuClearSavedRoomsButton"]
            ].forEach(([l,id,sub]) => g.appendChild(action(l,id,sub)));
            g.appendChild(makeButton("Friends / Favorites", () => openPopup("FRIENDS / FAVORITES", card("Coming Foundation", "Use Trusted, Favorite Mode, and Notes from the Database page for now. A true friend list can build on those tags."))));
            wrap.querySelector(".trapisV87Card:last-child").appendChild(g);
            return wrap;
        },
        room: () => {
            const html = card("Room Control", "Hosting controls, queue behavior, room access, and team movement.") + card("Room Tools", "");
            const wrap = document.createElement("div");
            wrap.innerHTML = html;
            const g = makeGrid();
            [
                ["Start Game","hostPlayerMenuStartShortcutButton"],["Go To Lobby","hostPlayerMenuGoToLobbyButton"],
                ["Team Lock","hostPlayerMenuTeamlock"],["Freejoin","hostPlayerMenuFreejoin"],
                ["Keep Scores","hostPlayerMenuKeepScores"],["Guest AK","hostPlayerMenuAkToggleButton"],
                ["Room Name","hostPlayerMenuRoomNameShortcutButton"],["Room Password","hostPlayerMenuRoomPassShortcutButton"],
                ["Clear Password","hostPlayerMenuClearRoomPassShortcutButton"]
            ].forEach(([l,id]) => g.appendChild(action(l,id)));
            wrap.querySelector(".trapisV87Card:last-child").appendChild(g);
            return wrap;
        },
        match: () => {
            const s = getMyStats();
            const html = card("Match Stats", bar(s.wins, s.games, "Career Win Rate") + statLine("Games", s.games) + statLine("Wins", s.wins)) + card("Match Tools", "");
            const wrap = document.createElement("div");
            wrap.innerHTML = html;
            const g = makeGrid();
            [
                ["Shuffle Teams","hostPlayerMenuShuffleRBButton","Split last winners"],
                ["Auto Balance","hostPlayerMenuAutoBalanceButton"],
                ["Swap Red / Blue","hostPlayerMenuSwapTeamsButton"],
                ["All Spectate","hostPlayerMenuAllSpecButton"],
                ["Captains Mode","hostPlayerMenuCaptainsButton"],
                ["Scoreboard","hostPlayerMenuScoreboardShortcutButton"],
                ["Red/Blue WR","hostPlayerMenuRedBlueWinrate"],
                ["Stats Overview","hostPlayerMenuStatsOverview"]
            ].forEach(([l,id,sub]) => g.appendChild(action(l,id,sub)));
            wrap.querySelector(".trapisV87Card:last-child").appendChild(g);
            return wrap;
        },
        maps: () => {
            const html = card("Maps & Playlists", "Playlist controls, Quickplay tools, and map rotation.") + card("Map Tools", "");
            const wrap = document.createElement("div");
            wrap.innerHTML = html;
            const g = makeGrid();
            [
                ["Open Playlists","hostPlayerMenuOpenPlaylistsButton"],["Quickplay","hostPlayerMenuQuickplayButton"],
                ["Wins/QP","hostPlayerMenuQpRoundsButton"],["Shuffle Queue","hostPlayerMenuQpShuffleButton"],
                ["Previous Map","hostPlayerMenuQpPrevButton"],["Next Map","hostPlayerMenuQpNextButton"],
                ["Map History","hostPlayerMenuMapHistoryStatus"],["Your Wins","hostPlayerMenuQpYourWinsStatus"]
            ].forEach(([l,id]) => g.appendChild(action(l,id)));
            wrap.querySelector(".trapisV87Card:last-child").appendChild(g);
            return wrap;
        },
        settings: () => {
            const html = card("Client Settings", "Daily-use toggles stay here. Rare debug tools moved to Advanced.") + card("Settings", "");
            const wrap = document.createElement("div");
            wrap.innerHTML = html;
            const g = makeGrid();
            [
                ["Auto Team Join","hostPlayerMenuAutoTeamJoinButton"],["Auto Join Next","hostPlayerMenuAutoJoinNextButton"],
                ["Global History","hostPlayerMenuGlobalHistoryButton"],["Career Stats","hostPlayerMenuCareerStatsButton"],
                ["Country Flags","hostPlayerMenuShowFlagsButton"],["Smart Shuffle","hostPlayerMenuSettingsSmartShuffleButton"],
                ["Rejoin Protect","hostPlayerMenuSettingsRejoinProtectionButton"]
            ].forEach(([l,id]) => g.appendChild(action(l,id)));
            g.appendChild(makeButton("Advanced", () => openPopup("ADVANCED", advancedTools())));
            wrap.querySelector(".trapisV87Card:last-child").appendChild(g);
            return wrap;
        },
        guide: () => {
            const wrap = document.createElement("div");
            wrap.innerHTML = card("Guide", "Open a help page, or use the page names as the new mental model: Profile, Database, Social, Room, Match, Maps, Settings.");
            const g = makeGrid();
            [
                ["Welcome","hostPlayerMenuHelpWelcome"],["Team Help","hostPlayerMenuHelpTeamTools"],
                ["Room Help","hostPlayerMenuHelpHostTools"],["Maps Help","hostPlayerMenuHelpPlaylists"],
                ["Stats Help","hostPlayerMenuHelpStats"],["Players Help","hostPlayerMenuHelpPlayers"],
                ["Settings Help","hostPlayerMenuHelpSettings"]
            ].forEach(([l,id]) => g.appendChild(action(l,id)));
            wrap.appendChild(g);
            return wrap;
        },
        changelog: () => {
            const wrap = document.createElement("div");
            wrap.innerHTML = card("Changelog", "Current update: v8.7 polished the client layout, profile cards, progress bars, and page structure.");
            const g = makeGrid();
            [
                ["Current Notes","hostPlayerMenuPatchNotesSummary"],["Compact Toggle","hostPlayerMenuPatchNotesCompactButton"],
                ["Older Notes","hostPlayerMenuPatchNotesV59"],["v5.7","hostPlayerMenuPatchNotesV57"]
            ].forEach(([l,id]) => g.appendChild(action(l,id)));
            wrap.appendChild(g);
            return wrap;
        }
    };
    const keyMap = {
        team: "match",
        room: "match",
        maps: "maps",
        stats: "profile",
        players: "database",
        database: "database",
        social: "social",
        settings: "settings",
        guide: "guide",
        changelog: "changelog"
    };
    const labels = {
        hostPlayerMenuTeamToolsToggle: "LOBBY CONTROLS",
        hostPlayerMenuHostToolsToggle: "ROOM",
        hostPlayerMenuPlaylistsToggle: "MAPS",
        hostPlayerMenuStatsToggle: "PROFILE",
        hostPlayerMenuPlayersToggle: "DATABASE",
        hostPlayerMenuDatabaseToggle: "DATABASE+",
        hostPlayerMenuSocialToggle: "SOCIAL",
        hostPlayerMenuSettingsToggle: "SETTINGS",
        hostPlayerMenuHelpToggle: "GUIDE",
        hostPlayerMenuPatchNotesToggle: "CHANGELOG"
    };
    const dedupeMainButtons = () => {
        const controls = $("hostPlayerMenuControls");
        if(!controls) return;
        Object.entries(labels).forEach(([id,label]) => {
            const el = $(id);
            if(el) {
                if(id === "hostPlayerMenuHostToolsToggle") {
                    el.style.display = "none";
                    el.setAttribute("data-trapis-v901-hidden-room", "true");
                    return;
                }
                el.textContent = label + " ▼";
                el.style.display = "";
            }
        });
        // Player Actions is now merged into Database; hide the old duplicate from the main menu.
        const players = $("hostPlayerMenuPlayersToggle");
        if(players) players.style.display = "none";
        // Keep DATABASE as the visible database page.
        const db = $("hostPlayerMenuDatabaseToggle");
        if(db) db.textContent = "DATABASE ▼";
        const ver = $("hostPlayerMenuClientVersion");
        if(ver) ver.textContent = "v" + V87;
        const title = $("hostPlayerMenuClientTitle");
        if(title) title.textContent = "Trapi's Client";
        ["hostPlayerMenuTeamToolsDropdown","hostPlayerMenuHostToolsDropdown","hostPlayerMenuPlaylistsDropdown","hostPlayerMenuStatsDropdown","hostPlayerMenuPlayersDropdown","hostPlayerMenuDatabaseDropdown","hostPlayerMenuSocialDropdown","hostPlayerMenuSettingsDropdown","hostPlayerMenuHelpDropdown","hostPlayerMenuPatchNotesDropdown"].forEach(id => {
            const el = $(id);
            if(el) el.style.display = "none";
        });
    };
    const routeDef = (def, force) => {
        const pageKey = keyMap[def && def.key] || (def && def.key) || "profile";
        if(!force && window.bonkHost.activeV87Page === pageKey) {
            window.bonkHost.closeClientPopup && window.bonkHost.closeClientPopup();
            window.bonkHost.activeV87Page = null;
            return;
        }
        window.bonkHost.activeV87Page = pageKey;
        const page = pages[pageKey] || pages.profile;
        openPopup((pageKey === "maps" ? "MAPS / PLAYLISTS" : pageKey === "match" ? "LOBBY CONTROLS" : pageKey.toUpperCase()), page());
        if(window.bonkHost.closeAllCategoryDropdownsV86) window.bonkHost.closeAllCategoryDropdownsV86();
    };
    const oldOpen = window.bonkHost.openCategoryPopupV86;
    window.bonkHost.openCategoryPopupV86 = (def, force) => routeDef(def, force);
    window.bonkHost.openV87Page = (pageKey) => {
        window.bonkHost.activeV87Page = pageKey;
        const page = pages[pageKey] || pages.profile;
        openPopup((pageKey === "maps" ? "MAPS / PLAYLISTS" : pageKey === "match" ? "LOBBY CONTROLS" : pageKey.toUpperCase()), page());
    };
    const oldClose = window.bonkHost.closeClientPopup;
    window.bonkHost.closeClientPopup = () => {
        try { oldClose && oldClose(); } catch(e) {}
        window.bonkHost.activeV87Page = null;
    };
    const css = document.createElement("style");
    css.id = "trapisClientV87Css";
    css.textContent = `
        #hostPlayerMenuPlayersToggle { display:none !important; }
        #hostPlayerMenuControls .newbonklobby_settings_button { letter-spacing:.2px; }
        #trapisClientInfoPopup { width:390px !important; max-height:70vh !important; }
        #trapisClientInfoPopupBody { max-height:calc(70vh - 35px) !important; overflow:auto !important; }
        .trapisV87Page { white-space:normal; font-family:futurept_b1,Arial,sans-serif; }
        .trapisV87Card {
            background:rgba(255,255,255,.06);
            border:1px solid rgba(255,255,255,.11);
            border-radius:6px;
            padding:9px;
            margin-bottom:9px;
        }
        .trapisV87Card h3 {
            margin:0 0 7px 0;
            font-size:13px;
            letter-spacing:.4px;
            text-transform:uppercase;
        }
        .trapisV87StatLine {
            display:flex;
            justify-content:space-between;
            gap:10px;
            font-size:12px;
            line-height:18px;
            border-bottom:1px solid rgba(255,255,255,.06);
        }
        .trapisV87StatLine:last-child { border-bottom:0; }
        .trapisV87Grid {
            display:grid;
            grid-template-columns:1fr 1fr;
            gap:7px;
        }
        .trapisV87Btn {
            min-height:34px;
            border:0;
            border-radius:5px;
            background:#8b6f61;
            color:white;
            cursor:pointer;
            font-family:futurept_b1,Arial,sans-serif;
            font-weight:800;
            font-size:11px;
            line-height:13px;
            padding:6px 8px;
            text-transform:uppercase;
            box-shadow:0 2px 4px rgba(0,0,0,.25);
        }
        .trapisV87Btn:hover { filter:brightness(1.1); }
        .trapisV87Btn small {
            display:block;
            opacity:.75;
            font-size:9px;
            line-height:11px;
            margin-top:2px;
            text-transform:none;
        }
        .trapisV87StatBar { margin:7px 0; }
        .trapisV87StatBarLabel {
            display:flex;
            justify-content:space-between;
            font-size:11px;
            margin-bottom:3px;
        }
        .trapisV87BarTrack {
            height:8px;
            border-radius:9px;
            background:rgba(0,0,0,.35);
            overflow:hidden;
        }
        .trapisV87BarFill {
            height:100%;
            background:#009688;
            border-radius:9px;
        }
        .trapisV87List {
            font-size:12px;
            line-height:17px;
            border-bottom:1px solid rgba(255,255,255,.06);
        }
    `;
    document.head.appendChild(css);
    dedupeMainButtons();
    setInterval(() => { try { dedupeMainButtons(); } catch(e) {} }, 1500);
})();


/* ===== Trapi's Client v8.8 - popup action reliability + unified shell theme ===== */
(() => {
    const V88 = "9.0.3";
    const $ = (id) => document.getElementById(id);
    const esc = (value) => {
        const div = document.createElement("div");
        div.textContent = String(value == null ? "" : value);
        return div.innerHTML;
    };
    const pageRoot = () => document.getElementById("pagecontainer") || document.body || document.documentElement;

    const renderPopup = (title, content) => {
        try {
            const old = $("trapisClientInfoPopup");
            if(old) old.remove();
            const box = document.createElement("div");
            box.id = "trapisClientInfoPopup";
            box.className = "trapisV88Popup";
            box.innerHTML =
                `<div id="trapisClientInfoPopupTop" class="trapisV88PopupTop">` +
                `<span>${esc(title || "TRAPI'S CLIENT")}</span>` +
                `<button id="trapisClientInfoPopupClose" class="trapisV88Close" title="Close">×</button>` +
                `</div><div id="trapisClientInfoPopupBody" class="trapisV88PopupBody"></div>`;
            const body = box.querySelector("#trapisClientInfoPopupBody");
            if(content instanceof Node) body.appendChild(content);
            else if(Array.isArray(content)) body.innerHTML = `<pre class="trapisV88Pre">${esc(content.join("\n"))}</pre>`;
            else if(typeof content === "string" && /<[^>]+>/.test(content)) body.innerHTML = content;
            else body.innerHTML = `<pre class="trapisV88Pre">${esc(String(content || ""))}</pre>`;
            pageRoot().appendChild(box);
            const close = $("trapisClientInfoPopupClose");
            if(close) close.onclick = () => { const el=$("trapisClientInfoPopup"); if(el) el.remove(); window.bonkHost.activeV87Page = null; };
            window.bonkHost.activePopupTitleV85 = title || "TRAPI'S CLIENT";
            if(window.bonkHost.positionClientPopupV85) window.bonkHost.positionClientPopupV85();
            else {
                const menu = $("hostPlayerMenu");
                const r = menu && menu.getBoundingClientRect ? menu.getBoundingClientRect() : {right:245, top:60};
                box.style.left = Math.round(r.right + 10) + "px";
                box.style.top = Math.round(Math.max(36, r.top)) + "px";
            }
        } catch(e) {
            console.warn("Trapi's Client popup render failed", e);
        }
    };

    window.bonkHost.showClientPopup = renderPopup;
    window.bonkHost.closeClientPopup = () => {
        const old = $("trapisClientInfoPopup");
        if(old) old.remove();
        window.bonkHost.activeV87Page = null;
        window.bonkHost.activeCategoryPopupV86 = null;
        window.bonkHost.activePopupTitleV85 = null;
    };
    window.bonkHost.showDatabasePanel = (title, lines) => {
        const panel = $("hostPlayerMenuDatabasePanel");
        if(panel) panel.style.display = "none";
        renderPopup(title || "PLAYER DATABASE", Array.isArray(lines) ? lines : [String(lines || "")]);
    };
    window.bonkHost.showHelpMessages = (title, lines) => renderPopup(title || "GUIDE", Array.isArray(lines) ? lines : [String(lines || "")]);

    const line = (label, value) => `<div class="trapisV88Line"><span>${esc(label)}</span><b>${esc(value)}</b></div>`;
    const card = (title, html) => `<section class="trapisV88Card"><h3>${esc(title)}</h3>${html || ""}</section>`;
    const wr = (wins, games) => games ? Math.round((Number(wins||0) / Number(games||0)) * 1000) / 10 : 0;
    const normalize = (rec) => {
        try {
            if(window.bonkHost.getCorrectedStatsRecordV86) return window.bonkHost.getCorrectedStatsRecordV86(rec || {});
            if(window.bonkHost.normalizeStatsBuckets) return window.bonkHost.normalizeStatsBuckets(rec || {});
        } catch(e) {}
        return rec || {};
    };
    const historyDb = () => {
        try { return (window.bonkHost.extraFeatures && window.bonkHost.extraFeatures.playerHistory) || window.bonkHost.playerHistory || {}; }
        catch(e) { return {}; }
    };
    const metaDb = () => {
        try { return (window.bonkHost.extraFeatures && window.bonkHost.extraFeatures.playerMeta) || JSON.parse(localStorage.getItem("trapisClientPlayerMeta") || "{}"); }
        catch(e) { return {}; }
    };
    const splitLines = (rec) => {
        rec = normalize(rec || {});
        return line("Total", `${(rec.total&&rec.total.wins)||rec.wins||0}W / ${(rec.total&&rec.total.games)||rec.games||0}G / ${wr((rec.total&&rec.total.wins)||rec.wins||0,(rec.total&&rec.total.games)||rec.games||0)}%`) +
            line("Quickplay", `${(rec.quickplay&&rec.quickplay.wins)||0}W / ${(rec.quickplay&&rec.quickplay.games)||0}G`) +
            line("1v1", `${(rec.onevone&&rec.onevone.wins)||0}W / ${(rec.onevone&&rec.onevone.games)||0}G`) +
            line("Teams", `${(rec.team&&rec.team.wins)||0}W / ${(rec.team&&rec.team.games)||0}G`) +
            line("Free For All", `${(rec.custom&&rec.custom.wins)||0}W / ${(rec.custom&&rec.custom.games)||0}G`);
    };
    const makePage = (html) => {
        const wrap = document.createElement("div");
        wrap.className = "trapisV88Page";
        wrap.innerHTML = html;
        return wrap;
    };
    const makeGrid = () => {
        const g = document.createElement("div");
        g.className = "trapisV88Grid";
        return g;
    };
    const btn = (label, fn, sub) => {
        const b = document.createElement("button");
        b.className = "trapisV88Btn";
        b.innerHTML = `<span>${esc(label)}</span>${sub ? `<small>${esc(sub)}</small>` : ""}`;
        b.onclick = (e) => { e.preventDefault(); e.stopPropagation(); try { fn && fn(); } catch(err) { renderPopup("ACTION ERROR", [String(err && err.message ? err.message : err)]); } };
        return b;
    };
    const clickLegacy = (id, fallbackTitle) => {
        const el = $(id);
        if(!el) { renderPopup(fallbackTitle || "MISSING TOOL", ["This tool is not available in this build yet."]); return false; }
        try {
            window.bonkHost.allowHiddenActionV86 = true;
            el.click();
            return true;
        } catch(e) {
            renderPopup(fallbackTitle || "ACTION FAILED", [String(e && e.message ? e.message : e)]);
            return false;
        } finally {
            setTimeout(() => { window.bonkHost.allowHiddenActionV86 = false; }, 0);
        }
    };

    const getPlayerRecordByName = (name) => {
        const canon = window.bonkHost.getCanonicalPlayerName ? window.bonkHost.getCanonicalPlayerName(name) : String(name || "").trim();
        let linked = [canon];
        try { if(window.bonkHost.getLinkedNamesFor) linked = window.bonkHost.getLinkedNamesFor(canon); } catch(e) {}
        let hist = {};
        try { hist = window.bonkHost.mergeHistoryRecords ? window.bonkHost.mergeHistoryRecords(linked) : (historyDb()[canon] || {}); } catch(e) { hist = historyDb()[canon] || {}; }
        hist = normalize(hist);
        const meta = metaDb()[canon] || {};
        return { canon, linked, hist, meta };
    };

    window.bonkHost.renderPlayerProfileV88 = (name) => {
        if(!name) return window.bonkHost.showPlayerLookup();
        const { canon, linked, hist, meta } = getPlayerRecordByName(name);
        const lastRoom = hist.lastRoomTitle || meta.lastRoomTitle || hist.lastRoom || meta.lastRoom || "Unknown";
        const tags = [meta.trusted || meta.tags?.Trusted ? "⭐ Trusted" : "", meta.problem || meta.tags?.["Problem Player"] ? "⚠ Problem" : ""].filter(Boolean).join("  ") || "None";
        const html =
            card("Identity", line("Name", canon) + line("Linked", linked.length > 1 ? linked.join(", ") : "None") + line("Tags", tags)) +
            card("Stats With You", splitLines(hist)) +
            card("History", line("Joins", hist.joins || 0) + line("First Seen", hist.firstSeen ? new Date(hist.firstSeen).toLocaleDateString() : "Unknown") + line("Last Seen", hist.lastSeen ? (window.bonkHost.formatTimeAgo ? window.bonkHost.formatTimeAgo(hist.lastSeen) : new Date(hist.lastSeen).toLocaleString()) : "Unknown") + line("Last Room", lastRoom)) +
            card("Notes", `<div class="trapisV88TextBlock">${esc(meta.notes || meta.note || "No notes saved.")}</div>`);
        const wrap = makePage(html);
        const g = makeGrid();
        g.appendChild(btn("Edit Notes", () => { const n = prompt("Notes for " + canon + ":", meta.notes || meta.note || ""); if(n !== null && window.bonkHost.editPlayerNotePrompt) { localStorage.setItem("trapisClientLastLookup", canon); window.bonkHost.editPlayerNotePrompt(canon, n); } else if(n !== null) { const all = metaDb(); all[canon] = all[canon] || {}; all[canon].notes = n; localStorage.setItem("trapisClientPlayerMeta", JSON.stringify(all)); window.bonkHost.renderPlayerProfileV88(canon); } }));
        g.appendChild(btn("Favorite Mode", () => clickLegacy("hostPlayerMenuEditFavoriteModeButton", "FAVORITE MODE")));
        g.appendChild(btn("Toggle Trusted", () => { if(window.bonkHost.togglePlayerTag) { window.bonkHost.togglePlayerTag(canon, "Trusted"); window.bonkHost.renderPlayerProfileV88(canon); } else clickLegacy("hostPlayerMenuTrustedButton", "TRUSTED"); }));
        g.appendChild(btn("Toggle Problem", () => { if(window.bonkHost.togglePlayerTag) { window.bonkHost.togglePlayerTag(canon, "Problem Player"); window.bonkHost.renderPlayerProfileV88(canon); } else clickLegacy("hostPlayerMenuProblemButton", "PROBLEM PLAYER"); }));
        wrap.appendChild(card("Actions", ""));
        wrap.querySelector(".trapisV88Card:last-child").appendChild(g);
        renderPopup("PLAYER PROFILE", wrap);
    };

    window.bonkHost.showPlayerLookup = () => {
        let suggested = "";
        try { suggested = ((window.bonkHost.players || []).filter(p=>p&&p.userName).map(p=>p.userName)[0]) || ""; } catch(e) {}
        const name = prompt("Player name to look up:", suggested);
        if(name) window.bonkHost.renderPlayerProfileV88(name.trim());
    };
    window.bonkHost.showPlayerProfile = window.bonkHost.renderPlayerProfileV88;
    window.bonkHost.showPlayerCard = window.bonkHost.showPlayerLookup;

    window.bonkHost.showMyProfileV88 = () => {
        let xp = {};
        try { xp = window.bonkHost.getBonkXpProfile ? window.bonkHost.getBonkXpProfile() : {}; } catch(e) {}
        let career = {};
        try { career = window.bonkHost.getCareerStatsRecord ? window.bonkHost.getCareerStatsRecord(window.bonkHost.getCanonicalPlayerName(xp.displayName || "me")) : {}; } catch(e) {}
        career = normalize(career);
        const totalGames = (career.total && career.total.games) || career.games || 0;
        const totalWins = (career.total && career.total.wins) || career.wins || 0;
        const base = Number(xp.baseXp || 0), next = Number(xp.nextXp || 0), total = Number(xp.totalXp || 0);
        const denom = Math.max(1, next - base);
        const pc = Math.max(0, Math.min(100, ((total - base) / denom) * 100));
        const html =
            card("Profile", line("Name", xp.displayName || "Unknown") + line("Level", xp.level || "?") + line("Total XP", Number(xp.totalXp || 0).toLocaleString()) + line("Total Games", totalGames) + line("Win Rate", wr(totalWins,totalGames) + "%")) +
            card("XP Progress", `<div class="trapisV88Bar"><div style="width:${pc}%"></div></div>` + line("XP Until Next", Number(xp.remaining || 0).toLocaleString()) + line("Rounds Needed", xp.roundWinsUntilNext || 0) + line("XP Capped", xp.xpCap && xp.xpCap.capped ? "YES" : "NO")) +
            card("Career Split", splitLines(career));
        const wrap = makePage(html);
        const g = makeGrid();
        g.appendChild(btn("Set Exact XP", () => clickLegacy("hostPlayerMenuSetExactXpButton", "SET EXACT XP")));
        g.appendChild(btn("XP Cap Status", () => clickLegacy("hostPlayerMenuXpCapButton", "XP CAP STATUS")));
        wrap.appendChild(card("Profile Tools", ""));
        wrap.querySelector(".trapisV88Card:last-child").appendChild(g);
        renderPopup("PROFILE", wrap);
    };
    window.bonkHost.showMyProfile = window.bonkHost.showMyProfileV88;
    window.bonkHost.showMyCareerStats = window.bonkHost.showMyProfileV88;

    const topRows = (sorter, minGames=0, limit=10) => {
        return Object.entries(historyDb()).map(([name, rec]) => ({ name, rec: normalize(rec || {}) }))
            .map(x => ({...x, games: (x.rec.total&&x.rec.total.games)||x.rec.games||0, wins:(x.rec.total&&x.rec.total.wins)||x.rec.wins||0 }))
            .filter(x => x.name && x.name !== "undefined" && x.games >= minGames)
            .sort(sorter).slice(0, limit);
    };
    window.bonkHost.showTopPlayedWith = () => renderPopup("TOP PLAYED WITH", topRows((a,b)=>b.games-a.games,0,12).map((x,i)=>`${i+1}. ${x.name} — ${x.games} games — ${wr(x.wins,x.games)}% WR`));
    window.bonkHost.showTopWinrate = () => renderPopup("TOP WINRATE", topRows((a,b)=>wr(b.wins,b.games)-wr(a.wins,a.games),3,12).map((x,i)=>`${i+1}. ${x.name} — ${wr(x.wins,x.games)}% WR — ${x.games} games`));
    window.bonkHost.showRivals = () => renderPopup("RIVALS", topRows((a,b)=>b.games-a.games,3,12).map((x,i)=>`${i+1}. ${x.name} — ${x.wins}W/${x.games}G — ${wr(x.wins,x.games)}%`));

    window.bonkHost.openV88Page = (page) => {
        const gridPage = (title, desc, buttons) => {
            const wrap = makePage(card(title, `<div class="trapisV88TextBlock">${esc(desc || "")}</div>`) + card("Tools", ""));
            const g = makeGrid();
            buttons.forEach(([label, fn, sub]) => g.appendChild(btn(label, fn, sub)));
            wrap.querySelector(".trapisV88Card:last-child").appendChild(g);
            renderPopup(title, wrap);
        };
        if(page === "profile") return window.bonkHost.showMyProfileV88();
        if(page === "database") return gridPage("DATABASE", "Player history, notes, tags, rivals, and chemistry.", [
            ["Player Lookup", () => window.bonkHost.showPlayerLookup()], ["Recent Players", () => clickLegacy("hostPlayerMenuRecentPlayersButton", "RECENT PLAYERS")],
            ["Top Played With", () => window.bonkHost.showTopPlayedWith()], ["Top Winrate", () => window.bonkHost.showTopWinrate()],
            ["Rivals", () => window.bonkHost.showRivals()], ["Team Chemistry", () => clickLegacy("hostPlayerMenuTeamChemistryButton", "TEAM CHEMISTRY")],
            ["Link Accounts", () => clickLegacy("hostPlayerMenuLinkAccountsButton", "LINK ACCOUNTS")], ["Account Links", () => clickLegacy("hostPlayerMenuAccountLinksButton", "ACCOUNT LINKS")]
        ]);
        if(page === "match") return gridPage("LOBBY CONTROLS", "Team setup, room flow, and hosting controls.", [
            ["Shuffle Teams", () => clickLegacy("hostPlayerMenuShuffleRBButton", "SHUFFLE TEAMS"), "splits last winners"], ["Auto Balance", () => clickLegacy("hostPlayerMenuAutoBalanceButton", "AUTO BALANCE")],
            ["Swap R/B", () => clickLegacy("hostPlayerMenuSwapTeamsButton", "SWAP R/B")], ["All Spec", () => clickLegacy("hostPlayerMenuAllSpecButton", "ALL SPEC")],
            ["Captains", () => clickLegacy("hostPlayerMenuCaptainsButton", "CAPTAINS")], ["Freejoin", () => clickLegacy("hostPlayerMenuFreejoin", "FREEJOIN")],
            ["Team Lock", () => clickLegacy("hostPlayerMenuTeamlock", "TEAM LOCK")], ["Guest Autokick", () => clickLegacy("hostPlayerMenuAkToggleButton", "GUEST AUTOKICK")]
        ]);
        if(page === "room") return gridPage("ROOM", "Hosting, access, and room command shortcuts.", [
            ["Start Game", () => clickLegacy("hostPlayerMenuStartShortcutButton", "START")], ["Go To Lobby", () => clickLegacy("hostPlayerMenuGoToLobbyButton", "GO TO LOBBY")],
            ["Team Lock", () => clickLegacy("hostPlayerMenuTeamlock", "TEAM LOCK")], ["Freejoin", () => clickLegacy("hostPlayerMenuFreejoin", "FREEJOIN")],
            ["Keep Scores", () => clickLegacy("hostPlayerMenuKeepScores", "KEEP SCORES")], ["Guest AK", () => clickLegacy("hostPlayerMenuAkToggleButton", "GUEST AK")]
        ]);
        if(page === "maps") return gridPage("MAPS / PLAYLISTS", "Playlist and quickplay controls.", [
            ["Open Playlists", () => clickLegacy("hostPlayerMenuOpenPlaylistsButton", "PLAYLISTS")], ["Quickplay", () => clickLegacy("hostPlayerMenuQuickplayButton", "QUICKPLAY")],
            ["Wins/QP", () => clickLegacy("hostPlayerMenuQpRoundsButton", "WINS/QP")], ["Shuffle Queue", () => clickLegacy("hostPlayerMenuQpShuffleButton", "SHUFFLE QUEUE")],
            ["Prev Map", () => clickLegacy("hostPlayerMenuQpPrevButton", "PREV MAP")], ["Next Map", () => clickLegacy("hostPlayerMenuQpNextButton", "NEXT MAP")]
        ]);
        if(page === "social") return gridPage("SOCIAL", "Private chat, compatible users, and saved room watch.", [
            ["Private Chat", () => clickLegacy("hostPlayerMenuPrivateChatButton", "PRIVATE CHAT")], ["Quick PM", () => clickLegacy("hostPlayerMenuPrivateChatQuickButton", "QUICK PM")],
            ["PM Users", () => clickLegacy("hostPlayerMenuPrivateChatUsersButton", "PM USERS")], ["Save Room", () => clickLegacy("hostPlayerMenuSaveRoomButton", "SAVE ROOM")],
            ["Saved Rooms", () => clickLegacy("hostPlayerMenuSavedRoomsButton", "SAVED ROOMS")], ["Clear Saved", () => clickLegacy("hostPlayerMenuClearSavedRoomsButton", "CLEAR SAVED")]
        ]);
        if(page === "settings") return gridPage("SETTINGS", "Common toggles and advanced tools.", [
            ["Auto Team Join", () => clickLegacy("hostPlayerMenuAutoTeamJoinButton", "AUTO TEAM JOIN")], ["Auto Join Next", () => clickLegacy("hostPlayerMenuAutoJoinNextButton", "AUTO JOIN NEXT")],
            ["Global History", () => clickLegacy("hostPlayerMenuGlobalHistoryButton", "GLOBAL HISTORY")], ["Career Stats", () => clickLegacy("hostPlayerMenuCareerStatsButton", "CAREER STATS")],
            ["Country Flags", () => clickLegacy("hostPlayerMenuShowFlagsButton", "COUNTRY FLAGS")], ["Export", () => clickLegacy("hostPlayerMenuExportSettingsButton", "EXPORT")],
            ["Import", () => clickLegacy("hostPlayerMenuImportSettingsButton", "IMPORT")], ["Status", () => clickLegacy("hostPlayerMenuStatusButton", "STATUS")]
        ]);
        if(page === "guide") return clickLegacy("hostPlayerMenuHelpWelcome", "GUIDE");
        if(page === "changelog") return clickLegacy("hostPlayerMenuPatchNotesSummary", "CHANGELOG");
    };

    const route = { team:"match", stats:"profile", players:"database", database:"database", room:"match", maps:"maps", social:"social", settings:"settings", guide:"guide", changelog:"changelog" };
    const oldOpen = window.bonkHost.openCategoryPopupV86;
    window.bonkHost.openCategoryPopupV86 = (def, force) => {
        const key = route[def && def.key] || (def && def.key) || "profile";
        window.bonkHost.openV88Page(key);
    };

    const css = document.createElement("style");
    css.id = "trapisClientV88Css";
    css.textContent = `
        #hostPlayerMenu {
            background:#2f2927 !important;
            border:2px solid #6f4f42 !important;
            color:#fff !important;
            border-radius:8px !important;
            box-shadow:0 8px 20px rgba(0,0,0,.35) !important;
            overflow:visible !important;
        }
        #hostPlayerMenu .newbonklobby_boxtop {
            background:#6f4f42 !important;
            border-radius:5px 5px 0 0 !important;
            color:#fff !important;
        }
        #hostPlayerMenuClientTitle { font-size:15px !important; letter-spacing:.2px !important; }
        #hostPlayerMenuClientVersion { color:rgba(255,255,255,.8) !important; }
        #hostPlayerMenuControls .newbonklobby_settings_button,
        #hostPlayerMenuMainToggle,
        #hostPlayerMenuRestartButton,
        #hostPlayerMenuGoToLobbyButton,
        #hostPlayerMenuCollapseMenuButton {
            background:#6f4f42 !important;
            color:#fff !important;
            border:0 !important;
            border-top:1px solid rgba(255,255,255,.08) !important;
            box-shadow:none !important;
            font-family:futurept_b1,Arial,sans-serif !important;
            font-size:14px !important;
            font-weight:900 !important;
            letter-spacing:.4px !important;
        }
        #hostPlayerMenuControls .newbonklobby_settings_button:hover,
        #hostPlayerMenuMainToggle:hover { filter:brightness(1.1) !important; }
        #hostPlayerMenuMainDropdown { background:#2f2927 !important; }
        #hostPlayerMenuBox { background:#2f2927 !important; color:#fff !important; }
        #hostPlayerMenuCloseButton,#hostPlayerMenuGrab,#hostPlayerMenuCollapse {
            background:#8b6f61 !important;
            color:#fff !important;
            border:0 !important;
        }
        #hostPlayerMenuLauncher {
            background:#6f4f42 !important;
            color:#fff !important;
            border:2px solid #8b6f61 !important;
            box-shadow:0 8px 20px rgba(0,0,0,.35) !important;
        }
        #trapisClientInfoPopup.trapisV88Popup,
        #trapisClientInfoPopup {
            background:#2f2927 !important;
            border:2px solid #6f4f42 !important;
            color:#fff !important;
            border-radius:8px !important;
            box-shadow:0 8px 20px rgba(0,0,0,.35) !important;
            width:390px !important;
            max-height:72vh !important;
            overflow:hidden !important;
            font-family:futurept_b1,Arial,sans-serif !important;
        }
        #trapisClientInfoPopupTop { background:#6f4f42 !important; color:#fff !important; height:32px !important; }
        #trapisClientInfoPopupBody { max-height:calc(72vh - 36px) !important; overflow:auto !important; padding:10px !important; }
        .trapisV88Pre { white-space:pre-wrap; margin:0; font-family:futurept_b1,Arial,sans-serif; font-size:12px; line-height:16px; }
        .trapisV88Page { white-space:normal; }
        .trapisV88Card {
            background:rgba(255,255,255,.06);
            border:1px solid rgba(255,255,255,.11);
            border-radius:6px;
            padding:9px;
            margin-bottom:9px;
        }
        .trapisV88Card h3 { margin:0 0 7px 0; font-size:13px; text-transform:uppercase; letter-spacing:.4px; }
        .trapisV88Line { display:flex; justify-content:space-between; gap:10px; font-size:12px; line-height:18px; border-bottom:1px solid rgba(255,255,255,.06); }
        .trapisV88Line:last-child { border-bottom:0; }
        .trapisV88TextBlock { font-size:12px; line-height:16px; opacity:.9; }
        .trapisV88Grid { display:grid; grid-template-columns:1fr 1fr; gap:7px; }
        .trapisV88Btn {
            min-height:34px; border:0; border-radius:5px; background:#8b6f61; color:#fff; cursor:pointer;
            font-family:futurept_b1,Arial,sans-serif; font-size:11px; font-weight:900; line-height:13px; padding:6px 8px;
            text-transform:uppercase; box-shadow:0 2px 4px rgba(0,0,0,.25);
        }
        .trapisV88Btn:hover { filter:brightness(1.1); }
        .trapisV88Btn small { display:block; opacity:.75; font-size:9px; line-height:11px; text-transform:none; margin-top:2px; }
        .trapisV88Bar { height:9px; border-radius:9px; overflow:hidden; background:rgba(0,0,0,.35); margin:6px 0 8px; }
        .trapisV88Bar > div { height:100%; background:#009688; border-radius:9px; }
    `;
    document.head.appendChild(css);
    const ver = $("hostPlayerMenuClientVersion"); if(ver) ver.textContent = "v" + V88;
})();


/* ===== Trapi's Client v8.9 - Match Settings popup + fair shuffle refinement ===== */
(function(){
    try {
        const $ = (id) => document.getElementById(id);
        const safe = (v) => String(v == null ? "" : v).replace(/[&<>"]/g, m => ({"&":"&amp;","<":"&lt;",">":"&gt;","\"":"&quot;"}[m]));
        const isOn = (id) => !!($(id) && ($(id).checked || /:\s*ON/i.test($(id).textContent || "")));
        const clickLegacy = (id, fallbackTitle) => {
            const el = $(id);
            if(!el) { window.bonkHost.showClientPopup && window.bonkHost.showClientPopup(fallbackTitle || "MISSING TOOL", ["This tool is not available in this build yet."]); return false; }
            try { el.click(); return true; }
            catch(e) { window.bonkHost.showClientPopup && window.bonkHost.showClientPopup(fallbackTitle || "ACTION FAILED", [String(e && e.message ? e.message : e)]); return false; }
        };
        const stateText = (on) => on ? "ON" : "OFF";
        const getToggleState = (key) => {
            const ex = window.bonkHost.extraFeatures || {};
            if(key === "teamFreejoin") return !!ex.autoNewJoinBalance;
            if(key === "freejoin") return !!window.bonkHost.freejoin;
            if(key === "captains") return !!ex.captainsMode;
            if(key === "guestAk") return !!window.bonkHost.autoKickGuests;
            if(key === "teamlock") return isOn("hostPlayerMenuTeamlock");
            if(key === "keepScores") return isOn("hostPlayerMenuKeepScores");
            if(key === "keepPositions") return isOn("hostPlayerMenuKeepPositions");
            if(key === "cheatDetection") return !!window.bonkHost.cheatDetection;
            return false;
        };
        const toggleAction = (key) => {
            if(key === "teamFreejoin") {
                window.bonkHost.extraFeatures.autoNewJoinBalance = !window.bonkHost.extraFeatures.autoNewJoinBalance;
                window.bonkHost.updateExtraFeatureButtons && window.bonkHost.updateExtraFeatureButtons();
                return;
            }
            if(key === "freejoin") return clickLegacy("hostPlayerMenuFreejoin", "FREEJOIN");
            if(key === "captains") return clickLegacy("hostPlayerMenuCaptainsButton", "CAPTAINS MODE");
            if(key === "guestAk") return clickLegacy("hostPlayerMenuAkToggleButton", "GUEST AUTOKICK");
            if(key === "teamlock") return clickLegacy("hostPlayerMenuTeamlock", "TEAM LOCK");
            if(key === "keepScores") return clickLegacy("hostPlayerMenuKeepScores", "KEEP SCORES");
            if(key === "keepPositions") return clickLegacy("hostPlayerMenuKeepPositions", "KEEP POSITIONS");
            if(key === "cheatDetection") return clickLegacy("hostPlayerMenuCheatDetectionCheckbox", "CHEAT DETECTION");
        };
        const makeButton = (label, desc, fn, opts={}) => {
            const b = document.createElement("button");
            b.type = "button";
            b.className = "trapisV89ToolBtn" + (opts.toggle ? " trapisV89Toggle" : "") + (opts.on ? " trapisV89On" : "");
            b.innerHTML = `<span>${safe(label)}${opts.toggle ? ` <b>${stateText(opts.on)}</b>` : ""}</span>${desc ? `<small>${safe(desc)}</small>` : ""}`;
            b.onclick = (e) => {
                e.preventDefault(); e.stopPropagation();
                try { fn && fn(); } catch(err) { console.warn("Trapi's Client v8.9 tool failed", err); }
                if(opts.refresh !== false) setTimeout(() => window.bonkHost.openMatchSettingsV89 && window.bonkHost.openMatchSettingsV89(true), 60);
            };
            return b;
        };
        const section = (title, desc, buttons) => {
            const card = document.createElement("section");
            card.className = "trapisV89Card";
            card.innerHTML = `<h3>${safe(title)}</h3>${desc ? `<p>${safe(desc)}</p>` : ""}`;
            const grid = document.createElement("div");
            grid.className = "trapisV89Grid";
            buttons.forEach(x => grid.appendChild(x));
            card.appendChild(grid);
            return card;
        };
        window.bonkHost.openMatchSettingsV89 = (force=false) => {
            const wrap = document.createElement("div");
            wrap.className = "trapisV89Page";
            wrap.appendChild(section("Team Setup", "Shuffle and balance teams without cluttering the left menu.", [
                makeButton("Shuffle Teams", "Splits last winners first, then balances by winrate and recent teammates.", () => window.bonkHost.shuffleRedBlue && window.bonkHost.shuffleRedBlue(), {refresh:false}),
                makeButton("Auto Balance", "Teams Freejoin", () => toggleAction("teamFreejoin"), {toggle:true, on:getToggleState("teamFreejoin")}),
                makeButton("Freejoin", "Normal FFA/freejoin toggle", () => toggleAction("freejoin"), {toggle:true, on:getToggleState("freejoin")}),
                makeButton("Captains Mode", "Captain setup toggle", () => toggleAction("captains"), {toggle:true, on:getToggleState("captains")}),
                makeButton("Swap R/B Pos.", "Swap red and blue player positions", () => clickLegacy("hostPlayerMenuSwapTeamsButton", "SWAP R/B POS."), {refresh:false}),
                makeButton("All Spec", "Move active players to spectator", () => clickLegacy("hostPlayerMenuAllSpecButton", "ALL SPEC"), {refresh:false})
            ]));
            wrap.appendChild(section("Room Controls", "Room-access and hosting controls moved here so Room and Match are one clean page.", [
                makeButton("Team Lock", "Lock team switching", () => toggleAction("teamlock"), {toggle:true, on:getToggleState("teamlock")}),
                makeButton("Keep Scores", "Keep scores through restarts", () => toggleAction("keepScores"), {toggle:true, on:getToggleState("keepScores")}),
                makeButton("Keep Positions", "Respawning may be required", () => toggleAction("keepPositions"), {toggle:true, on:getToggleState("keepPositions")}),
                makeButton("Cheat Detection", "Lag graph / suspicious lag monitor", () => toggleAction("cheatDetection"), {toggle:true, on:getToggleState("cheatDetection")}),
                makeButton("Guest Autokick", "Auto-kick guest accounts", () => toggleAction("guestAk"), {toggle:true, on:getToggleState("guestAk")}),
                makeButton("Start Game", "Start the match", () => clickLegacy("hostPlayerMenuStartShortcutButton", "START GAME"), {refresh:false}),
                makeButton("Return To Bonk Menu", "Leave the current game/room and return to Bonk menu", () => (window.bonkHost.returnToBonkMenu ? window.bonkHost.returnToBonkMenu() : clickLegacy("hostPlayerMenuGoToLobbyButton", "GO TO LOBBY")), {refresh:false}),
                makeButton("Room Name", "Set room title", () => clickLegacy("hostPlayerMenuRoomNameShortcutButton", "ROOM NAME"), {refresh:false}),
                makeButton("Room Password", "Set room password", () => clickLegacy("hostPlayerMenuRoomPassShortcutButton", "ROOM PASSWORD"), {refresh:false}),
                makeButton("Clear Password", "Remove room password", () => clickLegacy("hostPlayerMenuClearRoomPassShortcutButton", "CLEAR PASSWORD"), {refresh:false})
            ]));
            window.bonkHost.showClientPopup && window.bonkHost.showClientPopup("MATCH SETTINGS", wrap);
            window.bonkHost.activeV87Page = "match";
        };
        // Override the current popup router: Match and old Room both go to Match Settings.
        const oldOpen88 = window.bonkHost.openV88Page;
        window.bonkHost.openV88Page = (page) => {
            if(page === "match" || page === "room" || page === "team") return window.bonkHost.openMatchSettingsV89(true);
            return oldOpen88 ? oldOpen88(page) : null;
        };
        window.bonkHost.openV87Page = window.bonkHost.openV88Page;
        const oldCat = window.bonkHost.openCategoryPopupV86;
        window.bonkHost.openCategoryPopupV86 = (def, force) => {
            const key = def && def.key;
            if(key === "team" || key === "match" || key === "room") return window.bonkHost.openMatchSettingsV89(true);
            if(key === "stats") return window.bonkHost.openV88Page("profile");
            if(key === "players") return window.bonkHost.openV88Page("database");
            if(key === "database") return window.bonkHost.openV88Page("database");
            if(key === "maps") return window.bonkHost.openV88Page("maps");
            if(key === "social") return window.bonkHost.openV88Page("social");
            if(key === "settings") return window.bonkHost.openV88Page("settings");
            if(key === "guide") return window.bonkHost.openV88Page("guide");
            if(key === "changelog") return window.bonkHost.openV88Page("changelog");
            return oldCat ? oldCat(def, force) : null;
        };
        // Strong/fair shuffle override. Splits last winners, then avoids rebuilding the same team and balances by local WR.
        window.bonkHost.getPlayerFairScoreV89 = (id) => {
            const name = window.bonkHost.getPlayerNameById ? window.bonkHost.getPlayerNameById(id) : ((window.bonkHost.players[id] || {}).userName || "");
            const canon = window.bonkHost.getCanonicalPlayerName ? window.bonkHost.getCanonicalPlayerName(name) : name;
            const ex = window.bonkHost.extraFeatures || {};
            const sessionWins = (((ex.stats || {}).wins || {})[name] || 0);
            const rec = (ex.playerHistory || {})[canon] || {};
            const total = rec.total || {};
            const team = rec.team || {};
            const totalWR = total.games ? total.wins / total.games : 0;
            const teamWR = team.games ? team.wins / team.games : 0;
            return 1 + sessionWins + (totalWR * 4) + (teamWR * 3) + Math.min((total.games || 0) / 25, 4);
        };
        window.bonkHost.shuffleRedBlue = () => {
            try {
                if(typeof isHost === "function" && !isHost()) return;
                const players = window.bonkHost.players || [];
                const active = window.bonkHost.getActivePlayerIDs ? window.bonkHost.getActivePlayerIDs() : players.map((p,i)=>p && p.team !== 0 ? i : null).filter(x=>x!==null);
                if(active.length < 2) return;
                const ex = window.bonkHost.extraFeatures || {};
                const lastWinners = (ex.lastWinningPlayers || []).map(x => String(x).toLowerCase());
                const lastTeamById = {};
                active.forEach(id => { lastTeamById[id] = players[id] ? players[id].team : null; });
                const ordered = active.slice().sort((a,b) => {
                    const an = String(window.bonkHost.getPlayerNameById ? window.bonkHost.getPlayerNameById(a) : "").toLowerCase();
                    const bn = String(window.bonkHost.getPlayerNameById ? window.bonkHost.getPlayerNameById(b) : "").toLowerCase();
                    const aw = lastWinners.includes(an) ? 1 : 0;
                    const bw = lastWinners.includes(bn) ? 1 : 0;
                    if(aw !== bw) return bw - aw;
                    return window.bonkHost.getPlayerFairScoreV89(b) - window.bonkHost.getPlayerFairScoreV89(a);
                });
                const red=[], blue=[];
                let redScore=0, blueScore=0;
                const add = (id, teamArr, teamNum) => {
                    teamArr.push(id);
                    if(teamNum === 2) redScore += window.bonkHost.getPlayerFairScoreV89(id);
                    else blueScore += window.bonkHost.getPlayerFairScoreV89(id);
                };
                ordered.forEach((id, idx) => {
                    const name = String(window.bonkHost.getPlayerNameById ? window.bonkHost.getPlayerNameById(id) : "").toLowerCase();
                    const isWinner = lastWinners.includes(name);
                    let preferRed;
                    if(isWinner) preferRed = idx % 2 === 0;
                    else {
                        const lastTeam = lastTeamById[id];
                        const redSame = red.filter(pid => lastTeamById[pid] === lastTeam).length;
                        const blueSame = blue.filter(pid => lastTeamById[pid] === lastTeam).length;
                        preferRed = redSame < blueSame ? true : blueSame < redSame ? false : redScore <= blueScore;
                    }
                    if(red.length > blue.length) preferRed = false;
                    if(blue.length > red.length) preferRed = true;
                    preferRed ? add(id, red, 2) : add(id, blue, 3);
                });
                red.forEach(id => { try { window.bonkHost.toolFunctions.networkEngine.changeOtherTeam(id, 2); } catch(e) {} });
                blue.forEach(id => { try { window.bonkHost.toolFunctions.networkEngine.changeOtherTeam(id, 3); } catch(e) {} });
                if(window.bonkHost.clientStatus) window.bonkHost.clientStatus("Shuffled teams: winners split first, then balanced by winrate/recent teams.");
            } catch(e) { console.warn("Trapi's Client v8.9 shuffle failed", e); }
        };
        window.bonkHost.balanceBySessionWinrate = window.bonkHost.shuffleRedBlue;
        // Labels and visual polish.
        const relabel = () => {
            const v = $("hostPlayerMenuClientVersion"); if(v) v.textContent = "v9.0.5";
            const ak = $("hostPlayerMenuAkToggleButton"); if(ak) ak.textContent = "GUEST AUTOKICK: " + (window.bonkHost.autoKickGuests ? "ON" : "OFF");
            const swap = $("hostPlayerMenuSwapTeamsButton"); if(swap) swap.textContent = "SWAP R/B POS.";
            const auto = $("hostPlayerMenuAutoBalanceButton"); if(auto) auto.textContent = "AUTO BALANCE: " + stateText(getToggleState("teamFreejoin"));
            const cap = $("hostPlayerMenuCaptainsButton"); if(cap) cap.textContent = "CAPTAINS MODE: " + stateText(getToggleState("captains"));
        };
        relabel();
        setInterval(relabel, 1000);
        if(!$("trapisClientV89Css")) {
            const st = document.createElement("style");
            st.id = "trapisClientV89Css";
            st.textContent = `
                .trapisV89Page { font-family:futurept_b1,Arial,sans-serif; color:#fff; white-space:normal; }
                .trapisV89Card { background:#352f2c; border:1px solid rgba(255,255,255,.12); border-radius:7px; padding:9px; margin-bottom:9px; }
                .trapisV89Card h3 { margin:0 0 3px; font-size:14px; letter-spacing:.3px; text-transform:uppercase; }
                .trapisV89Card p { margin:0 0 8px; opacity:.82; font-size:11px; line-height:14px; }
                .trapisV89Grid { display:grid; grid-template-columns:1fr 1fr; gap:7px; }
                .trapisV89ToolBtn { min-height:42px; border:0; border-radius:5px; background:#967567; color:#fff; font-family:futurept_b1,Arial,sans-serif; font-weight:800; cursor:pointer; padding:6px 7px; text-align:center; box-shadow:0 2px 0 rgba(0,0,0,.22); }
                .trapisV89ToolBtn:hover { filter:brightness(1.08); }
                .trapisV89ToolBtn span { display:block; font-size:12px; line-height:13px; text-transform:uppercase; }
                .trapisV89ToolBtn small { display:block; margin-top:2px; font-size:9.5px; line-height:11px; opacity:.82; font-weight:600; text-transform:none; }
                .trapisV89ToolBtn b { font-size:10px; opacity:.92; }
                .trapisV89ToolBtn.trapisV89On { background:#7f9a72; }
            `;
            document.head.appendChild(st);
        }
    } catch(e) { console.warn("Trapi's Client v8.9 match settings patch failed", e); }
})();


/* ===== Trapi's Client v9.0.12 - My Profile polish inside injected game context ===== */
(function(){
    const VERSION = "v9.0.12";
    const CARD_ID = "trapisMyProfileCardV910";
    const STYLE_ID = "trapisMyProfileStyleV910";
    const $ = (id) => document.getElementById(id);
    const safe = (value) => String(value == null ? "" : value).replace(/[&<>"']/g, ch => ({"&":"&amp;","<":"&lt;",">":"&gt;","\"":"&quot;","'":"&#39;"}[ch]));
    const num = (value) => { const parsed = Number(value); return Number.isFinite(parsed) ? parsed : 0; };
    const fmt = (value) => Math.round(num(value)).toLocaleString();
    const pct = (wins, games) => num(games) > 0 ? ((num(wins) / num(games)) * 100).toFixed(1).replace(/\.0$/, "") + "%" : "0%";
    const SESSION_XP_BASELINE_KEY = "trapisMyProfileSessionXpBaselineV912";
    function fmtDate(value){
        const d = value ? new Date(value) : null;
        if(!d || Number.isNaN(d.getTime())) return "Unknown";
        const mm = String(d.getMonth() + 1).padStart(2, "0");
        const dd = String(d.getDate()).padStart(2, "0");
        const yyyy = d.getFullYear();
        return `${mm}/${dd}/${yyyy}`;
    }
    function getSessionXpGained(xp){
        const current = num(xp && xp.totalXp);
        let baseline = Number(sessionStorage.getItem(SESSION_XP_BASELINE_KEY));
        if(!Number.isFinite(baseline) || baseline <= 0 || baseline > current){
            baseline = current;
            try { sessionStorage.setItem(SESSION_XP_BASELINE_KEY, String(baseline)); } catch(e) {}
        }
        return Math.max(0, current - baseline);
    }

    function addStyle(){
        if($(STYLE_ID) || !document.head) return;
        const style = document.createElement("style");
        style.id = STYLE_ID;
        style.textContent = `
            #${CARD_ID}{position:absolute;left:245px;top:60px;width:360px;max-height:560px;overflow-y:auto;background:#2f2927;color:#fff;border:2px solid #6f4f42;border-radius:8px;box-shadow:0 4px 18px rgba(0,0,0,.48);z-index:2147483647;font-family:futurept_b1,Arial,sans-serif;display:none;white-space:normal;}
            #${CARD_ID} .mpTop{background:#6f4f42;padding:8px 38px 8px 10px;font-size:17px;font-weight:900;text-transform:uppercase;position:relative;letter-spacing:.3px;}
            #${CARD_ID} .mpClose{position:absolute;right:6px;top:4px;width:25px;height:25px;line-height:25px;text-align:center;background:#8b6f61;border-radius:4px;cursor:pointer;user-select:none;}
            #${CARD_ID} .mpBody{padding:10px;}
            #${CARD_ID} .mpName{font-size:23px;line-height:25px;font-weight:900;margin-bottom:3px;}
            #${CARD_ID} .mpSub{font-size:11px;opacity:.82;margin-bottom:9px;}
            #${CARD_ID} .mpGrid{display:grid;grid-template-columns:1fr 1fr;gap:7px;margin-bottom:9px;}
            #${CARD_ID} .mpTile,#${CARD_ID} .mpSection{background:#403733;border:1px solid rgba(255,255,255,.09);border-radius:6px;padding:7px;box-sizing:border-box;}
            #${CARD_ID} .mpLabel,#${CARD_ID} .mpTitle{font-size:10px;opacity:.78;text-transform:uppercase;letter-spacing:.3px;margin-bottom:2px;}
            #${CARD_ID} .mpValue{font-size:19px;font-weight:900;line-height:21px;}
            #${CARD_ID} .mpRow{display:flex;justify-content:space-between;gap:8px;border-top:1px solid rgba(255,255,255,.08);padding:5px 0;font-size:12px;line-height:15px;}
            #${CARD_ID} .mpRow:first-of-type{border-top:0;}
            #${CARD_ID} .mpRow b{text-align:right;font-weight:900;}
            #${CARD_ID} .mpBarOuter{height:10px;background:#1f1b19;border-radius:999px;overflow:hidden;margin:6px 0 5px 0;}
            #${CARD_ID} .mpBarInner{height:100%;background:#c9b09e;border-radius:999px;width:0%;}
            #${CARD_ID} .mpNote{font-size:11px;line-height:14px;opacity:.84;margin-top:8px;}
        `;
        document.head.appendChild(style);
    }

    function getOwnName(){
        try { const me = window.bonkHost && window.bonkHost.getMyPlayerObjectSafe && window.bonkHost.getMyPlayerObjectSafe(); if(me && me.userName) return me.userName; } catch(e) {}
        try { const id = window.bonkHost && window.bonkHost.toolFunctions && window.bonkHost.toolFunctions.networkEngine && window.bonkHost.toolFunctions.networkEngine.getLSID && window.bonkHost.toolFunctions.networkEngine.getLSID(); const p = window.bonkHost && window.bonkHost.players && window.bonkHost.players[id]; if(p && p.userName) return p.userName; } catch(e) {}
        try { const p = window.bonkHost && window.bonkHost.ensureClientProfile && window.bonkHost.ensureClientProfile(); if(p && p.displayName) return p.displayName; } catch(e) {}
        return "You";
    }

    function getProfile(){
        try { if(window.bonkHost && window.bonkHost.syncClientProfileFromPublicBonk) return window.bonkHost.syncClientProfileFromPublicBonk("my-profile-open"); } catch(e) {}
        try { if(window.bonkHost && window.bonkHost.ensureClientProfile) return window.bonkHost.ensureClientProfile(); } catch(e) {}
        return {};
    }

    function xpInfo(profile){
        try { if(window.bonkHost && window.bonkHost.getBonkXpProfile) return window.bonkHost.getBonkXpProfile(); } catch(e) {}
        const level = Math.max(1, num(profile.level || profile.lastPublicLevel || 1));
        const base = window.bonkHost && window.bonkHost.xpForLevel ? num(window.bonkHost.xpForLevel(level)) : 100 * Math.max(0, level - 1) * Math.max(0, level - 1);
        const next = window.bonkHost && window.bonkHost.xpForLevel ? num(window.bonkHost.xpForLevel(level + 1)) : 100 * level * level;
        const total = num(profile.totalXp || base);
        return {
            level, totalXp: total, baseXp: base, nextXp: next,
            thisLevel: Math.max(0, total - base), untilNext: Math.max(0, next - total),
            roundsUntilNext: Math.ceil(Math.max(0, next - total) / 100),
            levelSource: profile.levelSource || "saved/local", xpSource: profile.xpSource || "saved/local"
        };
    }

    function getStats(name){
        let rec = {};
        try {
            const key = window.bonkHost && window.bonkHost.getCanonicalPlayerName ? window.bonkHost.getCanonicalPlayerName(name) : name;
            if(window.bonkHost && window.bonkHost.getCareerStatsRecord) rec = window.bonkHost.getCareerStatsRecord(key) || {};
            if(window.bonkHost && window.bonkHost.normalizeStatsBuckets) rec = window.bonkHost.normalizeStatsBuckets(rec || {});
            if(window.bonkHost && window.bonkHost.getCorrectedStatsRecordV85) rec = window.bonkHost.getCorrectedStatsRecordV85(rec || {});
        } catch(e) { rec = {}; }
        ["quickplay","onevone","team","custom"].forEach(k => { if(!rec[k]) rec[k] = {wins:0,games:0}; });
        const buckets = ["quickplay","onevone","team","custom"];
        const total = buckets.reduce((a,k)=>({wins:a.wins+num(rec[k].wins),games:a.games+num(rec[k].games)}),{wins:0,games:0});
        if(!total.games && (rec.total || rec.games || rec.wins)) {
            total.wins = num(rec.wins || rec.totalWins || (rec.total && rec.total.wins));
            total.games = num(rec.games || rec.totalGames || (rec.total && rec.total.games));
        }
        rec.total = total;
        return rec;
    }

    const row = (label, value) => `<div class="mpRow"><span>${safe(label)}</span><b>${safe(value)}</b></div>`;
    const bucket = (label, b) => row(label, `${fmt(b && b.wins)}W / ${fmt(b && b.games)}G / ${pct(b && b.wins, b && b.games)}`);

    function openMyProfile(){
        addStyle();
        const profile = getProfile();
        const name = (profile && profile.displayName) || getOwnName();
        const xp = xpInfo(profile || {});
        try { profile.joinedVersion = "1.0"; if(window.bonkHost && window.bonkHost.saveClientProfile) window.bonkHost.saveClientProfile(); } catch(e) {}
        const sessionXpGained = getSessionXpGained(xp);
        const stats = getStats(name);
        stats.bestStreak = 0;
        const levelSpan = Math.max(1, xp.nextXp - xp.baseXp);
        const progress = Math.max(0, Math.min(100, Math.round(((xp.totalXp - xp.baseXp) / levelSpan) * 100)));
        let capStatus = "Unknown";
        try { const cap = window.bonkHost && window.bonkHost.getXpCapStatus && window.bonkHost.getXpCapStatus(profile); capStatus = cap && cap.capped ? "YES" : "NO"; } catch(e) {}

        let card = $(CARD_ID);
        if(!card) {
            card = document.createElement("div");
            card.id = CARD_ID;
            (document.getElementById("pagecontainer") || document.body || document.documentElement).appendChild(card);
        }
        card.innerHTML = `
            <div class="mpTop">My Profile <div class="mpClose" title="Close">×</div></div>
            <div class="mpBody">
                <div class="mpName">${safe(name)}</div>
                <div class="mpSub">Self player card • Trapi's Client ${safe(VERSION)}</div>
                <div class="mpGrid">
                    <div class="mpTile"><div class="mpLabel">Level</div><div class="mpValue">${fmt(xp.level)}</div></div>
                    <div class="mpTile"><div class="mpLabel">Win Rate</div><div class="mpValue">${pct(stats.total.wins, stats.total.games)}</div></div>
                    <div class="mpTile"><div class="mpLabel">Wins</div><div class="mpValue">${fmt(stats.total.wins)}</div></div>
                    <div class="mpTile"><div class="mpLabel">Games</div><div class="mpValue">${fmt(stats.total.games)}</div></div>
                </div>
                <div class="mpSection"><div class="mpTitle">XP / Level Progress</div>
                    ${row("Base XP", fmt(xp.baseXp))}${row("XP Gained This Session", fmt(sessionXpGained))}
                    <div class="mpBarOuter"><div class="mpBarInner" style="width:${progress}%"></div></div>
                    ${row("XP Capped", capStatus)}
                </div>
                <div class="mpSection" style="margin-top:9px;"><div class="mpTitle">Full Player Stats</div>
                    ${bucket("Overall", stats.total)}${bucket("Classic Quickplay", stats.quickplay)}${bucket("1v1", stats.onevone)}${bucket("Team Games", stats.team)}${bucket("Free For All", stats.custom)}${row("Current Streak", fmt(stats.currentStreak))}${row("Best Streak", fmt(stats.bestStreak))}
                </div>
                <div class="mpSection" style="margin-top:9px;"><div class="mpTitle">Client Info</div>
                    ${row("Profile Created", fmtDate(profile.joinedDate))}${row("Joined Version", "1.0")}${row("Level Source", xp.levelSource)}${row("XP Source", xp.xpSource)}
                </div>
                <div class="mpNote">This page is only for the player using the mod. Player lookup and other-player database tools are not included here.</div>
            </div>`;
        const close = card.querySelector(".mpClose");
        if(close) close.onclick = () => { card.style.display = "none"; };
        card.style.display = "block";
        try { const menu = $("hostPlayerMenu"); if(menu){ const r = menu.getBoundingClientRect(); card.style.left = Math.max(10, Math.round(r.right + 10)) + "px"; card.style.top = Math.max(10, Math.round(r.top)) + "px"; } } catch(e) {}
        return card;
    }

    function relabelAndBind(){
        try {
            addStyle();
            if(window.bonkHost) {
                window.bonkHost.showMyProfile = openMyProfile;
                window.bonkHost.openMyProfile = openMyProfile;
                window.bonkHost.openSelfProfileCard = openMyProfile;
                window.bonkHost.clientVersion = VERSION;
                window.bonkHost.publicPlayerCardsShowXpProgress = false;
            }
            const ver = $("hostPlayerMenuClientVersion"); if(ver) ver.textContent = VERSION;
            const statsToggle = $("hostPlayerMenuStatsToggle");
            if(statsToggle) {
                statsToggle.textContent = "MY PROFILE ▼";
                statsToggle.style.display = "";
                statsToggle.style.pointerEvents = "auto";
                statsToggle.style.cursor = "pointer";
                statsToggle.onclick = function(e){ if(e){ e.preventDefault(); e.stopPropagation(); } openMyProfile(); return false; };
            }
            const oldProfileBtn = $("hostPlayerMenuMyProfileButton");
            if(oldProfileBtn) {
                oldProfileBtn.textContent = "MY PROFILE";
                oldProfileBtn.style.pointerEvents = "auto";
                oldProfileBtn.style.cursor = "pointer";
                oldProfileBtn.onclick = function(e){ if(e){ e.preventDefault(); e.stopPropagation(); } openMyProfile(); return false; };
            }
            if(window.bonkHost && !window.bonkHost.__trapisProfileRouteV910Wrapped) {
                window.bonkHost.__trapisProfileRouteV910Wrapped = true;
                const oldOpenV87 = window.bonkHost.openV87Page;
                window.bonkHost.openV87Page = function(pageKey){
                    if(pageKey === "profile" || pageKey === "stats" || pageKey === "myprofile") return openMyProfile();
                    return oldOpenV87 ? oldOpenV87.apply(this, arguments) : undefined;
                };
                const oldCategory = window.bonkHost.openCategoryPopupV86;
                window.bonkHost.openCategoryPopupV86 = function(def, force){
                    const key = def && def.key;
                    if(key === "stats" || key === "profile") return openMyProfile();
                    return oldCategory ? oldCategory.apply(this, arguments) : undefined;
                };
            }
        } catch(e) { console.warn("Trapi's Client v9.0.10 My Profile rewrite waiting", e); }
    }

    function capture(e){
        try {
            const target = e.target && e.target.closest && e.target.closest("#hostPlayerMenuStatsToggle,#hostPlayerMenuMyProfileButton,.trapisV87Btn,.trapisV89ToolBtn,.newbonklobby_settings_button,button");
            if(!target) return;
            const text = (target.textContent || "").replace(/\s+/g," ").trim().toUpperCase();
            const isProfile = target.id === "hostPlayerMenuStatsToggle" || target.id === "hostPlayerMenuMyProfileButton" || text === "PROFILE" || text === "MY PROFILE" || text.startsWith("MY PROFILE ");
            if(!isProfile) return;
            e.preventDefault();
            e.stopPropagation();
            if(e.stopImmediatePropagation) e.stopImmediatePropagation();
            openMyProfile();
        } catch(err) { console.warn("Trapi's Client v9.0.10 profile click failed", err); }
    }

    document.addEventListener("click", capture, true);
    document.addEventListener("pointerdown", capture, true);
    document.addEventListener("mousedown", capture, true);
    window.trapisOpenMyProfileV910 = openMyProfile;
    relabelAndBind();
    if(!window.trapisMyProfileV910Interval) window.trapisMyProfileV910Interval = setInterval(relabelAndBind, 500);
})();

	console.log("Bonk Host injector run");
	return newStr;
}

if(!window.bonkCommands) window.bonkCommands = [];

if(!window.bonkCodeInjectors) window.bonkCodeInjectors = [];
window.bonkCodeInjectors.push(bonkCode => {
	try {
		return injector(bonkCode);
	} catch (error) {
		console.error("Trapi's Client injector failed:", error);
		return bonkCode;
	}
});

console.log("Bonk Host injector loaded");


/* ===== Bundled Bonk Playlists v6.0 ===== */
// ==Bundled Bonk Playlists UserScript==
// @name         Bonk Playlists
// @version      6.0
// @author       Salama
// @description  Adds map playlists to bonk.io
// @match        https://bonk.io/gameframe-release.html
// @run-at       document-start
// @grant        none
// @supportURL   https://discord.gg/Dj6usq7ww3
// @namespace    https://greasyfork.org/users/824888
// @downloadURL https://update.greasyfork.org/scripts/439123/Bonk%20Playlists.user.js
// @updateURL https://update.greasyfork.org/scripts/439123/Bonk%20Playlists.meta.js
// ==/Bundled Bonk Playlists UserScript==

// for use as a userscript ensure you have Excigma's code injector userscript
// https://greasyfork.org/en/scripts/433861-code-injector-bonk-io

let playlistsInjector = (str) => {
	let newStr = str;
window.playlists = {};
window.playlists.edit = false;
window.playlists.autofav = false;
window.playlists.editing = false;
window.playlists.categoryFunc = ()=>{};
window.playlists.mapLoader = ()=>{};
window.playlists.menuFunctions = {};
window.playlists.toolFunctions = {};
window.playlists.setMapsLoaded = ()=>{};
window.playlists.setMapsLoadFinished = ()=>{};
window.playlists.bigClass = {};

let token = null;
window.playlists.merge = {
	enabled: false,
	from: {
		element: null,
		index: null
	},
	to: {
		element: null,
		index: null
	}
};
let dropdownOption = document.createElement('div');
let playlistsButton = document.createElement('div');
let toolbox = document.createElement('div');
//Insert before favs
document.getElementById("maploadtypedropdown").insertBefore(dropdownOption, document.getElementById("maploadtypedropdownoption1"));

// Monitor style changes
const dropdownObserver = new MutationObserver(() => {
	document.getElementById("maploadtypedropdownoptionplaylists").style.display = document.getElementById("maploadtypedropdownoption1").style.display;
	document.getElementById("maploadtypedropdownoptionplaylists").onclick = () => {
		document.getElementById("maploadtypedropdowntitle").innerHTML = "MY PLAYLISTS";
		window.playlists.categoryFunc("blank", true);
		window.playlists.categoryFunc("playlists", true);
		document.getElementById("maploadwindowsearchoptions").style.visibility = "hidden";
		document.getElementById("maploadwindowhotnessslider").style.visibility = "hidden";
		document.getElementById("maploadwindowsearchinput").style.visibility = "hidden";
		document.getElementById("maploadwindowsearchbutton").style.visibility = "hidden";
		document.getElementById("maploadwindowtoolbox").style.display = "flex";
		getPlaylists();
	};
	if(document.getElementById("maploadtypedropdowntitle").innerHTML === "MY PLAYLISTS") {
		document.getElementById("maploadwindowplaylistbackbutton").style.display = "block";
		document.getElementById("maploadwindowtoolbox").style.display = "flex";
		document.getElementById("maploadwindowmapscontainer").style.bottom = "28px";
		document.getElementById("maploadwindowmapscontainer").style.height = "calc(100% - 108px - 23px)";
		document.getElementById("maploadwindowsearchinput").style.visibility = "hidden";
		document.getElementById("maploadwindowsearchbutton").style.visibility = "hidden";
	}
	else {
		// Might conflict with future mods
		document.getElementById("maploadwindowplaylistbackbutton").style.display = "none";
		document.getElementById("maploadwindowtoolbox").style.display = "none";
		document.getElementById("maploadwindowmapscontainer").style.removeProperty("bottom");
		document.getElementById("maploadwindowmapscontainer").style.removeProperty("height");
		document.getElementById("maploadwindowsearchinput").style.removeProperty("visibility");
		document.getElementById("maploadwindowsearchbutton").style.removeProperty("visibility");
		if(window.playlists.edit) {
			document.getElementById("maploadwindowplaylistedit").click();
		}
		if(window.playlists.merge.enabled) {
			document.getElementById("maploadwindowplaylistmerge").click();
		}
	}
	document.getElementById("maploadwindowplaylistbackbutton").onclick = document.getElementById("maploadtypedropdownoptionplaylists").onclick;
	document.getElementById("maploadtypedropdownoptionplaylists").onmouseenter = document.getElementById("maploadtypedropdownoption1").onmouseenter;
	document.getElementById("maploadtypedropdownoptionplaylists").onmouseleave = document.getElementById("maploadtypedropdownoption1").onmouseleave;
	document.getElementById("maploadtypedropdownoptionplaylists").onmousedown = document.getElementById("maploadtypedropdownoption1").onmousedown;

	document.getElementById("maploadwindowplaylistbackbutton").onmouseenter = document.getElementById("maploadtypedropdownoption1").onmouseenter;
	document.getElementById("maploadwindowplaylistbackbutton").onmouseleave = document.getElementById("maploadtypedropdownoption1").onmouseleave;
	document.getElementById("maploadwindowplaylistbackbutton").onmousedown = document.getElementById("maploadtypedropdownoption1").onmousedown;

});

chatObserver = new MutationObserver(e => {
	for(let mutation of e) {
		if(mutation.type == "childList") {
			for(let node of mutation.addedNodes) {
				if(node.textContent === "* Accepted commands are listed above ") {
					let helpmsg = document.createElement("div");
					mutation.target.insertBefore(helpmsg, node.previousSibling);
					helpmsg.outerHTML = '<div><span class="newbonklobby_chat_status" style="color: rgb(181, 48, 48);">/p - commands from playlists mod</span></div>';
				}
			}
		}
	}
});

chatObserver.observe(document.getElementById("newbonklobby_chat_content"), {attributes: false, childList: true, subtree: false});
dropdownObserver.observe(document.getElementById("maploadtypedropdownoption1"), {attributes: true, childList: false, subtree: true});

document.getElementById("maploadwindow").appendChild(playlistsButton);
document.getElementById("maploadwindow").appendChild(toolbox);
toolbox.outerHTML = `<div id="maploadwindowtoolbox" style="width: 100%;height: 23px;bottom: 0;position: absolute;background-color: inherit;z-index: 1;display: none;padding: 5px;">
	<div class="brownButton brownButton_classic buttonShadow" id="maploadwindowplaylistedit" style="line-height: 23px; height: 23px; width: 75px; margin-right: 5px; display: block;">EDIT</div>
	<div class="brownButton brownButton_classic buttonShadow" id="maploadwindowplaylistmerge" style="line-height: 23px; height: 23px; width: 75px; margin-right: 5px; display: block;">MERGE</div>
	<div class="brownButton brownButton_classic buttonShadow" id="maploadwindowplaylistimport" style="line-height: 23px; height: 23px; width: 75px; margin-right: 5px; display: block;">IMPORT</div>
	<div class="brownButton brownButton_classic buttonShadow" id="maploadwindowplaylistexport" style="line-height: 23px; height: 23px; width: 75px; margin-right: 5px; display: block;">EXPORT</div>
	<div class="brownButton brownButton_classic buttonShadow" id="maploadwindowplaylistautofav" style="line-height: 23px; height: 23px; width: 75px; margin-right: 5px; display: block;">AUTOFAV</div>
	<div class="brownButton brownButton_classic buttonShadow" id="maploadwindowplaylistdeleteall" style="line-height: 23px; height: 23px; width: 125px; margin-right: 5px; display: none;">DELETE ALL</div>
</div>`;

dropdownOption.outerHTML = `<div class="dropdown-option dropdown_classic" id="maploadtypedropdownoptionplaylists" style="display: none;">MY PLAYLISTS</div>`;
playlistsButton.outerHTML = `<div class="brownButton brownButton_classic buttonShadow" id="maploadwindowplaylistbackbutton" style="position: absolute; left: 210px; line-height: 23px; height: 23px; width: 75px; top: 57px; display: block;">BACK</div>`;

let dbRequest = indexedDB.open("salamaStorage", 1);
let db;

window.playlists.playlists = [];

window.playlists.savePlaylists = playlists => {
	try {
		let transaction = db.transaction("playlists", "readwrite");
		transaction.objectStore("playlists").put(playlists, 1);
	}
	catch(e) {
		console.log("Couln't save playlists to db: ", e)
	}
}

dbRequest.onsuccess = e => {
	db = e.target.result;
	let transaction = db.transaction("playlists");
	let getRequest = transaction.objectStore("playlists").get(1);
	getRequest.onsuccess = e => {
		window.playlists.playlists = e.target.result;
	}
}

dbRequest.onupgradeneeded = e => {
	db = e.target.result;
	let objectStore = db.createObjectStore("playlists");
	objectStore.put(JSON.parse(localStorage.playlists || "[]"), 1);
	delete localStorage.playlists;
}

window.playlists.setToken = t => {
	token = t;
}

//This is mainly meant to prevent you from accidentally importing the wrong file
const validatePlaylists = playlists => {
	try {
		let newPlaylists = JSON.parse(playlists);
		for(let playlist of newPlaylists) {
			if(!(
				Object.keys(playlist).find(i => !["name","description","image","maps","b1maps"].includes(i)) === undefined &&
				typeof(playlist.name) == "string" &&
				typeof(playlist.description) == "string" &&
				((typeof(playlist.image) == "string" &&
				playlist.image.substr(0, 5) == "data:") ||
				playlist.image == undefined) &&
				playlist.maps.filter(e => {return typeof(e)=="number"}).length == playlist.maps.length
			))
				return false;
		}
		return true;
	}
	catch {
		return false;
	}
}

document.getElementById("maploadwindowplaylistexport").addEventListener("click", () => {
    let a = document.createElement("a");
    document.body.appendChild(a);
    a.href = URL.createObjectURL(new Blob([JSON.stringify(window.playlists.playlists)], {type: "oclet/stream"}));
    a.download = "playlists.txt";
    a.click();
    document.body.removeChild(a);
})

document.getElementById("maploadwindowplaylistimport").addEventListener("click", () => {
    let a = document.createElement("input");
    a.type = 'file';
    document.body.appendChild(a);
    a.onchange = e => {
        let file = e.target.files[0];
        let reader = new FileReader();
        reader.readAsText(file);
        reader.onload = readerEvent => {
			let newPlaylists = readerEvent.target.result;
			if(validatePlaylists(newPlaylists)) {
				window.playlists.playlists = window.playlists.playlists.concat(JSON.parse(newPlaylists));
				window.playlists.savePlaylists(window.playlists.playlists);
				document.getElementById("maploadwindowplaylistautofav").click();
			}
        }
    };
    a.click();
    document.body.removeChild(a);
})

document.getElementById("maploadwindowplaylistautofav").addEventListener("click", async () => {
	let popup = document.createElement('div');
	document.getElementById("maploadwindow").appendChild(popup);
	popup.outerHTML = `
	<div id="maploadwindowplaylistautofavpopup" style="width: calc(100% - 200px);background-color: inherit;z-index: 1;position: absolute;left: 100px;top: 50px;height: calc(100% - 100px);filter: brightness(1.1);border-radius: 7px;">
		<div class="windowTopBar_classic" style="height: 40px;border-top-left-radius: 3px;border-top-right-radius: 3px;font-family: futurept_b1;line-height: 40px;color: #f9f9f9;font-size: 28px;text-align: center;">
			Autofav
			<div id="maploadwindowplaylistautofavprogress" style="height: 40px;font-family: monospace;line-height: 40px;color: #f9f9f9;font-size: 15px;text-align: center;right: 10px;position: absolute;top: 0px;"></div>
		</div>
		<div class="newbonklobby_chat_msg_txt" id="maploadwindowplaylistautofavstatus" style="text-align: center;top: 20px;position: relative;">Counting maps...</div>
		<div class="brownButton brownButton_classic buttonShadow" style="line-height: 23px; height: 23px; width: 75px; margin-right: 5px; display: block;bottom: 10px;position: absolute;left: 20px;" id="maploadwindowplaylistautofavcancel">CANCEL</div>
		<div style="line-height: 23px; height: 23px; width: 75px; margin-right: 5px; display: block;bottom: 10px;position: absolute;right: 20px;" id="maploadwindowplaylistautofavstart" class="brownButton brownButton_classic buttonShadow brownButtonDisabled">START</div>
		<div class="brownButton brownButton_classic buttonShadow" style="line-height: 23px; height: 23px; width: 75px; margin-right: 5px; display: none;bottom: 10px;position: absolute;left: 20px;" id="maploadwindowplaylistautofavno">NO</div>
		<div style="line-height: 23px; height: 23px; width: 75px; margin-right: 5px; display: none;bottom: 10px;position: absolute;right: 20px;" class="brownButton brownButton_classic buttonShadow" id="maploadwindowplaylistautofavyes">YES</div>
	</div>`;
	let error = false;
	document.getElementById("maploadwindowplaylistautofavcancel").addEventListener("click", () => {
		error = "cancelled";
		document.getElementById("maploadwindowplaylistautofavpopup").remove();
		document.getElementById("maploadtypedropdownoptionplaylists").click();
	});
	let maps = [];
	const get = async count => {
		return new Promise(resolve => {
			window.$.post("https://bonk2.io/scripts/map_getfave.php", {
				token: token,
				startingfrom: 32 * count
			}).done(async e => {
				maps = maps.concat(e.maps.map(e => {return e.id}));
				error = (e.r == "success" ? false : e.r);
				if(e.more && !error) await get(count + 1);
				resolve();
			}).fail(e => {
				error = e.statusText;
				resolve();
			});
		});
	}
	await get(0);
	if(error && error != "cancelled") {
		document.getElementById("maploadwindowplaylistautofavstatus").innerText += `\nError: ${error}`;
	}
	else if(error != "cancelled") {
		if(window.playlists.playlists.length > 0) {
			maps = [...new Set(window.playlists.playlists.map(e => {return e.maps;}).reduce((a, b) => {return a.concat(b);}).filter(e => {return !maps.includes(e);}))];
		}
		else {
			maps = [];
		}
		document.getElementById("maploadwindowplaylistautofavstatus").innerText += `\n${maps.length} maps to favorite`;
		document.getElementById("maploadwindowplaylistautofavprogress").innerText = `[${'0'.repeat((maps.length+'').length)} / ${maps.length}]`;
		if(maps.length == 0) return;
		let date = new Date();
		if(maps.length > 600) {
			document.getElementById("maploadwindowplaylistautofavstatus").innerText += `\nYou will get ratelimited due to the high amount of maps, which means that you will have to continue this after ${((date.getHours() + 1) % 24)}:00. This also means that you can't favorite maps normally before that time.`;
		}
		else if(maps.length > 580) {
			document.getElementById("maploadwindowplaylistautofavstatus").innerText += `\nYou might get ratelimited due to the high amount of maps, which means that you will have to continue this after ${((date.getHours() + 1) % 24)}:00. This would also mean that you can't favorite maps normally before that time.`;
		}
		document.getElementById("maploadwindowplaylistautofavstart").classList.remove("brownButtonDisabled");
		document.getElementById("maploadwindowplaylistautofavstart").addEventListener("click", async () => {
			document.getElementById("maploadwindowplaylistautofavstart").classList.add("brownButtonDisabled");
			window.playlists.autofav = true;
			let count = 0;
			let notFound = [];
			for(let map of maps) {
				if(error) {
					window.playlists.autofav = false;
					return;
				}
				await window.$.post("https://bonk2.io/scripts/map_fave.php", {
					token: token,
					mapid: map,
					action: "f"
				}).done(e => {
					if(e.r === "fail") {
						switch(e.e) {
							case "map_unpublished":
							case "map_not_found":
							case "already_faved":
								notFound.push(map);
								break;
							case "token":
								document.getElementById("maploadwindowplaylistautofavstatus").innerText += "\nError: invalid token";
								break;
							case "ratelimited":
								document.getElementById("maploadwindowplaylistautofavstatus").innerText += `\nRatelimited! Please continue after ${((date.getHours() + 1) % 24)}:00.`;
								error = "ratelimited";
								break;
						}
					}
					count++;
					if(!error) document.getElementById("maploadwindowplaylistautofavprogress").innerText = `[${'0'.repeat((maps.length+'').length-(count+'').length)}${count} / ${maps.length}]`;
					
				})
			}
			window.playlists.autofav = false;
			document.getElementById("maploadwindowplaylistautofavcancel").classList.add("brownButtonDisabled");
			if(notFound.length > 0) {
				document.getElementById("maploadwindowplaylistautofavstatus").innerText += `\nSome of the playlists contain ${notFound.length} maps in total that have been hidden or deleted. Do you want to remove them?`;
				document.getElementById("maploadwindowplaylistautofavno").style.display = "block";
				document.getElementById("maploadwindowplaylistautofavyes").style.display = "block";
				document.getElementById("maploadwindowplaylistautofavstart").style.display = "none";
				document.getElementById("maploadwindowplaylistautofavcancel").style.display = "none";
				document.getElementById("maploadwindowplaylistautofavno").addEventListener("click", () => {
					document.getElementById("maploadwindowplaylistautofavpopup").remove();
					document.getElementById("maploadtypedropdownoptionplaylists").click();
				});
				document.getElementById("maploadwindowplaylistautofavyes").addEventListener("click", () => {
					for(let list of window.playlists.playlists) {
						list.maps = list.maps.filter(e => {return notFound.indexOf(e) === -1;});
					}
					window.playlists.savePlaylists(window.playlists.playlists);
					document.getElementById("maploadwindowplaylistautofavpopup").remove();
					document.getElementById("maploadtypedropdownoptionplaylists").click();
				});
			}
			else {
				document.getElementById("maploadwindowplaylistautofavyes").innerText = "DONE";
				document.getElementById("maploadwindowplaylistautofavyes").style.display = "block";
				document.getElementById("maploadwindowplaylistautofavyes").addEventListener("click", () => {
					document.getElementById("maploadwindowplaylistautofavpopup").remove();
					document.getElementById("maploadtypedropdownoptionplaylists").click();
				})
			}
		});
	}
});

document.getElementById("maploadwindowplaylistedit").addEventListener("click", e => {
	window.playlists.editing = false;
	window.playlists.edit = !window.playlists.edit;
	if(window.playlists.edit) {
		document.getElementById("maploadwindowplaylistmerge").classList.add("brownButtonDisabled");
		document.getElementById("maploadwindowplaylistimport").classList.add("brownButtonDisabled");
		document.getElementById("maploadwindowplaylistexport").classList.add("brownButtonDisabled");
		document.getElementById("maploadwindowplaylistautofav").classList.add("brownButtonDisabled");
		document.getElementById("maploadwindowplaylistdeleteall").style.display = "block";
		document.getElementById("maploadwindowplaylistdeleteall").innerText = "DELETE ALL";
		e.target.style.filter = "brightness(1.75)";
		document.getElementById("maploadtypedropdownoptionplaylists").click();
	}
	else {
		document.getElementById("maploadwindowplaylistedit").style.removeProperty("filter");
		document.getElementById("maploadwindowplaylistmerge").classList.remove("brownButtonDisabled");
		document.getElementById("maploadwindowplaylistimport").classList.remove("brownButtonDisabled");
		document.getElementById("maploadwindowplaylistexport").classList.remove("brownButtonDisabled");
		document.getElementById("maploadwindowplaylistautofav").classList.remove("brownButtonDisabled");
		document.getElementById("maploadwindowplaylistdeleteall").style.display = "none";
		window.playlists.savePlaylists(window.playlists.playlists);
		if(document.getElementById("maploadtypedropdowntitle").innerHTML === "MY PLAYLISTS") {
			document.getElementById("maploadtypedropdownoptionplaylists").click();
		}
	}
});

document.getElementById("maploadwindowplaylistmerge").addEventListener("click", e => {
	window.playlists.merge.enabled = !window.playlists.merge.enabled;
	if(window.playlists.merge.enabled) {
		window.playlists.edit = false;
		document.getElementById("maploadwindowplaylistedit").style.removeProperty("filter");
		e.target.style.filter = "brightness(1.75)";
		document.getElementById("maploadwindowplaylistedit").classList.add("brownButtonDisabled");
		document.getElementById("maploadwindowplaylistimport").classList.add("brownButtonDisabled");
		document.getElementById("maploadwindowplaylistexport").classList.add("brownButtonDisabled");
		document.getElementById("maploadwindowplaylistautofav").classList.add("brownButtonDisabled");
	}
	else {
		e.target.style.removeProperty("filter");
		if(window.playlists.merge.from.element !== null) {
			window.playlists.merge.from.element.style.removeProperty("filter");
			window.playlists.merge.from.element.style.visibility = "hidden";
		}
		if(window.playlists.merge.to.element !== null) {
			window.playlists.merge.to.element.style.removeProperty("filter");
			window.playlists.merge.to.element.style.visibility = "hidden";
		}
		window.playlists.merge = {
			enabled: false,
			from: {
				element: null,
				index: null
			},
			to: {
				element: null,
				index: null
			}
		};
		document.getElementById("maploadwindowplaylistedit").classList.remove("brownButtonDisabled");
		document.getElementById("maploadwindowplaylistimport").classList.remove("brownButtonDisabled");
		document.getElementById("maploadwindowplaylistexport").classList.remove("brownButtonDisabled");
		document.getElementById("maploadwindowplaylistautofav").classList.remove("brownButtonDisabled");
		document.getElementById("maploadtypedropdownoptionplaylists").click();
	}
});

document.getElementById("maploadwindowplaylistdeleteall").addEventListener("click", e => {
	switch(e.target.innerText) {
		case "DELETE ALL":
			e.target.innerText = "SURE?";
			break;
		case "SURE?":
			e.target.innerText = "100% SURE?";
			break;
		case "100% SURE?":
			e.target.innerText = "FINAL WARNING";
			break;
		case "FINAL WARNING":
			e.target.innerText = "DELETE ALL";
			window.playlists.playlists = [];
			for(let playlist of [...document.getElementsByClassName("maploadwindowplaylistdiv")]) {
				playlist.style.opacity = 0.3;
				playlist.style.pointerEvents = "none";
			}
			break;
	}
})

document.getElementById("newbonklobby_mapbutton").addEventListener("click", () => {
    if(document.getElementById("maploadtypedropdowntitle").innerText === "MY PLAYLISTS") {
        document.getElementById("maploadwindowplaylistbackbutton").style.display = "block";
		document.getElementById("maploadwindowtoolbox").style.display = "flex";
		document.getElementById("maploadwindowmapscontainer").style.bottom = "28px";
		document.getElementById("maploadwindowmapscontainer").style.height = "calc(100% - 108px - 23px)";
    }
    else {
        document.getElementById("maploadwindowplaylistbackbutton").style.display = "none";
		document.getElementById("maploadwindowtoolbox").style.display = "none";
		document.getElementById("maploadwindowmapscontainer").style.removeProperty("bottom");
		document.getElementById("maploadwindowmapscontainer").style.removeProperty("height");
    }
});

const chatHandler = e => {
	if(e.keyCode === 13) {
		if(e.target.value.length > 0) {
			if(e.target.value[0] === "/") {
				let command = e.target.value.split(" ")[0].substring(1);
				let args = e.target.value.split(" ").slice(1);
				if(command === "fav") {
					console.log("Autofav = " + window.playlists.autofav);
					if(window.playlists.autofav) {
						e.target.value = "";
						window.playlists.menuFunctions.showStatusMessage("* Favoriting maps is disabled while autofav is on", "#b53030");
					}
				}
				else if(command.startsWith("p") && !Number.isNaN(Number(command.substr(1)))) {
					e.target.value = "";
					if(args[0] === "list" && command === "p") {
						window.playlists.menuFunctions.showStatusMessage("Saved playlists", "#b53030");
						for(let i = 0; i < window.playlists.playlists.length; i++) {
							window.playlists.menuFunctions.showStatusMessage("* [" + (i+1) + "] " + window.playlists.playlists[i].name, "#b53030");
						}
						return;
					}
					if(args.length === 0) {
						args[0] = parseInt(command.substr(1));
					}
					if(Number.isNaN(parseInt(args[0]))) {
						// Show help
						window.playlists.menuFunctions.showStatusMessage("* List of playlist commands:", "#b53030", true);
						window.playlists.menuFunctions.showStatusMessage("/p list", "#b53030", true);
						window.playlists.menuFunctions.showStatusMessage("/p [index]", "#b53030", true);
						return;
					}
					
					if(args[0] < 1 || args[0] > window.playlists.playlists.length) {
						if(window.playlists.playlists.length === 0) {
							window.playlists.menuFunctions.showStatusMessage("You don't have any playlists!", "#b53030");
							return;
						}
						window.playlists.menuFunctions.showStatusMessage("Playlist index must be between 1 and " + (window.playlists.playlists.length), "#b53030");
						return;
					}

					let gameSettings = window.playlists.toolFunctions.getGameSettings();
					
					if(!gameSettings.map.m.pub && gameSettings.map.dbv == 2) {
						window.playlists.menuFunctions.showStatusMessage("You can't add a private map to a playlist! If it is a Bonk 1 map, *you* need to select the map from Bonk 1 map list without starting the game. A Bonk 1 map, which is selected from a playlist, cannot be added or removed.", "#b53030");
						return;
					}

					if(gameSettings.map.m.dbv === 2 && gameSettings.map.m.date !== undefined && gameSettings.map.m.date !== null && gameSettings.map.m.date !== "") {
						if(window.playlists.playlists[args[0] - 1].maps.includes(gameSettings.map.m.dbid)) {
							window.playlists.playlists[args[0] - 1].maps.splice(window.playlists.playlists[args[0] - 1].maps.indexOf(gameSettings.map.m.dbid), 1);
							window.playlists.menuFunctions.showStatusMessage("* Map removed from playlist", "#b53030", true);
						}
						else {
							// Hacky way to favorite the map
							e.target.value = "/fav";
							window.playlists.playlists[args[0] - 1].maps.push(gameSettings.map.m.dbid);
							window.playlists.menuFunctions.showStatusMessage("* Map added to playlist", "#b53030", true);
						}
					}
					else {
						if(window.playlists.playlists[args[0] - 1].b1maps.map(e => {return e.id}).includes(gameSettings.map.m.dbid)) {
							window.playlists.playlists[args[0] - 1].b1maps.splice(window.playlists.playlists[args[0] - 1].b1maps.map(e => {return e.id}).indexOf(gameSettings.map.m.dbid), 1);
							window.playlists.menuFunctions.showStatusMessage("* Map removed from playlist", "#b53030", true);
						}
						else {
							let b1map = {
								id: gameSettings.map.m.dbid,
								name: gameSettings.map.m.n,
								authorname: gameSettings.map.m.a,
								leveldata: window.playlists.bigClass.encodeToDatabase(gameSettings.map),
								vu: gameSettings.map.m.vu,
								vd: gameSettings.map.m.vd,
								remixname: gameSettings.map.m.rxn,
								remixauthor: gameSettings.map.m.rxa,
								remixdb: gameSettings.map.m.rxdb,
								remixid: gameSettings.map.m.rxid,
								publisheddate: gameSettings.map.m.date
							}
							if(gameSettings.map.m.date === undefined || gameSettings.map.m.date === null || gameSettings.map.m.date === "" || gameSettings.map.m.vu * 1 != gameSettings.map.m.vu || gameSettings.map.m.vd * 1 != gameSettings.map.m.vd) {
								window.playlists.menuFunctions.showStatusMessage("* Map could not be added to the playlist! To add Bonk 1 maps, *you* need to select the map from Bonk 1 map list without starting the game. A Bonk 1 map, which is selected from a playlist, cannot be added or removed.", "#b53030", true);
							}
							else {
								window.playlists.playlists[args[0] - 1].b1maps.push(b1map);
								window.playlists.menuFunctions.showStatusMessage("* Map added to playlist", "#b53030", true);
							}
						}
					}
					window.playlists.savePlaylists(window.playlists.playlists);
				}
				
			}
		}
	}
}

document.getElementById("newbonklobby_chat_input").addEventListener("keydown", chatHandler, true);
document.getElementById("ingamechatinputtext").addEventListener("keydown", chatHandler, true);

const getPlaylists = () => {
	window.playlists.setMapsLoaded(false);
	window.playlists.setMapsLoadFinished(false);

	let newPlaylistButton = {
		name: "NEW PLAYLIST",
		description: "Click here to create a new playlist",
		image: `data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" height="110.417px" width="160.6px"><rect style="fill:0;height:100%;width:30px;x:65.3px;y:0;"/><rect style="fill:0;height:30px;width:110.417px;x:25.091px;y:40.208px;"/></svg>`,
		maps: "new",
		b1maps: "new"
	};

	let playlistCreator = (list = {name: "", description: "", image: ""}, edit = null) => {
		let newPlaylist = document.createElement("div");
		newPlaylist.classList.add("maploadwindowmapdiv");
		newPlaylist.style.height = "200px";
		
		let encodedImage;
		let image = document.createElement("input");
		image.type = 'file';
		image.style.width = "100%";
		image.style.height = "110.417px";
		image.style.borderWidth = "1px";
		image.style.borderStyle = "solid";
		image.onchange = e => {
			let file = e.target.files[0]; 
			let reader = new FileReader();
			reader.readAsDataURL(file);
			reader.onload = readerEvent => {
				encodedImage = readerEvent.target.result;
			}
		};
	
		let title = document.createElement("input");
		title.classList.add("maploadwindowtext_picks");
		title.classList.add("maploadwindowtextname_picks");
		title.placeholder = "Playlist Name";
		title.value = list.name;
	
		let description = document.createElement("input");
		description.classList.add("maploadwindowtext_picks");
		description.classList.add("maploadwindowtextcomment_picks");
		description.placeholder = "Playlist Description";
		description.style.top = "150px";
		description.style.height = "60px";
		description.style.height = "19px";
		description.value = list.description;

		let cancelButton = document.createElement("div");
		cancelButton.classList.add("brownButton");
		cancelButton.classList.add("brownButton_classic");
		cancelButton.classList.add("buttonShadow");
		cancelButton.style.width = "calc(50% - 12px)";
		cancelButton.style.height = "25px";
		cancelButton.style.bottom = "5px";
		cancelButton.style.position = "absolute";
		cancelButton.innerText = "CANCEL";
		cancelButton.onclick = () => {
			if(edit !== null) {
				edit.style.display = "";
			}
			newPlaylist.remove();
			window.playlists.editing = false;
		};
		
		let saveButton = document.createElement("div");
		saveButton.classList.add("brownButton");
		saveButton.classList.add("brownButton_classic");
		saveButton.classList.add("buttonShadow");
		saveButton.style.width = "calc(50% - 12px)";
		saveButton.style.height = "25px";
		saveButton.style.bottom = "5px";
		saveButton.style.right = "6px";
		saveButton.style.position = "absolute";
		saveButton.innerText = "SAVE";
		saveButton.onclick = () => {
			if(edit === null) {
				window.playlists.playlists.push({
					name: title.value,
					description: description.value,
					image: encodedImage,
					maps: [],
					b1maps: []
				});
			}
			else {
				window.playlists.playlists[window.playlists.playlists.indexOf(list)] = Object.assign(window.playlists.playlists[window.playlists.playlists.indexOf(list)], {name: title.value, description: description.value, image: encodedImage === undefined ? list.image : encodedImage});
			}
			document.getElementById("maploadtypedropdownoptionplaylists").click();
			window.playlists.editing = false;
		};
	
		newPlaylist.appendChild(image);
		newPlaylist.appendChild(title);
		newPlaylist.appendChild(description);
		newPlaylist.appendChild(cancelButton);
		newPlaylist.appendChild(saveButton);
		return newPlaylist;
	}

	window.playlists.setMapsLoaded(true);
	for(let list of window.playlists.playlists.concat([newPlaylistButton])) {
		let playlist = document.createElement("div");
		playlist.classList.add("maploadwindowmapdiv");
		if(list != newPlaylistButton) playlist.classList.add("maploadwindowplaylistdiv");
		playlist.style.height = "200px";
		document.getElementById("maploadwindowmapscontainer").appendChild(playlist);
		
		let image = document.createElement("img");
		if(list.image != undefined) {
			image.src = list.image;
		}
		else {
			let color = [...list.name.substr(0,32)].reduce((a, b) => a + b.charCodeAt(0), 0) % 360;
			image.src = `data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" height="110.417px" width="160.6px"><rect style="fill:hsl(${color},75%,50%);height:100%;width:100%;"/></svg>`;
		}
		
		image.style.width = "160.6px";
		image.style.height = "110.417px";

		let title = document.createElement("span");
		title.classList.add("maploadwindowtext_picks");
		title.classList.add("maploadwindowtextname_picks");
		title.innerText = list.name;

		let description = document.createElement("span");
		description.classList.add("maploadwindowtext_picks");
		description.classList.add("maploadwindowtextcomment_picks");
		description.style.top = "150px";
		description.style.height = "60px";
		description.innerText = list.description;

		if(list.maps !== "new") {
			let deleteButton = document.createElement("div");
			deleteButton.style.visibility = "hidden";
			deleteButton.style.width = "50px";
			deleteButton.style.padding = "3px";
			deleteButton.style.fontSize = "16px";
			deleteButton.classList.add("maploadwindowdeletebutton");
			deleteButton.classList.add("brownButton");
			deleteButton.classList.add("brownButton_classic");
			deleteButton.classList.add("buttonShadow");
			deleteButton.innerText = "DELETE";
			deleteButton.onclick = () => {
				if(deleteButton.innerText === "DELETE") {
					deleteButton.innerText = "SURE?";
					return;
				}
				playlist.style.opacity = 0.3;
				playlist.style.pointerEvents = "none";
				window.playlists.playlists.splice(window.playlists.playlists.indexOf(list), 1);
			}

			let editButton = document.createElement("div");
			editButton.style.visibility = "hidden";
			editButton.style.left = "10px";
			editButton.style.width = "50px";
			editButton.style.padding = "3px";
			editButton.style.fontSize = "16px";
			editButton.classList.add("maploadwindowdeletebutton");
			editButton.classList.add("brownButton");
			editButton.classList.add("brownButton_classic");
			editButton.classList.add("buttonShadow");
			editButton.innerText = "EDIT";
			editButton.onclick = () => {
				playlist.style.display = "none";
				document.getElementById("maploadwindowmapscontainer").insertBefore(playlistCreator(list, playlist), playlist);
				window.playlists.editing = true;
			}

			let leftButton = document.createElement("div");
			leftButton.style.visibility = "hidden";
			leftButton.classList.add("brownButton");
			leftButton.classList.add("brownButton_classic");
			leftButton.classList.add("buttonShadow");
			leftButton.style.width = "26px";
			leftButton.style.height = "26px"
			leftButton.style.bottom = "10px";
			leftButton.style.left = "10px";
			leftButton.style.position = "absolute";
			leftButton.style.zIndex = 1;
			leftButton.innerText = "<";

			let rightButton = document.createElement("div");
			rightButton.style.visibility = "hidden";
			rightButton.classList.add("brownButton");
			rightButton.classList.add("brownButton_classic");
			rightButton.classList.add("buttonShadow");
			rightButton.style.width = "26px";
			rightButton.style.height = "26px"
			rightButton.style.bottom = "10px";
			rightButton.style.right = "10px";
			rightButton.style.position = "absolute";
			rightButton.style.zIndex = 1;
			rightButton.innerText = ">";

			leftButton.onclick = () => {
				let index = window.playlists.playlists.indexOf(list);
				if(index > 0) {
					window.playlists.playlists[index] = window.playlists.playlists.splice(index - 1, 1, window.playlists.playlists[index])[0];
					playlist.remove();
					document.getElementById("maploadwindowmapscontainer").insertBefore(playlist, document.getElementById("maploadwindowmapscontainer").children[index-1]);
					playlist.onmouseleave();
				}
			}

			rightButton.onclick = () => {
				let index = window.playlists.playlists.indexOf(list);
				if(index < window.playlists.playlists.length - 1) {
					window.playlists.playlists[index] = window.playlists.playlists.splice(index + 1, 1, window.playlists.playlists[index])[0];
					playlist.remove();
					document.getElementById("maploadwindowmapscontainer").insertBefore(playlist, document.getElementById("maploadwindowmapscontainer").children[index+1]);
					playlist.onmouseleave();
				}
			}

			let mergeButton = document.createElement("div");
			mergeButton.style.visibility = "hidden";
			mergeButton.classList.add("brownButton");
			mergeButton.classList.add("brownButton_classic");
			mergeButton.classList.add("buttonShadow");
			mergeButton.style.width = "124px";
			mergeButton.style.height = "52px"
			mergeButton.style.top = "23px";
			mergeButton.style.left = "23px";
			mergeButton.style.position = "absolute";
			mergeButton.style.zIndex = 1;
			mergeButton.style.justifyContent = "center";
			mergeButton.style.display = "flex";
			mergeButton.style.alignItems = "center";

			mergeButton.onclick = () => {
				mergeButton.style.filter = "brightness(1.75)";
				if(window.playlists.merge.from.index === null) {
					window.playlists.merge.from = {
						element: mergeButton,
						index: window.playlists.playlists.indexOf(list)
					}
				}
				else if(window.playlists.merge.from.element === mergeButton) {
					mergeButton.style.removeProperty("filter");
					window.playlists.merge.from = {
						element: null,
						index: null
					}
				}
				else {
					if(mergeButton.innerText !== "SURE?") {
						mergeButton.innerText = "SURE?";
						return;
					}
					window.playlists.merge.to = {
						element: mergeButton,
						index: window.playlists.playlists.indexOf(list)
					}
					window.playlists.playlists[window.playlists.merge.to.index].b1maps = [...new Set(
						(window.playlists.playlists[window.playlists.merge.from.index].b1maps.concat(window.playlists.playlists[window.playlists.merge.to.index].b1maps)).map(m => JSON.stringify(m))
					)].map(m => JSON.parse(m));
					window.playlists.playlists[window.playlists.merge.to.index].maps = [...new Set(window.playlists.playlists[window.playlists.merge.from.index].maps.concat(window.playlists.playlists[window.playlists.merge.to.index].maps))];
					window.playlists.playlists.splice(window.playlists.merge.from.index, 1);
					window.playlists.merge.from.element.parentElement.style.opacity = 0.3;
					window.playlists.merge.from.element.parentElement.style.pointerEvents = "none";
					window.playlists.savePlaylists(window.playlists.playlists);

					window.playlists.merge.from.element.style.removeProperty("filter");
					window.playlists.merge.from.element.style.visibility = "hidden";
					window.playlists.merge.to.element.style.removeProperty("filter");
					window.playlists.merge.to.element.style.visibility = "hidden";
					document.getElementById("maploadwindowplaylistmerge").click();
				}
			}

			playlist.onmouseenter = () => {
				if(window.playlists.editing) return;
				if(window.playlists.edit) {
					deleteButton.style.visibility = "inherit";
					editButton.style.visibility = "inherit";
					leftButton.style.visibility = "inherit";
					rightButton.style.visibility = "inherit";
				}
				else if(window.playlists.merge.enabled && window.playlists.merge.from.element !== mergeButton) {
					if(window.playlists.merge.from.index === null) {
						mergeButton.innerText = "MERGE FROM";
					}
					else {
						mergeButton.innerText = "MERGE TO";
						mergeButton.style.removeProperty("filter");
					}
					mergeButton.style.visibility = "inherit";
				}
			}
			playlist.onmouseleave = () => {
				deleteButton.style.visibility = "hidden";
				editButton.style.visibility = "hidden";
				leftButton.style.visibility = "hidden";
				rightButton.style.visibility = "hidden";
				if(window.playlists.merge.from.element !== mergeButton && window.playlists.merge.to.element !== mergeButton)
					mergeButton.style.visibility = "hidden";
			}

			playlist.appendChild(deleteButton);
			playlist.appendChild(editButton);
			playlist.appendChild(leftButton);
			playlist.appendChild(rightButton);
			playlist.appendChild(mergeButton);
		}
		else {
			playlist.id = "maploadwindowplaylistnew";
			playlist.style.display = window.playlists.edit ? "inline-block" : "none";
		}
		playlist.appendChild(image);
		playlist.appendChild(title);
		playlist.appendChild(description);
		playlist.onclick = (e) => {
			if(list.maps !== "new") {
				if(window.playlists.edit || window.playlists.merge.enabled) return;
				while(document.getElementById("maploadwindowmapscontainer").firstChild) {
					document.getElementById("maploadwindowmapscontainer").firstChild.remove();
				}
				document.getElementById("maploadwindowtoolbox").style.display = "none";
				document.getElementById("maploadwindowmapscontainer").style.removeProperty("bottom");
				document.getElementById("maploadwindowmapscontainer").style.removeProperty("height");
				document.getElementById("maploadwindowstatustext").style.visibility = "inherit";
				if((list.maps.length + list.b1maps.length) == 0) {
					document.getElementById("maploadwindowstatustext").textContent = "No Maps";
					return;
				}
				if(e.target.classList.contains("brownButton")) return;
				let foundBonk2Maps = 0;
				let addMaps = (offset = 0) => {
					$.post("https://bonk2.io/scripts/map_getfave.php", {
						token: token,
						startingfrom: offset * 32
					}).done(function (h0i, Y0i) {
						if (arguments[0].r != "success") {
							document.getElementById("maploadwindowstatustext").style.visibility = "inherit";
							document.getElementById("maploadwindowstatustext").textContent = "Fetch error";
						}
						else {
							let filteredMapList = arguments[0];
							filteredMapList.maps = filteredMapList.maps.filter(e => {if(list.maps.includes(e.id)) {return e;}}).sort().slice(0, list.maps.length);
							document.getElementById("maploadwindowstatustext").style.visibility = "hidden";
							if(filteredMapList.maps.length > 0) {
								window.playlists.mapLoader(filteredMapList);
								foundBonk2Maps += filteredMapList.maps.length;
							}
							if(arguments[0].more && foundBonk2Maps < list.maps.length) {
								addMaps(offset + 1);
							}
							else if(list.b1maps.length > 0) {
								window.playlists.mapLoader({r: "success", maps: list.b1maps, more: false}, 0);
								window.playlists.setMapsLoadFinished(true);
							}
							else {
								window.playlists.setMapsLoadFinished(true);
							}
						}
					});
				}
				addMaps();
			}
			else if(list.maps === "new" && !window.playlists.editing) {
				playlist.style.display = "none";
				document.getElementById("maploadwindowmapscontainer").insertBefore(playlistCreator(), playlist);
			}
		}
	}

	document.getElementById("maploadwindowstatustext").style.visibility = "hidden";
}
patchCounter = 0;
const patch = (a, b) => {
	c = newStr;
	newStr = newStr.replace(a, b);
	//console.log(`Patch ${patchCounter++}: ${c === newStr ? 'fail' : 'success'}`);
	return c !== newStr;
}

const categoryFunc = newStr.match(/[A-Za-z0-9\$_]{3}\([A-Za-z0-9\$_]{3}\.[A-Za-z0-9\$_]{3}\([0-9]*\),true\)/)[0].substr(0,3);
patch(`function ${categoryFunc}`, `window.playlists.categoryFunc=${categoryFunc};function ${categoryFunc}`);

//Get map loader
let mapLoader = newStr.match(/maploadwindowsearchinput.{0,200}else if\([A-Za-z0-9\$_]{3}\[0\]\[0\]\[[A-Za-z0-9\$_]{3}\[[0-9]+\][[0-9]+\]\] == [A-Za-z0-9\$_]{3}\.[A-Za-z0-9\$_]{3}\([0-9]+\)\)\{[A-Za-z0-9\$_]{3}\([A-Za-z0-9\$_]{3}\[0\]\[0\]\);[A-Za-z0-9\$_]{3}\[[0-9]+\]=[A-Za-z0-9\$_]{3}\[0\]\[0\]\[[A-Za-z0-9\$_]{3}\[[0-9]+\]\[[0-9]+\]\];\}\}\)/g)[0].match(/[A-Za-z0-9\$_]{3}\([A-Za-z0-9\$_]{3}\[0\]\[0\]\);/)[0].slice(0, 3);

patch(`function ${mapLoader}`, `window.playlists.mapLoader=${mapLoader};function ${mapLoader}`);

//Get token
patch('[1,10000,25000,100000,500000,8000000,5000000000];', '[1,10000,25000,100000,500000,8000000,5000000000];' + "window.playlists.setToken(arguments[0]);");

//Mapload ready variable
let readyVar = newStr.match(/\+ 1000 && [A-Za-z0-9\$_]{3}\[[0-9+]+\]/)[0].split(" ")[3];
patch('[1,10000,25000,100000,500000,8000000,5000000000];', '[1,10000,25000,100000,500000,8000000,5000000000];' + `window.playlists.setMapsLoaded=a=>{${readyVar}=a;};`);

//Mapload finish variable
let finishVar = newStr.match(/false\);\}else \{if\([A-Za-z0-9\$_]{3}\[[0-9]+\]\)\{/)[0].split(/[\(\)]/)[2];
//Inverting the value is important
patch('[1,10000,25000,100000,500000,8000000,5000000000];', '[1,10000,25000,100000,500000,8000000,5000000000];' + `window.playlists.setMapsFinished=a=>{${finishVar}=!a;};`);

//Get some useful functions
let menuRegex = newStr.match(/== 13\){...\(\);}}/)[0];
patch(menuRegex, menuRegex + "window.playlists.menuFunctions = this;");
let toolRegex = newStr.match(/=new [A-Za-z0-9\$_]{1,3}\(this,[A-Za-z0-9\$_]{1,3}\[0\]\[0\],[A-Za-z0-9\$_]{1,3}\[0\]\[1\]\);/);
patch(toolRegex, toolRegex + "window.playlists.toolFunctions = this;");

//Big class
let bigClass = newStr.match(/[A-Z]\[[A-Za-z0-9\$_]{1,3}\[[0-9]+\]\[[0-9]+\]\]\([A-Za-z0-9\$_]{1,3}\[0\]\[0\]\);[A-Za-z0-9\$_]{1,3}\[[0-9]+\]\[[A-Za-z0-9\$_]{1,3}\[[0-9]+\]\[[0-9]+\]\]\([A-Za-z0-9\$_]{1,3}\[[0-9]+\],{m:/)[0][0];
patch(`function ${bigClass}(){}`, `function ${bigClass}(){};window.playlists.bigClass=${bigClass};`);


	console.log("Bonk Playlists injector run");
	return newStr;
}

if(!window.bonkCodeInjectors) window.bonkCodeInjectors = [];
window.bonkCodeInjectors.push(bonkCode => {
	try {
		return playlistsInjector(bonkCode);
	} catch (error) {
		console.error("Trapi's Client playlists injector failed:", error);
		return bonkCode;
	}
});

console.log("Bonk Playlists injector loaded");


// v9.0 cleanup: remove Room category and make Lobby Controls the single match/room page.
(function(){
    try {
        const V90 = "9.0.1";
        const $ = (id) => document.getElementById(id);
        const norm = (s) => String(s || "").replace(/[▲▼]/g, "").trim().toUpperCase();

        // Version/title cleanup.
        const setVersion = () => {
            const ver = $("hostPlayerMenuClientVersion");
            if(ver) ver.textContent = "v" + V90;
            const title = $("hostPlayerMenuClientTitle");
            if(title) title.textContent = "Trapi's Client";
        };

        // Hide the old Room/Host Tools category completely. Its useful controls now live in Lobby Controls.
        const hideRoomCategory = () => {
            const roomIds = [
                "hostPlayerMenuHostToolsToggle",
                "hostPlayerMenuHostToolsDropdown"
            ];
            roomIds.forEach(id => {
                const el = $(id);
                if(el) {
                    el.style.display = "none";
                    el.setAttribute("data-trapis-v90-hidden-room", "true");
                }
            });

            // Clean any generated main-menu buttons that still say Room/Room Tools/Host Tools.
            const main = $("hostPlayerMenuMainDropdown") || $("hostPlayerMenuControls");
            if(main) {
                [...main.querySelectorAll(".newbonklobby_settings_button, .trapisV87Btn, .trapisV88Btn, .trapisV89Btn, button, div")].forEach(el => {
                    if(!el || el.id === "hostPlayerMenuHostToolsDropdown") return;
                    const t = norm(el.textContent);
                    if(t === "ROOM" || t === "ROOM TOOLS" || t === "HOST TOOLS") {
                        el.style.display = "none";
                        el.setAttribute("data-trapis-v90-hidden-room", "true");
                    }
                    if(t === "MATCH" || t === "MATCH SETTINGS" || t === "TEAM TOOLS") {
                        if(el.id !== "hostPlayerMenuTeamToolsDropdown") el.textContent = "LOBBY CONTROLS ▼";
                    }
                });
            }

            const match = $("hostPlayerMenuTeamToolsToggle");
            if(match) {
                match.textContent = "LOBBY CONTROLS ▼";
                match.style.display = "";
            }
        };

        const relabelLegacyButtons = () => {
            const labels = {
                hostPlayerMenuAkToggleButton: "GUEST AUTOKICK: OFF",
                hostPlayerMenuSwapTeamsButton: "SWAP R/B POS.",
                hostPlayerMenuAutoBalanceButton: "AUTO BALANCE: OFF",
                hostPlayerMenuCaptainsButton: "CAPTAINS MODE: OFF",
                hostPlayerMenuFreejoin: "FREEJOIN: OFF"
            };
            for(const [id, label] of Object.entries(labels)) {
                const el = $(id);
                if(el && !/ON/i.test(el.textContent)) el.textContent = label;
            }
        };

        // Remove duplicate/legacy Room route from all currently exposed popup routers.
        const installRouting = () => {
            const openLobby = () => {
                if(window.bonkHost.openMatchSettingsV89) {
                    window.bonkHost.openMatchSettingsV89(true);
                    setTimeout(() => {
                        const title = $("trapisClientInfoPopupTitle") || document.querySelector("#trapisClientInfoPopup h2, #trapisClientInfoPopup .trapisPopupTitle");
                        if(title && /MATCH SETTINGS/i.test(title.textContent || "")) title.textContent = "LOBBY CONTROLS";
                    }, 0);
                    return true;
                }
                return false;
            };

            const oldV88 = window.bonkHost.openV88Page;
            window.bonkHost.openV88Page = function(page) {
                if(page === "match" || page === "room" || page === "team" || page === "lobby") return openLobby();
                return oldV88 ? oldV88.apply(this, arguments) : null;
            };
            window.bonkHost.openV87Page = window.bonkHost.openV88Page;

            const oldCat = window.bonkHost.openCategoryPopupV86;
            window.bonkHost.openCategoryPopupV86 = function(def, force) {
                const key = def && def.key;
                if(key === "room") return openLobby();
                if(key === "team" || key === "match" || key === "lobby") return openLobby();
                return oldCat ? oldCat.apply(this, arguments) : null;
            };

            const oldPopup = window.bonkHost.showClientPopup;
            if(oldPopup && !oldPopup.__trapisV90Wrapped) {
                const wrapped = function(title, content) {
                    if(/^(MATCH|MATCH SETTINGS|ROOM|ROOM TOOLS|HOST TOOLS)$/i.test(String(title || "").trim())) {
                        title = "LOBBY CONTROLS";
                    }
                    return oldPopup.call(this, title, content);
                };
                wrapped.__trapisV90Wrapped = true;
                window.bonkHost.showClientPopup = wrapped;
            }
        };

        // If the v8.9 Lobby page exists, standardize title and polish cards without rewriting all handlers.
        const wrapLobbyTitle = () => {
            const old = window.bonkHost.openMatchSettingsV89;
            if(!old || old.__trapisV90Wrapped) return;
            const wrapped = function(force) {
                const result = old.apply(this, arguments);
                setTimeout(() => {
                    const popup = $("trapisClientInfoPopup");
                    if(popup) {
                        const candidates = [
                            $("trapisClientInfoPopupTitle"),
                            popup.querySelector("h2"),
                            popup.querySelector(".trapisPopupTitle"),
                            popup.querySelector(".trapisV89Title")
                        ].filter(Boolean);
                        candidates.forEach(el => {
                            if(/MATCH SETTINGS|MATCH|ROOM/i.test(el.textContent || "")) el.textContent = "LOBBY CONTROLS";
                        });
                    }
                }, 0);
                return result;
            };
            wrapped.__trapisV90Wrapped = true;
            window.bonkHost.openMatchSettingsV89 = wrapped;
        };

        // CSS polish: fixed popup width, same card feel, hide old Room surfaces.
        const css = document.createElement("style");
        css.id = "trapisClientV90Css";
        css.textContent = `
            #hostPlayerMenuHostToolsToggle,
            #hostPlayerMenuHostToolsDropdown,
            [data-trapis-v90-hidden-room="true"] { display:none !important; }

            #hostPlayerMenuTeamToolsToggle { display:block !important; }

            #trapisClientInfoPopup {
                width: 410px !important;
                min-width: 410px !important;
                max-width: 410px !important;
                background: #2f2927 !important;
                color: #fff !important;
                border: 1px solid rgba(255,255,255,.16) !important;
                border-radius: 9px !important;
                box-shadow: 0 12px 30px rgba(0,0,0,.38) !important;
                font-family: futurept_b1, Arial, sans-serif !important;
            }
            #trapisClientInfoPopupBody {
                max-height: calc(72vh - 40px) !important;
                overflow-y: auto !important;
                overflow-x: hidden !important;
                white-space: normal !important;
            }
            .trapisV87Card,
            .trapisV88Card,
            .trapisV89Card,
            .trapisV90Card {
                background: rgba(255,255,255,.06) !important;
                border: 1px solid rgba(255,255,255,.12) !important;
                border-radius: 7px !important;
                padding: 9px !important;
                margin-bottom: 9px !important;
                box-sizing: border-box !important;
            }
            .trapisV87Btn,
            .trapisV88Btn,
            .trapisV89Btn,
            .trapisV90Btn {
                border: 0 !important;
                border-radius: 6px !important;
                background: #8b6f61 !important;
                color: #fff !important;
                font-family: futurept_b1, Arial, sans-serif !important;
                font-weight: 800 !important;
                cursor: pointer !important;
                box-sizing: border-box !important;
            }
            .trapisV87Btn:hover,
            .trapisV88Btn:hover,
            .trapisV89Btn:hover,
            .trapisV90Btn:hover {
                filter: brightness(1.12) !important;
            }
        `;
        if(!$("trapisClientV90Css")) document.head.appendChild(css);

        setVersion();
        hideRoomCategory();
        relabelLegacyButtons();
        installRouting();
        wrapLobbyTitle();

        // Some of the old UI rebuilds after clicks; keep this lightweight cleanup alive.
        if(!window.bonkHost.v90CleanupWatcher) {
            window.bonkHost.v90CleanupWatcher = setInterval(() => {
                try {
                    setVersion();
                    hideRoomCategory();
                    relabelLegacyButtons();
                } catch(e) {}
            }, 700);
        }

        // Open Lobby Controls from old Match button label.
        const match = $("hostPlayerMenuTeamToolsToggle");
        if(match && !match.__trapisV90LobbyClick) {
            match.__trapisV90LobbyClick = true;
            match.addEventListener("click", (e) => {
                if(window.bonkHost.openMatchSettingsV89) {
                    e.preventDefault();
                    e.stopImmediatePropagation();
                    window.bonkHost.openMatchSettingsV89(true);
                }
            }, true);
        }

        console.log("Trapi's Client v9.0.1 cleanup loaded");
    } catch(e) {
        console.warn("Trapi's Client v9.0 cleanup failed", e);
    }
})();


// v9.0.1 safety pass: keep v9.0 stable internals, but enforce visible Lobby Controls and no Room header.
(function(){
    try {
        const $ = (id) => document.getElementById(id);
        const clean = () => {
            const ver = $("hostPlayerMenuClientVersion");
            if(ver) ver.textContent = "v9.0.1";
            const title = $("hostPlayerMenuClientTitle");
            if(title) title.textContent = "Trapi's Client";

            const lobby = $("hostPlayerMenuTeamToolsToggle");
            if(lobby) {
                lobby.textContent = "LOBBY CONTROLS ▼";
                lobby.style.display = "";
            }

            const room = $("hostPlayerMenuHostToolsToggle");
            if(room) {
                room.style.display = "none";
                room.setAttribute("data-trapis-v901-hidden-room", "true");
            }
            const roomDrop = $("hostPlayerMenuHostToolsDropdown");
            if(roomDrop) {
                roomDrop.style.display = "none";
                roomDrop.setAttribute("data-trapis-v901-hidden-room", "true");
            }

            const main = $("hostPlayerMenuMainDropdown") || $("hostPlayerMenuControls");
            if(main) {
                [...main.querySelectorAll(".newbonklobby_settings_button, .trapisV87Btn, .trapisV88Btn, .trapisV89Btn, .trapisV90Btn, button, div")].forEach(el => {
                    const txt = String(el.textContent || "").replace(/[▲▼]/g, "").trim().toUpperCase();
                    if(txt === "MATCH" || txt === "MATCH SETTINGS" || txt === "TEAM TOOLS") {
                        el.textContent = "LOBBY CONTROLS ▼";
                    }
                    if(txt === "ROOM" || txt === "ROOM TOOLS" || txt === "HOST TOOLS") {
                        el.style.display = "none";
                        el.setAttribute("data-trapis-v901-hidden-room", "true");
                    }
                });
            }
        };

        const css = document.createElement("style");
        css.id = "trapisClientV901Css";
        css.textContent = `
            #hostPlayerMenuHostToolsToggle,
            #hostPlayerMenuHostToolsDropdown,
            [data-trapis-v901-hidden-room="true"] {
                display: none !important;
            }
        `;
        if(!$("trapisClientV901Css")) document.head.appendChild(css);

        clean();
        if(!window.bonkHost.v901CleanupWatcher) {
            window.bonkHost.v901CleanupWatcher = setInterval(() => {
                try { clean(); } catch(e) {}
            }, 500);
        }
        console.log("Trapi's Client v9.0.1 visible Room cleanup loaded");
    } catch(e) {
        console.warn("Trapi's Client v9.0.1 cleanup failed", e);
    }
})();


// v9.0.2 Maps / Playlists cleanup: map-only Smart Shuffle and playlist manager buttons.
(function(){
    const VERSION = "v9.0.5";
    const $ = (id) => document.getElementById(id);
    const esc = (s) => String(s == null ? "" : s).replace(/[&<>"]/g, c => ({"&":"&amp;","<":"&lt;",">":"&gt;","\"":"&quot;"}[c]));
    const status = (msg) => {
        try {
            if(window.bonkHost && window.bonkHost.menuFunctions && window.bonkHost.menuFunctions.showStatusMessage) {
                window.bonkHost.menuFunctions.showStatusMessage("* " + msg, "#b53030", false);
            } else console.log("[Trapi's Client] " + msg);
        } catch(e) { console.log("[Trapi's Client] " + msg); }
    };
    const getSelectedPlaylistIndex = () => {
        let lists = (window.playlists && Array.isArray(window.playlists.playlists)) ? window.playlists.playlists : [];
        let idx = parseInt(localStorage.getItem("trapisClientSelectedPlaylistIndex") || "0", 10);
        if(!Number.isFinite(idx) || idx < 0) idx = 0;
        if(lists.length && idx >= lists.length) idx = lists.length - 1;
        if(!lists.length) idx = -1;
        return idx;
    };
    const setSelectedPlaylistIndex = (idx) => {
        const lists = (window.playlists && Array.isArray(window.playlists.playlists)) ? window.playlists.playlists : [];
        if(!lists.length) idx = -1;
        else idx = ((idx % lists.length) + lists.length) % lists.length;
        localStorage.setItem("trapisClientSelectedPlaylistIndex", String(Math.max(0, idx)));
        return idx;
    };
    const getSelectedPlaylist = () => {
        const lists = (window.playlists && Array.isArray(window.playlists.playlists)) ? window.playlists.playlists : [];
        const idx = getSelectedPlaylistIndex();
        return idx >= 0 ? { list: lists[idx], index: idx, lists } : { list: null, index: -1, lists };
    };
    const savePlaylists = () => {
        try { if(window.playlists && window.playlists.savePlaylists) window.playlists.savePlaylists(window.playlists.playlists || []); } catch(e) { console.warn(e); }
    };
    const getGameSettings = () => {
        try {
            if(window.playlists && window.playlists.toolFunctions && window.playlists.toolFunctions.getGameSettings) return window.playlists.toolFunctions.getGameSettings();
            if(window.bonkHost && window.bonkHost.toolFunctions && window.bonkHost.toolFunctions.getGameSettings) return window.bonkHost.toolFunctions.getGameSettings();
        } catch(e) {}
        return null;
    };
    const getCurrentMapInfo = () => {
        const gs = getGameSettings();
        const m = gs && gs.map && gs.map.m;
        if(!m || m.dbid == null) return null;
        return { gs, m, id: m.dbid, name: m.n || "Current Map", author: m.a || "Unknown", dbv: m.dbv || (gs.map && gs.map.dbv) || 2 };
    };
    const ensureSelectedPlaylist = () => {
        const selected = getSelectedPlaylist();
        if(!selected.list) {
            status("Create or select a playlist first");
            return null;
        }
        selected.list.maps = Array.isArray(selected.list.maps) ? selected.list.maps : [];
        selected.list.b1maps = Array.isArray(selected.list.b1maps) ? selected.list.b1maps : [];
        return selected;
    };
    const createPlaylist = () => {
        if(!window.playlists || !Array.isArray(window.playlists.playlists)) { status("Open playlists once first so the playlist database can load"); return; }
        const name = prompt("Playlist name:", "New Playlist");
        if(!name || !name.trim()) return;
        const list = { name: name.trim(), description: "Created from Trapi's Client", maps: [], b1maps: [] };
        window.playlists.playlists.push(list);
        setSelectedPlaylistIndex(window.playlists.playlists.length - 1);
        savePlaylists();
        status("Created playlist: " + list.name);
        renderMapsPage();
    };
    const addCurrentMap = () => {
        const selected = ensureSelectedPlaylist();
        const info = getCurrentMapInfo();
        if(!selected || !info) { status("No current map detected"); return; }
        const list = selected.list;
        if(info.dbv === 2) {
            if(!list.maps.includes(info.id)) list.maps.push(info.id);
        } else {
            if(!list.b1maps.map(x => x && x.id).includes(info.id)) {
                list.b1maps.push({ id: info.id, name: info.name, authorname: info.author, leveldata: "", vu: info.m.vu, vd: info.m.vd, publisheddate: info.m.date });
            }
        }
        savePlaylists();
        status("Added current map to " + list.name);
        renderMapsPage();
    };
    const removeCurrentMap = () => {
        const selected = ensureSelectedPlaylist();
        const info = getCurrentMapInfo();
        if(!selected || !info) { status("No current map detected"); return; }
        const list = selected.list;
        const before = list.maps.length + list.b1maps.length;
        list.maps = list.maps.filter(id => id !== info.id);
        list.b1maps = list.b1maps.filter(m => !m || m.id !== info.id);
        savePlaylists();
        status((before === list.maps.length + list.b1maps.length ? "Current map was not in " : "Removed current map from ") + list.name);
        renderMapsPage();
    };
    const openPlaylistManager = () => {
        const btn = $("hostPlayerMenuOpenPlaylistsButton");
        if(btn) btn.click();
        else status("Playlist manager button missing");
    };
    const loadSelectedPlaylist = () => {
        const selected = getSelectedPlaylist();
        if(!selected.list) { status("No playlist selected"); return; }
        openPlaylistManager();
        setTimeout(() => {
            try {
                const cards = [...document.querySelectorAll(".maploadwindowplaylistdiv")];
                const card = cards.find(c => (c.innerText || "").toLowerCase().includes(String(selected.list.name || "").toLowerCase()));
                if(card) { card.click(); status("Loaded playlist: " + selected.list.name); }
                else status("Open Playlists loaded. Click " + selected.list.name + " if it does not open automatically.");
            } catch(e) { status("Open Playlists loaded. Click " + selected.list.name + " manually."); }
        }, 400);
    };
    const quickplaySmartOn = () => localStorage.getItem("trapisQuickplayMapSmartShuffle") !== "false";
    const setQuickplaySmart = (on) => {
        localStorage.setItem("trapisQuickplayMapSmartShuffle", on ? "true" : "false");
        if(window.bonkHost && window.bonkHost.quickplay && window.bonkHost.quickplay.queue && window.bonkHost.quickplay.queue.length && window.bonkHost.shuffleQuickplayQueue) window.bonkHost.shuffleQuickplayQueue();
        renderMapsPage();
    };
    const installQuickplayOverrides = () => {
        if(!window.bonkHost || window.bonkHost.v902QuickplayOverrides) return;
        window.bonkHost.v902QuickplayOverrides = true;
        const oldUpdate = window.bonkHost.updateQuickplayButtons;
        window.bonkHost.updateQuickplayButtons = function(){
            try { oldUpdate && oldUpdate.apply(this, arguments); } catch(e) {}
            const qp = window.bonkHost.quickplay || {};
            const qpBtn = $("hostPlayerMenuQuickplayButton");
            const roundsBtn = $("hostPlayerMenuQpRoundsButton");
            const shuffleBtn = $("hostPlayerMenuQpShuffleButton");
            const mapsToggle = $("hostPlayerMenuPlaylistsToggle");
            if(qpBtn) qpBtn.textContent = "QUICKPLAY: " + (qp.state === "on" ? "ON" : "OFF");
            if(roundsBtn) roundsBtn.textContent = "WINS/QP: " + (qp.roundsPer || 3);
            if(shuffleBtn) shuffleBtn.textContent = "SMART SHUFFLE: " + (quickplaySmartOn() ? "ON" : "OFF");
            if(mapsToggle && /MAPS/i.test(mapsToggle.textContent || "")) mapsToggle.textContent = "MAPS / PLAYLISTS ▼";
        };
        const oldShuffleArray = window.bonkHost.shuffleArray || ((arr) => arr.slice().sort(() => Math.random() - 0.5));
        window.bonkHost.shuffleQuickplayQueue = function(){
            const qp = window.bonkHost.quickplay;
            if(!qp) return;
            if(qp.queue.length === 0 && !(window.bonkHost.captureQuickplayQueue && window.bonkHost.captureQuickplayQueue())) return;
            const recent = (window.bonkHost.extraFeatures && window.bonkHost.extraFeatures.mapHistoryNames ? window.bonkHost.extraFeatures.mapHistoryNames : []).slice(0, Math.min(5, Math.max(1, qp.queue.length - 1)));
            let queue = oldShuffleArray(qp.queue);
            if(quickplaySmartOn()) {
                const notRecent = queue.filter(card => !recent.includes(window.bonkHost.getQuickplayCardName(card)));
                const recentOnes = queue.filter(card => recent.includes(window.bonkHost.getQuickplayCardName(card)));
                queue = notRecent.concat(recentOnes);
            }
            qp.queue = queue;
            qp.index = -1;
            qp.roundCounter = 0;
            status((quickplaySmartOn() ? "Smart shuffled" : "Shuffled") + " Quickplay maps");
            window.bonkHost.updateQuickplayButtons && window.bonkHost.updateQuickplayButtons();
        };
        window.bonkHost.updateQuickplayButtons && window.bonkHost.updateQuickplayButtons();
    };
    const renderPopup = (title, node) => {
        let old = $("trapisClientInfoPopup");
        if(old) old.remove();
        const box = document.createElement("div");
        box.id = "trapisClientInfoPopup";
        box.className = "trapisV88Popup trapisV902Popup";
        box.innerHTML = `<div id="trapisClientInfoPopupTop" class="trapisV88PopupTop"><span>${esc(title)}</span><button id="trapisClientInfoPopupClose" class="trapisV88Close" title="Close">×</button></div><div id="trapisClientInfoPopupBody" class="trapisV88PopupBody"></div>`;
        const body = box.querySelector("#trapisClientInfoPopupBody");
        if(node instanceof Node) body.appendChild(node); else body.innerHTML = String(node || "");
        (document.getElementById("pagecontainer") || document.body || document.documentElement).appendChild(box);
        const close = $("trapisClientInfoPopupClose");
        if(close) close.onclick = () => box.remove();
    };
    const card = (title, html) => `<section class="trapisV88Card"><h3>${esc(title)}</h3>${html || ""}</section>`;
    const line = (label, value) => `<div class="trapisV88Line"><span>${esc(label)}</span><b>${esc(value)}</b></div>`;
    const makeBtn = (label, fn, sub) => {
        const b = document.createElement("button");
        b.className = "trapisV88Btn trapisV902Btn";
        b.innerHTML = `<span>${esc(label)}</span>${sub ? `<small>${esc(sub)}</small>` : ""}`;
        b.onclick = (e) => { e.preventDefault(); e.stopPropagation(); try { fn && fn(); } catch(err) { renderPopup("ACTION ERROR", `<pre>${esc(err && err.message ? err.message : err)}</pre>`); } };
        return b;
    };
    const makeInfoBtn = (title, lines) => {
        const b = document.createElement("button");
        b.className = "trapisV902InfoBtn";
        b.textContent = "ⓘ";
        b.title = title;
        b.onclick = (e) => { e.preventDefault(); e.stopPropagation(); renderPopup(title, `<div class="trapisV88Page">${card(title, `<div class="trapisV88TextBlock">${lines.map(l => "• " + esc(l)).join("<br>")}</div>`)}</div>`); };
        return b;
    };
    function renderMapsPage(){
        installQuickplayOverrides();
        const qp = (window.bonkHost && window.bonkHost.quickplay) || { state:"off", roundsPer:3, queue:[] };
        const selected = getSelectedPlaylist();
        const mapInfo = getCurrentMapInfo();
        const wrap = document.createElement("div");
        wrap.className = "trapisV88Page trapisV902MapsPage";
        wrap.innerHTML =
            card("Quickplay", line("Quickplay", qp.state === "on" ? "ON" : "OFF") + line("Wins/QP", qp.roundsPer || 3) + line("Smart Shuffle", quickplaySmartOn() ? "ON" : "OFF")) +
            card("Current Playlist", line("Selected", selected.list ? selected.list.name : "None") + line("Maps", selected.list ? ((selected.list.maps || []).length + (selected.list.b1maps || []).length) : 0) + line("Current Map", mapInfo ? mapInfo.name : "Unknown")) +
            card("Tools", "");
        const grid = document.createElement("div");
        grid.className = "trapisV88Grid trapisV902Grid";
        grid.appendChild(makeBtn("Quickplay: " + (qp.state === "on" ? "ON" : "OFF"), () => { window.bonkHost.setQuickplayState(qp.state === "on" ? "off" : "on"); renderMapsPage(); }, "Toggle map rotation"));
        grid.appendChild(makeBtn("Wins/QP: " + (qp.roundsPer || 3), () => { const old = $("hostPlayerMenuQpRoundsButton"); if(old) old.click(); renderMapsPage(); }, "Click to cycle"));
        const smartWrap = document.createElement("div");
        smartWrap.className = "trapisV902SmartWrap";
        smartWrap.appendChild(makeBtn("Smart Shuffle: " + (quickplaySmartOn() ? "ON" : "OFF"), () => setQuickplaySmart(!quickplaySmartOn()), "Map variety only"));
        smartWrap.appendChild(makeInfoBtn("SMART SHUFFLE", ["Avoids recently played maps", "Prevents immediate map repeats when possible", "Prioritizes maps not played recently", "Falls back to normal randomization if the playlist is too small", "Only affects map order, not players or teams"]));
        grid.appendChild(smartWrap);
        grid.appendChild(makeBtn("Open Manager", openPlaylistManager, "Bonk playlist window"));
        grid.appendChild(makeBtn("< Playlist", () => { setSelectedPlaylistIndex(getSelectedPlaylistIndex() - 1); renderMapsPage(); }, selected.lists.length ? "Previous saved playlist" : "No playlists"));
        grid.appendChild(makeBtn("Playlist >", () => { setSelectedPlaylistIndex(getSelectedPlaylistIndex() + 1); renderMapsPage(); }, selected.lists.length ? "Next saved playlist" : "No playlists"));
        grid.appendChild(makeBtn("Load Current Playlist", loadSelectedPlaylist, selected.list ? selected.list.name : "None selected"));
        grid.appendChild(makeBtn("Create Playlist", createPlaylist, "Name and save"));
        grid.appendChild(makeBtn("Add Current Map", addCurrentMap, "To selected playlist"));
        grid.appendChild(makeBtn("Remove Current Map", removeCurrentMap, "From selected playlist"));
        grid.appendChild(makeBtn("Previous Map", () => { const b = $("hostPlayerMenuQpPrevButton"); if(b) b.click(); }, "Quickplay history"));
        grid.appendChild(makeBtn("Next Map", () => { const b = $("hostPlayerMenuQpNextButton"); if(b) b.click(); }, "Quickplay history"));
        wrap.querySelector(".trapisV88Card:last-child").appendChild(grid);
        renderPopup("MAPS / PLAYLISTS", wrap);
    }
    const installPopupOverride = () => {
        if(!window.bonkHost || window.bonkHost.v902MapsInstalled) return;
        window.bonkHost.v902MapsInstalled = true;
        installQuickplayOverrides();
        const oldOpenV88 = window.bonkHost.openV88Page;
        window.bonkHost.openV88Page = function(page){
            if(page === "maps") return renderMapsPage();
            return oldOpenV88 ? oldOpenV88.apply(this, arguments) : undefined;
        };
        const oldCat = window.bonkHost.openCategoryPopupV86;
        window.bonkHost.openCategoryPopupV86 = function(def, force){
            const key = def && def.key;
            if(key === "maps" || key === "playlists") return renderMapsPage();
            return oldCat ? oldCat.apply(this, arguments) : undefined;
        };
        window.bonkHost.renderMapsPlaylistsV902 = renderMapsPage;
    };
    const polish = () => {
        const ver = $("hostPlayerMenuClientVersion"); if(ver) ver.textContent = VERSION;
        const maps = $("hostPlayerMenuPlaylistsToggle"); if(maps && /MAPS|PLAYLISTS/i.test(maps.textContent || "")) maps.textContent = "MAPS / PLAYLISTS ▼";
        if(window.bonkHost && window.bonkHost.updateQuickplayButtons) window.bonkHost.updateQuickplayButtons();
    };
    const css = document.createElement("style");
    css.id = "trapisClientV902Css";
    css.textContent = `
        .trapisV902Grid { grid-template-columns:1fr 1fr !important; }
        .trapisV902SmartWrap { display:grid; grid-template-columns:1fr 34px; gap:6px; }
        .trapisV902SmartWrap .trapisV88Btn { width:100%; }
        .trapisV902InfoBtn { min-height:42px; border:0; border-radius:5px; background:#8b6f61; color:#fff; font-family:futurept_b1,Arial,sans-serif; font-size:16px; font-weight:900; cursor:pointer; box-shadow:0 2px 0 rgba(0,0,0,.22); }
        .trapisV902InfoBtn:hover,.trapisV902Btn:hover { filter:brightness(1.1); }
        #trapisClientInfoPopup.trapisV902Popup { width:390px !important; max-height:72vh !important; }
        #trapisClientInfoPopup .trapisV88Card { background:#352f2c !important; border:1px solid rgba(255,255,255,.12) !important; border-radius:7px !important; padding:9px !important; margin-bottom:9px !important; }
    `;
    const boot = () => {
        try {
            if(!$("trapisClientV902Css")) document.head.appendChild(css);
            installPopupOverride();
            polish();
        } catch(e) { console.warn("Trapi's Client v9.0.4 maps patch waiting", e); }
    };
    boot();
    if(!window.trapisV902MapsInterval) window.trapisV902MapsInterval = setInterval(boot, 750);
})();


/* ===== Trapi's Client v9.0.3 - Version authority / old patch clash guard ===== */
(function(){
    const AUTHORITATIVE_VERSION = "v9.0.5";
    const setVersion = () => {
        try {
            const el = document.getElementById("hostPlayerMenuClientVersion");
            if(el && el.textContent !== AUTHORITATIVE_VERSION) el.textContent = AUTHORITATIVE_VERSION;
            if(window.bonkHost) window.bonkHost.clientVersion = AUTHORITATIVE_VERSION;
        } catch(e) {}
    };
    const boot = () => {
        setVersion();
        try {
            const el = document.getElementById("hostPlayerMenuClientVersion");
            if(el && !el.__trapisVersionLocked) {
                el.__trapisVersionLocked = true;
                new MutationObserver(setVersion).observe(el, { childList:true, characterData:true, subtree:true });
            }
        } catch(e) {}
    };
    boot();
    if(!window.trapisClientVersionLockInterval) {
        window.trapisClientVersionLockInterval = setInterval(boot, 300);
    }
})();

/* ===== Trapi's Client v9.0.5 - Focused Maps / Playlists label guard only ===== */
(function(){
    const MAPS_LABEL = "MAPS / PLAYLISTS";
    const $ = (id) => document.getElementById(id);
    const isOpen = (id) => {
        const el = $(id);
        if(!el) return false;
        try { return getComputedStyle(el).display !== "none" && getComputedStyle(el).visibility !== "hidden"; }
        catch(e) { return false; }
    };
    const mapsArrow = () => isOpen("hostPlayerMenuPlaylistsDropdown") ? " ▲" : " ▼";
    const normalizeMapsText = () => {
        try {
            const mapsToggle = $("hostPlayerMenuPlaylistsToggle");
            if(mapsToggle) {
                const wanted = MAPS_LABEL + mapsArrow();
                if(mapsToggle.textContent !== wanted) mapsToggle.textContent = wanted;
            }

            const popup = $("trapisClientInfoPopup");
            if(popup) {
                const titleCandidates = [
                    $("trapisClientInfoPopupTitle"),
                    popup.querySelector("h2"),
                    popup.querySelector(".trapisPopupTitle"),
                    popup.querySelector(".trapisV89Title")
                ].filter(Boolean);
                for(const el of titleCandidates) {
                    const raw = String(el.textContent || "").replace(/[▲▼]/g, "").trim();
                    if(/^(MAPS|PLAYLISTS|MAPS\s*[&/]\s*PLAYLISTS)$/i.test(raw) && el.textContent !== MAPS_LABEL) {
                        el.textContent = MAPS_LABEL;
                    }
                }
            }
        } catch(e) {}
    };
    const observe = (el) => {
        try {
            if(!el || el.__trapisMapsLabelObserved) return;
            el.__trapisMapsLabelObserved = true;
            new MutationObserver(normalizeMapsText).observe(el, { childList:true, characterData:true, subtree:true });
        } catch(e) {}
    };
    const boot = () => {
        normalizeMapsText();
        observe($("hostPlayerMenuPlaylistsToggle"));
        observe($("hostPlayerMenuPlaylistsDropdown"));
        observe($("trapisClientInfoPopup"));
    };
    boot();
    if(!window.trapisMapsLabelOnlyInterval) window.trapisMapsLabelOnlyInterval = setInterval(boot, 500);
})();


/* ===== Trapi's Client v9.0.7 - Focused Room Controls Return To Bonk Menu only ===== */
(function(){
    const AUTHORITATIVE_VERSION = "v9.0.7";
    const $ = (id) => document.getElementById(id);

    const isVisible = (el) => {
        if(!el) return false;
        try {
            const style = getComputedStyle(el);
            return style.display !== "none" && style.visibility !== "hidden" && style.opacity !== "0";
        } catch(e) { return false; }
    };

    const clickSafe = (el) => {
        try {
            if(!el) return false;
            el.click();
            return true;
        } catch(e) { return false; }
    };

    const cleanText = (el) => String(el && el.textContent || "").replace(/[▲▼]/g, "").trim().toUpperCase();

    const clickVisibleText = (matches) => {
        const wanted = matches.map(x => String(x).toUpperCase());
        const candidates = Array.from(document.querySelectorAll("button, .brownButton, .newbonklobby_settings_button"));
        for(const el of candidates) {
            if(!isVisible(el)) continue;
            const txt = cleanText(el);
            if(wanted.some(w => txt === w || txt.includes(w))) return clickSafe(el);
        }
        return false;
    };

    const confirmLeavePrompt = () => {
        const roots = ["leaveconfirmwindow", "hostleaveconfirmwindow", "newbonklobby_leaveconfirmwindow", "prettywindow", "genericdialog"]
            .map(id => $(id)).filter(Boolean);
        roots.push(...Array.from(document.querySelectorAll(".prettywindow, .windowShadow")));
        for(const root of roots) {
            if(!isVisible(root)) continue;
            const buttons = Array.from(root.querySelectorAll("button, .brownButton, .newbonklobby_settings_button"));
            const confirm = buttons.find(b => {
                const txt = cleanText(b);
                return txt === "YES" || txt === "OK" || txt === "LEAVE" || txt === "EXIT" || txt === "CONFIRM" || txt.includes("LEAVE") || txt.includes("EXIT");
            });
            if(clickSafe(confirm)) return true;
        }
        return false;
    };

    const clickExitOrBack = () => {
        return clickSafe($("pretty_top_exit")) ||
               clickSafe($("newbonklobby_backbutton")) ||
               clickSafe($("newbonklobby_exitbutton")) ||
               clickVisibleText(["EXIT", "LEAVE", "BACK"]);
    };

    window.bonkHost.returnToBonkMenu = function(){
        try {
            const amHost = !!(window.bonkHost.isCurrentHost && window.bonkHost.isCurrentHost());

            if(amHost) {
                // Host safety: keep the older host behavior, which returns to your room lobby instead of destroying the room.
                if(window.bonkHost.sendReturnToLobbyPacket && window.bonkHost.sendReturnToLobbyPacket()) {
                    if(window.bonkHost.menuFunctions) window.bonkHost.menuFunctions.showStatusMessage("* Returning to room lobby", "#b53030", false);
                    return;
                }
                clickExitOrBack();
                setTimeout(confirmLeavePrompt, 120);
                return;
            }

            // Non-host: leave the active game first, then leave the room/custom lobby if Bonk lands there.
            clickExitOrBack();
            setTimeout(confirmLeavePrompt, 100);
            setTimeout(confirmLeavePrompt, 275);

            setTimeout(() => {
                if(window.bonkHost.leaveRoomToCustomMenu) {
                    try { window.bonkHost.leaveRoomToCustomMenu(); } catch(e) {}
                }
                clickVisibleText(["CUSTOM GAMES", "CUSTOM GAME MENU", "LEAVE", "EXIT", "BACK", "RETURN"]);
                setTimeout(confirmLeavePrompt, 120);
            }, 700);

            setTimeout(() => {
                if(window.bonkHost.leaveRoomToCustomMenu) {
                    try { window.bonkHost.leaveRoomToCustomMenu(); } catch(e) {}
                }
                clickVisibleText(["CUSTOM GAMES", "CUSTOM GAME MENU", "LEAVE", "EXIT", "BACK", "RETURN"]);
                setTimeout(confirmLeavePrompt, 120);
            }, 1450);

            if(window.bonkHost.menuFunctions) window.bonkHost.menuFunctions.showStatusMessage("* Returning to Bonk menu", "#b53030", false);
        } catch(e) {
            console.warn("Trapi's Client: Return To Bonk Menu failed", e);
        }
    };

    const keepVersionCurrent = () => {
        const ver = $("hostPlayerMenuClientVersion");
        if(ver && ver.textContent !== AUTHORITATIVE_VERSION) ver.textContent = AUTHORITATIVE_VERSION;
        if(window.bonkHost) window.bonkHost.clientVersion = AUTHORITATIVE_VERSION;
    };

    keepVersionCurrent();
    if(!window.trapisV907VersionInterval) window.trapisV907VersionInterval = setInterval(keepVersionCurrent, 500);
})();