BounceClient

Enhanced Bloxd.io client with modern UI

// ==UserScript==
// @name         BounceClient
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Enhanced Bloxd.io client with modern UI
// @author       ninja + CyphrNX + marcus (Modified for BounceClient)
// @match        *://*.bloxd.io/*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';
    if (document.getElementById('bounceclient-root')) return;

    // --- Module definitions ---
    const Modules = {
        Blatant: { "Killaura": false, "Scaffold": false },
        Visual: { "ESP": false, "Coords": false },
        Legit: { "FPS Counter": false, "CPS Counter": false, "Keystrokes": false }
    };

    // Keybinds configuration
    const Keybinds = {
        "Killaura": null,
        "Scaffold": null,
        "ESP": null,
        "Coords": null
    };

    // Module configuration
    const Config = {
        Killaura: {
            delay: 50,
            range: 4.5,
            jitter: 0
        },
        Scaffold: {
            delay: 50,
            tower: false
        },
        ESP: {
            chestUpdateInterval: 3000,
            playerUpdateInterval: 100,
            wireframeColor: [0, 0.7, 1] // Cyan RGB
        },
        Coords: {
            updateInterval: 100
        }
    };

    // Load config from localStorage
    const savedConfig = localStorage.getItem('bounceclient-config');
    if (savedConfig) {
        try {
            Object.assign(Config, JSON.parse(savedConfig));
        } catch(e) {
            console.error("Failed to load config:", e);
        }
    }

    // Save config to localStorage
    function saveConfig() {
        localStorage.setItem('bounceclient-config', JSON.stringify(Config));
    }

    let currentCategory = "Blatant";
    let gameState = {};
    let injectionStatus = { injected: false, failed: false }; // Track injection status
    let injectionRetryInterval = null;
    let objectUtils = {
        keys(e) {
            var t = [], n = 0;
            for (var o in e) null != e && (t[n] = o, n++);
            return t;
        },
        values(e) {
            for (var t = this.keys(e), n = [], o = 0, i = 0; o < t.length;) {
                var a = e[t[o]];
                n[i] = a, i++, o++;
            }
            return n;
        }
    };

    // === GUI Setup ===
    const root = document.createElement("div");
    root.id = "bounceclient-root";
    document.body.appendChild(root);

    const style = document.createElement("style");
    style.textContent = `
    :root {
        --primary: #00d4ff;
        --primary-dark: #0099cc;
        --secondary: #7c4dff;
        --bg: rgba(15, 15, 30, 0.95);
        --card-bg: rgba(25, 25, 45, 0.8);
        --border: rgba(100, 100, 255, 0.3);
        --text: #ffffff;
        --text-secondary: rgba(255, 255, 255, 0.7);
    }
    
    #bounceclient-root * { 
        font-family: 'Segoe UI', system-ui, sans-serif; 
        box-sizing: border-box; 
        transition: all 0.2s ease;
    }

    #bounceclient-gui {
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 500px;
        max-width: 90vw;
        background: var(--bg);
        color: var(--text);
        border-radius: 16px;
        border: 1px solid var(--border);
        backdrop-filter: blur(10px);
        box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
        display: none;
        flex-direction: column;
        z-index: 999999;
        overflow: hidden;
    }
    
    #bounceclient-header {
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 16px 20px;
        background: linear-gradient(135deg, var(--primary), var(--secondary));
        border-bottom: 1px solid var(--border);
    }
    
    .title {
        font-weight: 800;
        font-size: 24px;
        text-shadow: 0 0 10px rgba(0, 212, 255, 0.5);
        letter-spacing: 1px;
    }
    
    .title sup {
        font-size: 14px;
        color: var(--text);
        opacity: 0.8;
    }
    
    #category-buttons {
        display: flex;
        gap: 8px;
        margin: 16px;
        margin-bottom: 0;
    }
    
    .cat-btn {
        flex: 1;
        padding: 10px;
        border-radius: 10px;
        border: 1px solid var(--border);
        background: var(--card-bg);
        color: var(--text-secondary);
        cursor: pointer;
        text-align: center;
        font-weight: 600;
        transition: all 0.2s;
    }
    
    .cat-btn:hover {
        background: rgba(255, 255, 255, 0.1);
        transform: translateY(-2px);
    }
    
    .cat-btn.on {
        background: linear-gradient(135deg, var(--primary), var(--secondary));
        color: white;
        border-color: transparent;
        box-shadow: 0 4px 12px rgba(0, 212, 255, 0.3);
    }
    
    #bounceclient-content {
        display: flex;
        padding: 16px;
        gap: 16px;
    }
    
    #bounceclient-mods {
        flex: 1;
        display: flex;
        flex-direction: column;
        gap: 12px;
    }
    
    .module {
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 14px 16px;
        border-radius: 12px;
        background: var(--card-bg);
        border: 1px solid var(--border);
        cursor: pointer;
        transition: all 0.2s;
    }
    
    .module:hover {
        background: rgba(255, 255, 255, 0.08);
        transform: translateY(-2px);
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
    }
    
    .module-name {
        font-weight: 600;
        font-size: 16px;
    }
    
    .toggle {
        width: 48px;
        height: 24px;
        border-radius: 24px;
        background: rgba(255, 255, 255, 0.15);
        position: relative;
        cursor: pointer;
    }
    
    .knob {
        position: absolute;
        top: 3px;
        left: 3px;
        width: 18px;
        height: 18px;
        background: white;
        border-radius: 50%;
        transition: all 0.2s;
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
    }
    
    .toggle.on {
        background: linear-gradient(90deg, var(--primary), var(--secondary));
    }
    
    .toggle.on .knob {
        left: 27px;
    }
    
    #themes {
        width: 160px;
        display: flex;
        flex-direction: column;
        gap: 12px;
    }
    
    #themeLabel {
        font-weight: 700;
        color: var(--primary);
        margin-bottom: 4px;
        text-shadow: 0 0 8px rgba(0, 212, 255, 0.5);
    }
    
    .theme {
        padding: 12px;
        border-radius: 10px;
        border: 1px solid var(--border);
        background: var(--card-bg);
        cursor: pointer;
        transition: all 0.2s;
    }
    
    .theme:hover {
        background: rgba(255, 255, 255, 0.1);
        transform: translateY(-2px);
    }
    
    .keybind-btn {
        width: 36px;
        height: 24px;
        font-size: 10px;
        padding: 0;
        margin-left: 10px;
        cursor: pointer;
        border: 1px solid var(--border);
        background: var(--card-bg);
        color: var(--text-secondary);
        border-radius: 6px;
        transition: all 0.2s;
    }
    
    .keybind-btn:hover {
        background: rgba(255, 255, 255, 0.1);
    }
    
    .module-info {
        display: flex;
        align-items: center;
        flex: 1;
    }
    
    .keybind-label {
        font-size: 11px;
        opacity: 0.6;
        margin-left: 8px;
    }
    
    #inject-status {
        width: 14px;
        height: 14px;
        border-radius: 50%;
        margin-left: 12px;
        transition: background 0.3s;
        box-shadow: 0 0 8px rgba(0, 0, 0, 0.3);
    }
    
    #inject-status.red {
        background: #ff3b49;
        box-shadow: 0 0 8px rgba(255, 59, 73, 0.5);
    }
    
    #inject-status.green {
        background: #34dd55;
        box-shadow: 0 0 8px rgba(52, 221, 85, 0.5);
    }
    
    #inject-status.yellow {
        background: #ffa500;
        box-shadow: 0 0 8px rgba(255, 165, 0, 0.5);
    }
    
    #inject-btn, #settings-btn {
        padding: 10px 16px;
        background: linear-gradient(135deg, var(--primary), var(--secondary));
        border: none;
        border-radius: 10px;
        color: white;
        cursor: pointer;
        font-weight: 700;
        font-size: 13px;
        margin-top: 8px;
        transition: all 0.2s;
        box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
    }
    
    #inject-btn:hover, #settings-btn:hover {
        transform: translateY(-2px);
        box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3);
    }
    
    #settings-panel {
        display: none;
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 500px;
        max-width: 90vw;
        background: var(--bg);
        color: var(--text);
        border: 1px solid var(--border);
        border-radius: 16px;
        backdrop-filter: blur(10px);
        box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
        z-index: 1000000;
        padding: 24px;
        max-height: 80vh;
        overflow-y: auto;
    }
    
    #settings-panel.show {
        display: block;
    }
    
    .settings-section {
        margin-bottom: 24px;
    }
    
    .settings-section h3 {
        color: var(--primary);
        margin-bottom: 16px;
        font-size: 18px;
        text-shadow: 0 0 8px rgba(0, 212, 255, 0.3);
    }
    
    .setting-item {
        margin-bottom: 16px;
        display: flex;
        align-items: center;
        justify-content: space-between;
    }
    
    .setting-label {
        flex: 1;
        font-size: 14px;
    }
    
    .setting-input {
        width: 100px;
        padding: 8px 12px;
        background: var(--card-bg);
        border: 1px solid var(--border);
        border-radius: 8px;
        color: var(--text);
        font-size: 14px;
        transition: all 0.2s;
    }
    
    .setting-input:focus {
        outline: none;
        border-color: var(--primary);
        box-shadow: 0 0 8px rgba(0, 212, 255, 0.3);
    }
    
    .setting-toggle {
        width: 48px;
        height: 24px;
        border-radius: 24px;
        background: rgba(255, 255, 255, 0.15);
        position: relative;
        cursor: pointer;
    }
    
    .setting-toggle.knob {
        position: absolute;
        top: 3px;
        left: 3px;
        width: 18px;
        height: 18px;
        background: white;
        border-radius: 50%;
        transition: all 0.2s;
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
    }
    
    .setting-toggle.on {
        background: linear-gradient(90deg, var(--primary), var(--secondary));
    }
    
    .setting-toggle.on .knob {
        left: 27px;
    }
    
    #settings-close {
        position: absolute;
        top: 16px;
        right: 16px;
        background: none;
        border: none;
        color: var(--text-secondary);
        font-size: 24px;
        cursor: pointer;
        padding: 4px 8px;
        border-radius: 6px;
        transition: all 0.2s;
    }
    
    #settings-close:hover {
        background: rgba(255, 255, 255, 0.1);
        color: var(--text);
    }
    
    #watermark {
        position: fixed;
        top: 15px;
        right: 15px;
        font-weight: 900;
        font-size: 28px;
        background: linear-gradient(135deg, var(--primary), var(--secondary));
        -webkit-background-clip: text;
        -webkit-text-fill-color: transparent;
        text-shadow: 0 0 15px rgba(0, 212, 255, 0.5);
        z-index: 9999999;
        pointer-events: none;
        letter-spacing: 1px;
    }
    
    #arraylist {
        position: fixed;
        right: 15px;
        top: 60px;
        display: flex;
        flex-direction: column;
        gap: 8px;
        z-index: 9999999;
        text-align: right;
    }
    
    .arrItem {
        background: rgba(20, 20, 40, 0.8);
        padding: 8px 14px;
        border-radius: 10px;
        font-weight: 600;
        color: white;
        border: 1px solid var(--border);
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
        backdrop-filter: blur(5px);
    }
    
    .arrItem::before {
        content: '';
        position: absolute;
        right: 0;
        top: 0;
        height: 100%;
        width: 4px;
        background: linear-gradient(to bottom, var(--primary), var(--secondary));
        border-radius: 4px 0 0 4px;
    }
    `;
    document.head.appendChild(style);

    root.innerHTML = `
    <div id="bounceclient-gui">
        <div id="bounceclient-header">
            <div style="display:flex;align-items:center;">
                <div class="title">Bounce<sup>Client</sup></div>
                <div id="inject-status" class="red"></div>
            </div>
            <div style="font-size:13px;opacity:.8;">RightShift to toggle</div>
        </div>
        <div id="category-buttons"></div>
        <div id="bounceclient-content">
            <div id="bounceclient-mods"></div>
            <div id="themes">
                <div id="themeLabel">Theme</div>
                <div class="theme" data-theme="ocean">Ocean Blue</div>
                <div class="theme" data-theme="purple">Deep Purple</div>
                <div class="theme" data-theme="matrix">Matrix Green</div>
                <div class="theme" data-theme="sunset">Sunset Red</div>
                <button id="inject-btn">Inject/Re-inject</button>
                <button id="settings-btn">Settings</button>
            </div>
        </div>
    </div>
    <div id="settings-panel">
        <button id="settings-close">×</button>
        <h2 style="color:var(--primary);margin-bottom:24px;">Settings</h2>
        <div id="settings-content"></div>
    </div>
    <div id="watermark">BounceClient</div>
    <div id="arraylist"></div>
    `;

    const gui = root.querySelector("#bounceclient-gui");
    const modsDiv = root.querySelector("#bounceclient-mods");
    const arraylist = root.querySelector("#arraylist");
    const watermark = root.querySelector("#watermark");
    const categoryButtonsDiv = root.querySelector("#category-buttons");
    const injectStatus = root.querySelector("#inject-status");
    const injectBtn = root.querySelector("#inject-btn");
    const settingsPanel = root.querySelector("#settings-panel");
    const settingsContent = root.querySelector("#settings-content");
    const settingsBtn = root.querySelector("#settings-btn");
    const settingsClose = root.querySelector("#settings-close");

    // === Category buttons ===
    ["Blatant","Visual","Legit"].forEach(cat=>{
        const btn=document.createElement("div");
        btn.className="cat-btn";
        btn.textContent=cat;
        btn.onclick=()=>{currentCategory=cat; refreshModules(); updateCategoryButtons();}
        categoryButtonsDiv.appendChild(btn);
    });
    function updateCategoryButtons(){
        categoryButtonsDiv.querySelectorAll(".cat-btn").forEach(b=>{
            if(b.textContent===currentCategory) b.classList.add("on"); else b.classList.remove("on");
        });
    }

    // === Module refresh ===
    function refreshModules(){
        modsDiv.innerHTML="";
        const mods=Modules[currentCategory];
        Object.keys(mods).forEach(name=>{
            const div=document.createElement("div");
            div.className="module";
            
            const moduleInfo = document.createElement("div");
            moduleInfo.className = "module-info";
            moduleInfo.innerHTML = `<span class="module-name">${name}</span>`;
            
            const hasKeybind = Keybinds.hasOwnProperty(name);
            if(hasKeybind){
                const keybindBtn = document.createElement("button");
                keybindBtn.className = "keybind-btn";
                keybindBtn.textContent = Keybinds[name] || "NONE";
                keybindBtn.onclick = (e) => {
                    e.stopPropagation();
                    listeningKeybind = name;
                    keybindBtn.textContent = "...";
                    keybindBtn.style.background = "var(--primary)";
                };
                moduleInfo.appendChild(keybindBtn);
            }
            
            const toggle = document.createElement("div");
            toggle.className = "toggle";
            toggle.dataset.mod = name;
            toggle.innerHTML = "<div class='knob'></div>";
            
            div.appendChild(moduleInfo);
            div.appendChild(toggle);
            
            div.onclick = () => {
                mods[name] = !mods[name];
                updateToggles(); updateArray(); toggleModule(name, mods[name]);
            };
            modsDiv.appendChild(div);
        });
        updateToggles();
    }
    
    let listeningKeybind = null;
    window.addEventListener("keydown", e => {
        if(listeningKeybind && listeningKeybind in Keybinds){
            Keybinds[listeningKeybind] = e.code;
            listeningKeybind = null;
            refreshModules();
        }
    });

    function updateToggles(){
        root.querySelectorAll(".toggle").forEach(t=>{
            const m=t.dataset.mod;
            const mods=Modules[currentCategory];
            if(mods[m]) t.classList.add("on"); else t.classList.remove("on");
        });
    }

    function updateArray(){
        arraylist.innerHTML="";
        Object.values(Modules).forEach(group=>{
            Object.keys(group).forEach(m=>{
                if(group[m]){
                    const el=document.createElement("div");
                    el.className="arrItem"; 
                    el.textContent=m;
                    el.style.position = "relative";
                    el.style.paddingRight = "20px";
                    arraylist.appendChild(el);
                }
            });
        });
    }

    // === Themes ===
    const themes = {
        ocean: { 
            primary: "#00d4ff", 
            primaryDark: "#0099cc",
            secondary: "#7c4dff",
            border: "rgba(100, 100, 255, 0.3)"
        },
        purple: { 
            primary: "#9d4edd", 
            primaryDark: "#7b2cbf",
            secondary: "#c77dff",
            border: "rgba(157, 78, 221, 0.3)"
        },
        matrix: { 
            primary: "#00ff41", 
            primaryDark: "#00cc33",
            secondary: "#00ff7f",
            border: "rgba(0, 255, 65, 0.3)"
        },
        sunset: { 
            primary: "#ff5e5b", 
            primaryDark: "#ff3d3a",
            secondary: "#ff9a8b",
            border: "rgba(255, 94, 91, 0.3)"
        }
    };
    root.querySelectorAll(".theme").forEach(t=>{
        t.onclick=()=>{
            const th=themes[t.dataset.theme];
            document.documentElement.style.setProperty("--primary",th.primary);
            document.documentElement.style.setProperty("--primary-dark",th.primaryDark);
            document.documentElement.style.setProperty("--secondary",th.secondary);
            document.documentElement.style.setProperty("--border",th.border);
        };
    });

    // === GUI Toggle ===
    let visible=false;
    function toggleGUI(){visible=!visible; gui.style.display=visible?"flex":"none";}
    window.addEventListener("keydown",e=>{
        if(e.code==="ShiftRight" && !listeningKeybind) toggleGUI();
    });

    // === Vector utilities ===
    const vectorUtils = {
        normalizeVector(e) {
            let t = e[0] * e[0] + e[1] * e[1] + e[2] * e[2];
            if (t > 0) {
                let n = 1 / Math.sqrt(t);
                return [e[0] * n, e[1] * n, e[2] * n];
            }
            return e;
        },
        distanceBetweenSqrt(e, t) {
            let n = t[0] - e[0], o = t[1] - e[1], i = t[2] - e[2];
            return Math.sqrt(n * n + o * o + i * i);
        }
    };

    // === NOA API wrappers ===
    const noaAPI = {
        noa: {
            getPosition(e) {
                return gameState.entities?.getState(e, "position")?.position;
            },
            get getMoveState() {
                return objectUtils.values(gameState.noa?.entities)?.[36];
            },
            get getHeldItem() {
                return objectUtils.values(gameState.noa?.entities).find((e => 1 == e?.length && e?.toString()?.length < 13 && e?.toString().includes(").")));
            },
            safeGetHeldItem(e) {
                let t;
                try {
                    t = this.getHeldItem(e);
                } catch {}
                return t;
            },
            get playerList() {
                try {
                    return objectUtils.values(gameState.bloxd?.getPlayerIds()).filter((e => 1 !== e && this.safeGetHeldItem(e))).map((e => parseInt(e)));
                } catch(e) {
                    return [];
                }
            },
            get doAttack() {
                let e = this.safeGetHeldItem(1);
                return (e?.doAttack || e?.breakingItem?.doAttack)?.bind(e);
            }
        }
    };

    // === Killaura ===
    let killauraInterval = null;

    function tryKill() {
        let myPos = noaAPI.noa.getPosition(1);
        
        noaAPI.noa.playerList.forEach((target => {
            let targetPos = noaAPI.noa.getPosition(target);
            
            if (targetPos && vectorUtils.distanceBetweenSqrt(myPos, targetPos) <= Config.Killaura.range) {
                let direction = vectorUtils.normalizeVector([
                    targetPos[0] - myPos[0], 
                    targetPos[1] - myPos[1], 
                    targetPos[2] - myPos[2]
                ]);
                
                // Apply random jitter if enabled
                if (Config.Killaura.jitter > 0) {
                    const jitterAmount = Config.Killaura.jitter / 100;
                    direction[0] += (Math.random() - 0.5) * jitterAmount;
                    direction[1] += (Math.random() - 0.5) * jitterAmount;
                    direction[2] += (Math.random() - 0.5) * jitterAmount;
                    direction = vectorUtils.normalizeVector(direction);
                }
                
                noaAPI.noa.doAttack(direction, target.toString(), "BodyMesh");
                noaAPI.noa.getHeldItem(1)?.trySwingBlock?.();
                noaAPI.noa.getMoveState(1)?.setArmsAreSwinging?.();
            }
        }));
    }

    function toggleKillaura(on) {
        if (on) {
            if (killauraInterval) clearInterval(killauraInterval);
            let last = 0;
            killauraInterval = setInterval(() => {
                const now = Date.now();
                if (now - last >= Config.Killaura.delay) {
                    last = now;
                    tryKill();
                }
            }, 16); // ~60fps
        } else {
            if (killauraInterval) {
                clearInterval(killauraInterval);
                killauraInterval = null;
            }
        }
    }

    function toggleModule(name,state){
        if(name==="Killaura") toggleKillaura(state);
        if(name==="Scaffold") toggleScaffold(state);
        if(name==="ESP") toggleESP(state);
        if(name==="Coords") toggleCoords(state);
        if(name==="FPS Counter") fpsHUD.style.display=state?"block":"none";
        if(name==="CPS Counter") cpsHUD.style.display=state?"block":"none";
        if(name==="Keystrokes") keystrokesHUD.style.display=state?"grid":"none";
    }

    // === Update injection status indicator ===
    function updateInjectStatus() {
        if (injectionStatus.injected) {
            injectStatus.className = "green";
        } else if (injectionStatus.failed) {
            injectStatus.className = "yellow";
        } else {
            injectStatus.className = "red";
        }
    }

    // === Try to get noa object ===
    function tryGetNoa() {
        try {
            // Always check if noa still exists and is valid
            if (gameState.noa && gameState.noa.entities && typeof gameState.noa.entities.getState === 'function') {
                return true; // Already have valid noa
            }
            
            let e = Object.getOwnPropertyDescriptors(window);
            let i = Object.keys(e).find(key => e[key]?.set?.toString().includes("++"));
            
            if (!i || !window[i]) {
                return false;
            }
            
            (window[i] = window[i]).push([
                [Math.floor(9e4 * Math.random()) + 1e4], {},
                function (t) {
                    gameState.findModule = e => t(Object.keys(t.m)[Object.values(t.m).findIndex((t => t.toString().includes(e)))]);            
                    gameState.Props = Object.values(gameState.findModule("nonBlocksClient:")).find((e => "object" == typeof e));
                    gameState.noa = Object.values(gameState.Props).find((e => e?.entities));
                }
            ]);
            
            return gameState.noa && gameState.noa.entities;
        } catch(err) {
            return false;
        }
    }

    // === Game Injection ===
    function injectGame() {
        try {
            // Try to get noa object
            if (!tryGetNoa()) {
                console.error("[BounceClient] Injection failed: Could not find webpack array or noa object.");
                injectionStatus.injected = false;
                injectionStatus.failed = true;
                updateInjectStatus();
                return false;
            }
            
            // Always reinitialize game state references
            const a = objectUtils.values(gameState.noa.entities)[2];
            const r = Object.entries(gameState.noa.entities);
            gameState.impKey = r.find((([e, t]) => t === a))?.[0];
            gameState.registry = objectUtils.values(gameState.noa)[17];
            gameState.rendering = objectUtils.values(gameState.noa)[12];
            gameState.entities = gameState.noa.entities;
            gameState.world = objectUtils.values(gameState.noa)[11];
            gameState.camera = gameState.noa.camera;
            gameState.bloxd = gameState.noa.bloxd;
            gameState.physics = gameState.noa.physics;
            gameState.entityList = objectUtils.values(gameState.noa)[30];
            
            let f = objectUtils.values(gameState.rendering)[7].meshes[0];
            let m = objectUtils.values(gameState.rendering)[7];
            
            gameState.Lion = {
                scene: m,
                Mesh: f.constructor,
                StandardMaterial: f.material.constructor,
                Color3: f.material.diffuseColor.constructor
            };
            
            injectionStatus.injected = true;
            injectionStatus.failed = false;
            updateInjectStatus();
            console.log("%c[BounceClient] Game injected successfully!","color:var(--primary);font-weight:700;");
            return true;
        } catch(err) {
            console.error("[BounceClient] Injection error:", err);
            injectionStatus.injected = false;
            injectionStatus.failed = true;
            updateInjectStatus();
            return false;
        }
    }

    // === Auto-retry injection ===
    function startInjectionRetry() {
        if (injectionRetryInterval) return;
        
        injectionRetryInterval = setInterval(() => {
            if (!injectionStatus.injected) {
                const gotNoa = tryGetNoa();
                if (gotNoa && !gameState.impKey) {
                    // Have noa but not initialized, try to initialize
                    injectGame();
                } else if (!gotNoa && injectionStatus.injected) {
                    // Lost noa object
                    injectionStatus.injected = false;
                    injectionStatus.failed = true;
                    updateInjectStatus();
                }
            }
        }, 1000);
    }

    // === Inject button handler ===
    injectBtn.onclick = () => {
        injectGame();
    };

    // === Settings panel ===
    function renderSettings() {
        settingsContent.innerHTML = `
            <div class="settings-section">
                <h3>Killaura</h3>
                <div class="setting-item">
                    <span class="setting-label">Delay (ms)</span>
                    <input type="number" class="setting-input" value="${Config.Killaura.delay}" 
                           onchange="window.BounceClient.Config.Killaura.delay = parseInt(this.value); window.BounceClient.saveConfig(); toggleKillaura(window.BounceClient.Modules.Blatant.Killaura);">
                </div>
                <div class="setting-item">
                    <span class="setting-label">Range</span>
                    <input type="number" class="setting-input" step="0.1" value="${Config.Killaura.range}" 
                           onchange="window.BounceClient.Config.Killaura.range = parseFloat(this.value); window.BounceClient.saveConfig();">
                </div>
                <div class="setting-item">
                    <span class="setting-label">Jitter (%)</span>
                    <input type="number" class="setting-input" value="${Config.Killaura.jitter}" 
                           onchange="window.BounceClient.Config.Killaura.jitter = parseInt(this.value); window.BounceClient.saveConfig();">
                </div>
            </div>
            <div class="settings-section">
                <h3>Scaffold</h3>
                <div class="setting-item">
                    <span class="setting-label">Delay (ms)</span>
                    <input type="number" class="setting-input" value="${Config.Scaffold.delay}" 
                           onchange="window.BounceClient.Config.Scaffold.delay = parseInt(this.value); window.BounceClient.saveConfig(); toggleScaffold(window.BounceClient.Modules.Blatant.Scaffold);">
                </div>
            </div>
            <div class="settings-section">
                <h3>ESP</h3>
                <div class="setting-item">
                    <span class="setting-label">Chest Update (ms)</span>
                    <input type="number" class="setting-input" value="${Config.ESP.chestUpdateInterval}" 
                           onchange="window.BounceClient.Config.ESP.chestUpdateInterval = parseInt(this.value); window.BounceClient.saveConfig(); toggleESP(window.BounceClient.Modules.Visual.ESP);">
                </div>
                <div class="setting-item">
                    <span class="setting-label">Player Update (ms)</span>
                    <input type="number" class="setting-input" value="${Config.ESP.playerUpdateInterval}" 
                           onchange="window.BounceClient.Config.ESP.playerUpdateInterval = parseInt(this.value); window.BounceClient.saveConfig(); togglePlayerESP(playerESPEnabled);">
                </div>
            </div>
            <div class="settings-section">
                <h3>Coords</h3>
                <div class="setting-item">
                    <span class="setting-label">Update Interval (ms)</span>
                    <input type="number" class="setting-input" value="${Config.Coords.updateInterval}" 
                           onchange="window.BounceClient.Config.Coords.updateInterval = parseInt(this.value); window.BounceClient.saveConfig(); toggleCoords(window.BounceClient.Modules.Visual.Coords);">
                </div>
            </div>
        `;
    }

    settingsBtn.onclick = () => {
        renderSettings();
        settingsPanel.classList.add("show");
    };

    settingsClose.onclick = () => {
        settingsPanel.classList.remove("show");
    };

    // === ESP ===
    let espInterval = null;
    let espMeshes = {};
    let espChunks = new Set();
    let chunkDataKey = null;

    function getChunkKey(chunk) {
        const [x, y, z] = chunk.pos || [0, 0, 0];
        const chunkX = Math.floor(x / 32);
        const chunkY = Math.floor(y / 32);
        const chunkZ = Math.floor(z / 32);
        return `${chunkX}|${chunkY}|${chunkZ}|overworld`;
    }

    function decodeChunkIndex(index, stride) {
        const x = Math.floor(index / stride[0]);
        const remainder = index % stride[0];
        const y = Math.floor(remainder / stride[1]);
        const z = remainder % stride[1];
        return [x, y, z];
    }

    function findChunkDataKey(chunk) {
        for (const key of Object.keys(chunk)) {
            const value = chunk[key];
            if (value && typeof value === "object" && 
                Array.isArray(value.stride) && value.stride.length === 3 &&
                (Array.isArray(value.data) || ArrayBuffer.isView(value.data))) {
                return key;
            }
        }
        return null;
    }

    function renderChestESP(chunk, blockIds) {
        const chunkData = chunk[chunkDataKey];
        if (!chunkData) return;
        
        const { data, stride } = chunkData;
        const chunkPos = chunk.pos || [0, 0, 0];
        
        if (!data || !stride) return;
        
        const chunkKey = getChunkKey(chunk);
        
        for (let i = 0; i < data.length; i++) {
            const blockId = data[i];
            if (!blockIds.includes(blockId)) continue;
            
            const [localX, localY, localZ] = decodeChunkIndex(i, stride);
            const worldX = chunkPos[0] + localX + 0.5;
            const worldY = chunkPos[1] + localY + 0.5;
            const worldZ = chunkPos[2] + localZ + 0.5;
            
            const mesh = gameState.Lion.Mesh.CreateBox("espbox", 1, gameState.Lion.scene);
            mesh.position.set(worldX, worldY, worldZ);
            mesh.renderingGroupId = 1;
            mesh.material = new gameState.Lion.StandardMaterial("mat", gameState.Lion.scene);
            mesh.material.wireframe = true;
            mesh.material.emissiveColor = new gameState.Lion.Color3(
                Config.ESP.wireframeColor[0], 
                Config.ESP.wireframeColor[1], 
                Config.ESP.wireframeColor[2]
            );
            
            if (!espMeshes[chunkKey]) espMeshes[chunkKey] = [];
            espMeshes[chunkKey].push(mesh);
        }
    }

    function updateESP() {
        if (!gameState?.world || !gameState?.world?.[gameState.impKey]?.hash) return;
        
        const worldChunks = gameState.world[gameState.impKey].hash;
        
        for (const chunkId in worldChunks) {
            const chunk = worldChunks[chunkId];
            
            if (!chunkDataKey) chunkDataKey = findChunkDataKey(chunk);
            
            if (!chunkDataKey || !chunk[chunkDataKey]?.data || !chunk.pos) continue;
            
            const chunkKey = getChunkKey(chunk);
            if (espChunks.has(chunkKey)) continue;
            
            espChunks.add(chunkKey);
            renderChestESP(chunk, [204, 205, 206, 207]);
        }
    }

    function clearESP() {
        for (const chunkKey in espMeshes) {
            for (const mesh of espMeshes[chunkKey]) {
                mesh.dispose();
            }
        }
        espChunks.clear();
        espMeshes = {};
    }

    // === Player ESP ===
    let playerESPInterval = null;
    let playerESPEnabled = false;

    function updatePlayerESP() {
        if (!gameState.rendering || !playerESPEnabled) {
            return;
        }
        
        try {
            const thinMeshes = objectUtils.values(gameState.rendering)[19]?.thinMeshes;
            if (!Array.isArray(thinMeshes)) {
                return;
            }
            
            const renderingGroupId = 2;
            for (const item of thinMeshes) {
                if (item?.meshVariations?.__DEFAULT__?.mesh && typeof item?.meshVariations?.__DEFAULT__?.mesh.renderingGroupId === "number") {
                    item.meshVariations.__DEFAULT__.mesh.renderingGroupId = renderingGroupId;
                }
            }
        } catch(e) {
            console.error("Player ESP error:", e);
        }
    }

    function togglePlayerESP(on) {
        playerESPEnabled = on;
        
        if (on) {
            if (!playerESPInterval) {
                playerESPInterval = setInterval(updatePlayerESP, Config.ESP.playerUpdateInterval);
            }
            updatePlayerESP();
        } else {
            if (playerESPInterval) {
                clearInterval(playerESPInterval);
                playerESPInterval = null;
            }
            
            // Reset to normal rendering
            if (gameState.rendering) {
                try {
                    const thinMeshes = objectUtils.values(gameState.rendering)[19]?.thinMeshes;
                    if (Array.isArray(thinMeshes)) {
                        for (const item of thinMeshes) {
                            if (item.meshVariations?.__DEFAULT__?.mesh && typeof item?.meshVariations?.__DEFAULT__?.mesh.renderingGroupId === "number") {
                                item.meshVariations.__DEFAULT__.mesh.renderingGroupId = 0;
                            }
                        }
                    }
                } catch(e) {
                    console.error("Player ESP reset error:", e);
                }
            }
        }
    }

    function toggleESP(on) {
        if (on) {
            if (!espInterval) {
                espInterval = setInterval(updateESP, Config.ESP.chestUpdateInterval);
            }
            updateESP();
            togglePlayerESP(true);
        } else {
            if (espInterval) {
                clearInterval(espInterval);
                espInterval = null;
            }
            clearESP();
            togglePlayerESP(false);
        }
    }

    // === Coords ===
    let coordsInterval = null;
    function updateCoords() {
        if (!gameState?.bloxd?.entityNames) return;
        
        try {
            for (const entityId in gameState.bloxd.entityNames) {
                if (entityId === "1") continue;
                
                const entityData = gameState.bloxd.entityNames[entityId];
                const positionData = gameState.entities.getState(entityId, "position");
                
                if (!positionData || !positionData.position) continue;
                
                const position = positionData.position;
                const x = Math.round(position[0]);
                const y = Math.round(position[1]);
                const z = Math.round(position[2]);
                
                const baseName = entityData.entityName.replace(/\s*\(\-?\d+,\s*\-?\d+,\s*\-?\d+\)$/, "");
                entityData.entityName = `${baseName} (${x}, ${y}, ${z})`;
            }
        } catch (error) {
            console.error("Error updating coords:", error);
            stopCoords();
        }
    }

    function startCoords() {
        if (!coordsInterval) {
            updateCoords();
            coordsInterval = setInterval(updateCoords, Config.Coords.updateInterval);
        }
    }

    function stopCoords() {
        if (coordsInterval) {
            clearInterval(coordsInterval);
            coordsInterval = null;
        }
    }

    function toggleCoords(on) {
        if (on) startCoords();
        else stopCoords();
    }

    // === Scaffold ===
    let scaffoldInterval = null;

    function placeBlock(position) {
        try {
            const blockItem = objectUtils.values(gameState.noa.entities[gameState.impKey])[22].list[0]._blockItem;
            const firstKey = Object.keys(blockItem)[0];
            const firstValue = Object.values(blockItem)[0];
            const posKey = Object.keys(firstValue)[25];
            const posValue = firstValue[posKey];
            
            blockItem.placeBlock.call(createPlacementProxy(position, blockItem, firstKey, firstValue, posKey, posValue));
        } catch(e) {
            console.error("Scaffold placement error:", e);
        }
    }

    function createPlacementProxy(targetPos, blockItem, firstKey, firstValue, posKey, posValue) {
        return new Proxy({}, {
            get: (target, prop) => {
                if (prop === firstKey) {
                    return new Proxy(firstValue, {
                        get(t, p) {
                            if (p === posKey) {
                                let cloned = structuredClone(posValue) || {};
                                cloned.position = targetPos;
                                return cloned;
                            }
                            return firstValue[p];
                        }
                    });
                } else if (prop === "checkTargetedBlockCanBePlacedOver") {
                    return () => true;
                } else if (typeof blockItem[prop] === "function") {
                    return blockItem[prop].bind(blockItem);
                }
                return blockItem[prop];
            }
        });
    }

    function canPlaceBlock(x, y, z, heldItem) {
        try {
            return heldItem.checkTargetedBlockCanBePlacedOver([x, y, z]) || 
                   0 === objectUtils.values(gameState.world)[47].call(gameState.world, x, y, z);
        } catch(e) {
            return false;
        }
    }

    function scaffoldTick() {
        try {
            const playerPos = gameState.entities.getState(1, "position").position;
            if (!playerPos) return;
            
            const heldItemData = gameState.noa.entities?.[gameState.impKey];
            const currentHeldItem = Object.values(Object.values(heldItemData)?.[22]?.list?.[0])?.[1];
            
            if (!currentHeldItem || !currentHeldItem.heldItemState || currentHeldItem.heldItemState.heldType !== "CubeBlock") return;
            
            const px = playerPos[0];
            const py = Math.floor(playerPos[1]);
            const pz = playerPos[2];
            const fx = Math.floor(px);
            const fz = Math.floor(pz);
            
            if (canPlaceBlock(fx, py - 1, fz, currentHeldItem)) {
                placeBlock([fx, py - 1, fz]);
                return;
            }
            
            const offsetX = px - fx;
            const offsetZ = pz - fz;
            const positions = [];
            
            if (offsetX < 0.3) positions.push([-1, 0]);
            if (offsetX > 0.7) positions.push([1, 0]);
            if (offsetZ < 0.3) positions.push([0, -1]);
            if (offsetZ > 0.7) positions.push([0, 1]);
            
            for (const [dx, dz] of positions) {
                const targetX = fx + dx;
                const targetZ = fz + dz;
                if (canPlaceBlock(targetX, py - 1, targetZ, currentHeldItem)) {
                    placeBlock([targetX, py - 1, targetZ]);
                    return;
                }
            }
        } catch(e) {
            console.error("Scaffold tick error:", e);
        }
    }

    function toggleScaffold(on) {
        if (on) {
            if (!scaffoldInterval) {
                scaffoldInterval = setInterval(scaffoldTick, Config.Scaffold.delay);
            }
        } else {
            if (scaffoldInterval) {
                clearInterval(scaffoldInterval);
                scaffoldInterval = null;
            }
        }
    }

    // === Keybind listeners for module toggles ===
    window.addEventListener("keydown", e => {
        // Don't trigger module keybinds if GUI is open or setting a keybind
        if (visible || listeningKeybind) return;
        
        for (const [module, keybind] of Object.entries(Keybinds)) {
            if (keybind && e.code === keybind) {
                if (module in Modules.Blatant) {
                    Modules.Blatant[module] = !Modules.Blatant[module];
                    toggleModule(module, Modules.Blatant[module]);
                } else if (module in Modules.Visual) {
                    Modules.Visual[module] = !Modules.Visual[module];
                    toggleModule(module, Modules.Visual[module]);
                }
                updateArray();
            }
        }
    });

    // === Legit HUD ---
    const hudStyle = "font-family:'Segoe UI',sans-serif;font-weight:700;color:white;text-align:center;"+
                     "background:rgba(20, 20, 40, 0.8);padding:10px 14px;border-radius:10px;"+
                     "border:1px solid var(--border);box-shadow:0 4px 12px rgba(0, 0, 0, 0.2);backdrop-filter:blur(5px);";

    const fpsHUD=document.createElement("div");
    fpsHUD.style.cssText=`position:fixed;bottom:15px;left:15px;z-index:9999999;${hudStyle}`;
    fpsHUD.textContent="FPS: 0"; fpsHUD.style.display="none"; document.body.appendChild(fpsHUD);

    const cpsHUD=document.createElement("div");
    cpsHUD.style.cssText=`position:fixed;bottom:70px;left:15px;z-index:9999999;${hudStyle}`;
    cpsHUD.textContent="CPS: 0"; cpsHUD.style.display="none"; document.body.appendChild(cpsHUD);

const keystrokesHUD = document.createElement("div");
keystrokesHUD.style.cssText = `
    position: fixed;
    bottom: 20px;
    right: 20px;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 8px;
    z-index: 9999999;
`;

const keys = {};
function createKey(char, gridArea) {
    const k = document.createElement("div");
    k.textContent = char;
    k.style.cssText = `
        min-width: 40px;
        height: 40px;
        background: rgba(20, 20, 40, 0.8);
        color: white;
        display: flex;
        align-items: center;
        justify-content: center;
        border-radius: 10px;
        font-weight: 700;
        border: 1px solid var(--border);
        box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
        transition: all 0.1s;
        ${gridArea ? `grid-area: ${gridArea};` : ''}
    `;
    keys[char] = k;
    return k;
}

// Add keys in a WASD layout
keystrokesHUD.appendChild(createKey("", "top")); // Empty top-left
keystrokesHUD.appendChild(createKey("W", "top"));
keystrokesHUD.appendChild(createKey("", "top")); // Empty top-right

keystrokesHUD.appendChild(createKey("A", "middle"));
keystrokesHUD.appendChild(createKey("S", "middle"));
keystrokesHUD.appendChild(createKey("D", "middle"));

keystrokesHUD.style.display = "none"; // hide by default
document.body.appendChild(keystrokesHUD);

    // FPS / CPS logic
    let lastFrame=performance.now(),frameCount=0,fps=0;
    function updateFPS(){
        const now=performance.now();
        frameCount++;
        if(now-lastFrame>=1000){ fps=frameCount; frameCount=0; lastFrame=now; fpsHUD.textContent="FPS: "+fps; }
        requestAnimationFrame(updateFPS);
    }
    requestAnimationFrame(updateFPS);

    let clicks=0,cps=0;
    document.addEventListener("mousedown",()=>clicks++);
    setInterval(()=>{
        cps=clicks; clicks=0; cpsHUD.textContent="CPS: "+cps;
    },1000);

    // Key press highlight for keystrokes
    document.addEventListener("keydown",e=>{
        const k=e.key.toUpperCase();
        if(keys[k]) {
            keys[k].style.background="linear-gradient(135deg, var(--primary), var(--secondary))";
            keys[k].style.transform="scale(0.95)";
        }
    });
    document.addEventListener("keyup",e=>{
        const k=e.key.toUpperCase();
        if(keys[k]) {
            keys[k].style.background="rgba(20, 20, 40, 0.8)";
            keys[k].style.transform="scale(1)";
        }
    });

    // --- Initialize ---
    updateCategoryButtons(); refreshModules();
    updateInjectStatus(); // Set initial status
    
    // Start auto-retry injection
    startInjectionRetry();
    
    // Inject game state on load
    setTimeout(() => {
        if (injectGame()) {
            console.log("%c[BounceClient] All systems ready!","color:var(--primary);font-weight:700;");
        }
    }, 2000);
    
    window.BounceClient={Modules, Keybinds, Config, gameState, injectGame, saveConfig};
    console.log("%cBounceClient GUI loaded - Press RightShift","color:var(--primary);font-weight:700;");
})();