Click Helper

Auto Clicker (L/R), Custom CPS, Account Generator, Killaura, Aimbot, BHOP, ViewModel, Player ESP.

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Click Helper
// @namespace    https://bloxd.io
// @version      1.5.2.7
// @description  Auto Clicker (L/R), Custom CPS, Account Generator, Killaura, Aimbot, BHOP, ViewModel, Player ESP.
// @author       MakeItOrBreakIt
// @license      MIT
// @match        *://*.bloxd.io/*

// @match        *://*.bloxd.com/*
// @match        *://*.bloxd.dev/*
// @match        *://*.bloxdhop.io/*
// @match        *://*.bloxdunblocked.*/*
// @match        *://*.unbloxd.*/*
// @match        *://*.playbloxd.com/*

// @match        *://*.buildhub.*/*
// @match        *://*.skillhub.vip/*
// @match        *://*.classcraft.*/*
// @match        *://*.collabspace.space/*
// @match        *://*.creativebuilding.*/*
// @match        *://*.bedwars*.net/*
// @match        *://*.bedwars*.space/*
// @match        *://*.buildminecreate.com/*
// @match        *://*.iogamesunblocked.com/*
// @match        *://*.unblockedgames.club/*

// @grant        none
// @run-at       document-start
// ==/UserScript==
/*
 * MIT License
 * Copyright (c) 2026 MakeItOrBreakIt
 * Modified by Johnny-The
 */
(() => {
  'use strict';
  const attempt = (fn, fb = null) => { try { return fn(); } catch (_) { return fb; } };
  const vals = o => Object.values(o ?? {});
  const ks = o => Object.keys(o ?? {});

  const bloxd = {
    noa: null,
    _hooked: false,
    _gameReady: false,
    init() {
      console.log('[CH] bloxd.init() called');
      if (bloxd._hooked) {
        console.log('[CH] Already hooked, skipping');
        return;
      }
      bloxd._hooked = true;
      console.log('[CH] Setting up hooks...');

      // Use Object.defineProperty to wrap Function.prototype.call
      // This is different from replacing it directly - might avoid detection
      try {
        const origCall = Function.prototype.call;
        let callGetter = function() {
          return function(thisArg, ...args) {
            // Check if the first argument looks like the noa instance
            if (args[0] && args[0].entities && args[0].bloxd && !bloxd.noa) {
              console.log('[CH] Intercepted noa instance via Function.prototype.call!');
              bloxd.noa = args[0];
              // Restore original call after capturing
              Object.defineProperty(Function.prototype, 'call', {
                value: origCall,
                writable: true,
                configurable: true
              });
              // Start the game loop immediately
              if (!window._chRafStarted) {
                window._chRafStarted = true;
                console.log('[CH] Starting game loop...');
                requestAnimationFrame(rafLoop);
              }
            }
            return origCall.apply(this, [thisArg, ...args]);
          };
        };

        Object.defineProperty(Function.prototype, 'call', {
          get: callGetter,
          set: function(val) {
            // Allow setting but we'll override it
            callGetter = function() { return val; };
          },
          configurable: true,
          enumerable: false
        });
        console.log('[CH] Function.prototype.call wrapped with defineProperty');
      } catch (e) {
        console.log('[CH] Error wrapping call:', e.message);
      }

      // Also try to hook the noa-container being shown
      const observer = new MutationObserver((mutations) => {
        for (const mutation of mutations) {
          for (const node of mutation.addedNodes) {
            if (node.id === 'noa-container' ||
                (node.querySelector && node.querySelector('#noa-container'))) {
              console.log('[CH] noa-container detected via MutationObserver!');
            }
          }
        }
      });

      if (document.documentElement) {
        observer.observe(document.documentElement, { childList: true, subtree: true });
        console.log('[CH] MutationObserver attached');
      }
    }
  };

  function waitForGameReady() {
    console.log('[CH] Waiting for game to be ready...');
    const checkInterval = setInterval(() => {
      if (bloxd.noa?.bloxd?.client?.msgHandler) {
        console.log('[CH] Game is ready! msgHandler found');
        bloxd._gameReady = true;
        clearInterval(checkInterval);
      }
    }, 100);

    // Timeout after 30 seconds
    setTimeout(() => {
      clearInterval(checkInterval);
      if (!bloxd._gameReady) {
        console.log('[CH] Game readiness check timed out');
      }
    }, 30000);
  }

  function tryFindNoa() {
    console.log('[CH] tryFindNoa() called, bloxd.noa:', !!bloxd.noa);
    if (bloxd.noa) return;

    // Fallback methods in case the call hook doesn't work
    try {
      for (const key of Object.getOwnPropertyNames(window)) {
        try {
          const val = window[key];
          if (val && typeof val === 'object' && val.entities && val.bloxd && val.camera) {
            console.log('[CH] Found noa via window property:', key);
            bloxd.noa = val;
            return;
          }
        } catch (e) {}
      }
    } catch (e) {}

    console.log('[CH] tryFindNoa() completed - noa NOT found');
  }

  const game = {
    _held: null,
    inGame() { return !!(bloxd.noa?.bloxd?.client?.msgHandler); },
    getPos(id) { return attempt(() => bloxd.noa.entities.getState(id, 'position').position); },
    getIds() { return attempt(() => bloxd.noa?.bloxd?.getPlayerIds?.() ?? {}, {}); },
    getMovement(id = 1) { return attempt(() => bloxd.noa.entities.getState(id, 'movement')); },
    getHeld(id = 1) {
      if (!bloxd.noa) return null;
      if (!this._held) this._held = vals(bloxd.noa.entities).find(fn => {
        if (typeof fn !== 'function' || fn.length !== 1) return false;
        const s = fn.toString();
        return s.length < 80 && s.includes(').') && !s.includes('opWrapper');
      });
      return attempt(() => this._held?.(id));
    },
    doAttack(id) {
      attempt(() => {
        const item = this.getHeld(1);
        if (!item) return;
        const fn = game.doAttack._fn ||= Object.getOwnPropertyNames(Object.getPrototypeOf(item)).map(k=>item[k]).find(f=>typeof f==='function'&&f.length===3);
        if (!fn) return;
        let offset = [0, 0, 0];
        const jitter = state.killaura.jitter;
        if (jitter > 0) {
          const cam = bloxd.noa?.camera;
          if (cam && cam.heading !== undefined && cam.pitch !== undefined) {
            const yaw = cam.heading + (Math.random() - 0.5) * jitter * 0.35;
            const pitch = cam.pitch + (Math.random() * 0.8 - 0.2) * jitter * 0.5;
            const amt = jitter * 0.12;
            offset = [
              Math.sin(yaw) * Math.cos(pitch) * amt,
              Math.sin(pitch) * amt,
              Math.cos(yaw) * Math.cos(pitch) * amt
            ];
          } else {
            offset = [
              (Math.random() - 0.5) * jitter,
              (Math.random() - 0.5) * jitter * 0.8,
              (Math.random() - 0.5) * jitter * 0.6
            ];
          }
        }
        fn.call(item, offset, id.toString(), 'HeadMesh');
      });
    },
    fireInput(action, down) {
      attempt(() => {
        const inp = bloxd.noa?.inputs;
        if (!inp) return;
        (down ? inp.down : inp.up)._events?.[action]?.(0);
      });
    },
    updateESP(enabled) {
      attempt(() => {
        if (!bloxd.noa) return;
        const r = vals(bloxd.noa)[12];
        if (!r) return;
        const tm = vals(r).find(v => v?.thinMeshes)?.thinMeshes;
        if (Array.isArray(tm)) {
          tm.forEach(i => {
            const m = i?.meshVariations?.__DEFAULT__?.mesh;
            if (m && typeof m.renderingGroupId === "number") {
              m.renderingGroupId = enabled ? 2 : 0;
            }
          });
        }
      });
    }
  };

  const state = {
    killaura: { enabled: false, delay: 50, range: 6.7, jitter: 0.35, _last: 0 },
    aimbot: { enabled: false, range: 16, smooth: 0.8, delay: 8, fov: 135, _last: 0 },
    clicker: { left: { enabled: false, iv: null }, right: { enabled: false, iv: null }, cpsMin: 15, cpsMax: 18 },
    movement: { bhop: false, bhopChance: 100 },
    visuals: { esp: false },
    viewmodel: { enabled: false, pos: { x: 0, y: 0, z: 0 }, rot: { x: 0, y: 0, z: 0 }, spin: false, spinSpeed: { x: 0, y: 2, z: 0 }, spinAngle: { x: 0, y: 0, z: 0 }, _lastTime: 0 },
    binds: { ka: 'KeyK', aim: '', lc: 'KeyR', rc: 'KeyF', bhop: 'KeyZ', esp: '', vm: '', menu: 'ShiftRight' }
  };

  let savedBinds = {};
  const bindsStr = localStorage.getItem('clickHelper-keybinds');
  if (bindsStr) { try { savedBinds = JSON.parse(bindsStr); } catch (_) {} }
  Object.assign(state.binds, savedBinds);

  let savedSettings = {};
  const settingsStr = localStorage.getItem('clickHelper-settings');
  if (settingsStr) { try { savedSettings = JSON.parse(settingsStr); } catch (_) {} }
  if (savedSettings.killaura) Object.assign(state.killaura, savedSettings.killaura);
  if (savedSettings.aimbot) Object.assign(state.aimbot, savedSettings.aimbot);
  if (savedSettings.clicker) {
    state.clicker.cpsMin = savedSettings.clicker.cpsMin ?? state.clicker.cpsMin;
    state.clicker.cpsMax = savedSettings.clicker.cpsMax ?? state.clicker.cpsMax;
  }
  if (savedSettings.movement) state.movement.bhopChance = savedSettings.movement.bhopChance ?? state.movement.bhopChance;
  if (savedSettings.viewmodel) {
    if (savedSettings.viewmodel.pos) Object.assign(state.viewmodel.pos, savedSettings.viewmodel.pos);
    if (savedSettings.viewmodel.rot) Object.assign(state.viewmodel.rot, savedSettings.viewmodel.rot);
    if (savedSettings.viewmodel.spinSpeed) Object.assign(state.viewmodel.spinSpeed, savedSettings.viewmodel.spinSpeed);
  }

  function saveBinds() { localStorage.setItem('clickHelper-keybinds', JSON.stringify(state.binds)); }
  function saveSettings() {
    const settings = {
      killaura: { delay: state.killaura.delay, range: state.killaura.range, jitter: state.killaura.jitter },
      aimbot: { range: state.aimbot.range, smooth: state.aimbot.smooth, delay: state.aimbot.delay },
      clicker: { cpsMin: state.clicker.cpsMin, cpsMax: state.clicker.cpsMax },
      movement: { bhopChance: state.movement.bhopChance },
      viewmodel: { pos: state.viewmodel.pos, rot: state.viewmodel.rot, spinSpeed: state.viewmodel.spinSpeed }
    };
    localStorage.setItem('clickHelper-settings', JSON.stringify(settings));
  }
  let saveTimeout;
  function debouncedSave() {
    clearTimeout(saveTimeout);
    saveTimeout = setTimeout(() => { saveBinds(); saveSettings(); }, 300);
  }

  function toggleClicker(side) {
    const s = state.clicker[side];
    const action = side === 'left' ? 'primary-fire' : 'alt-fire';
    s.enabled = !s.enabled;
    clearTimeout(s.iv);
    if (s.enabled) {
      const tick = () => {
        if (!s.enabled) return;
        game.fireInput(action, true);
        setTimeout(() => game.fireInput(action, false), 20);
        let next = 1000 / (state.clicker.cpsMin + Math.random() * (state.clicker.cpsMax - state.clicker.cpsMin));
        s.iv = setTimeout(tick, Math.max(10, next));
      };
      tick();
    }
    return s.enabled;
  }

  function clearAndReload() {
    document.cookie.split(';').forEach(c => {
      const n = c.split('=')[0].trim();
      [`path=/`, `path=/;domain=${location.hostname}`, `path=/;domain=.${location.hostname}`]
        .forEach(p => document.cookie = `${n}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;${p}`);
    });
    localStorage.clear();
    sessionStorage.clear();
    location.reload();
  }

  function bindDisplay(code) {
    if (!code) return '—';
    return code.replace('Key', '').replace('Digit', '').replace('ShiftRight', 'RS').replace('ShiftLeft', 'LS')
      .replace('ControlLeft', 'LC').replace('ControlRight', 'RC').replace('AltLeft', 'LA').replace('AltRight', 'RA')
      .replace('Backquote', '`').replace('Backslash', '\\').replace('BracketLeft', '[').replace('BracketRight', ']')
      .replace('Semicolon', ';').replace('Quote', "'").replace('Comma', ',').replace('Period', '.')
      .replace('Slash', '/').replace('Minus', '-').replace('Equal', '=').replace('Space', 'SPC')
      .replace('Tab', 'TAB').replace('CapsLock', 'CAPS').replace('Enter', 'ENT').replace('Escape', 'ESC').replace('Arrow', '');
  }

  function safeAppend(el) {
    if (document.body) return document.body.appendChild(el);
    const observer = new MutationObserver(() => {
      if (document.body) { observer.disconnect(); document.body.appendChild(el); }
    });
    observer.observe(document.documentElement, { childList: true });
  }

  function buildUI() {
    if (document.getElementById('ch-root')) return;

    const css = document.createElement('style');
    css.textContent = `
      @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
      @import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css');
      #ch-root, #ch-root * { box-sizing: border-box; }
      :root {
        --ghost-bg: rgba(18, 18, 24, 0.95);
        --ghost-panel: rgba(28, 28, 40, 0.85);
        --ghost-panel-border: rgba(255,255,255,0.06);
        --ghost-accent-1: #7c5cfc;
        --ghost-accent-2: #c84cff;
        --ghost-text: #e8e4f0;
        --ghost-text-dim: #8a8499;
        --ghost-on: #7c5cfc;
        --ghost-off: rgba(60,55,80,0.6);
        --ghost-danger: #ff4466;
        --ghost-success: #44ffaa;
      }
      #ch-root { position: fixed; top: 20px; left: 20px; width: 300px; background: var(--ghost-bg); border: 1px solid var(--ghost-panel-border); border-radius: 10px; font-family: 'Inter', sans-serif; font-size: 11px; color: var(--ghost-text); z-index: 99999; box-shadow: 0 16px 40px rgba(0,0,0,0.6), 0 0 1px rgba(124,92,252,0.4); user-select: none; transition: opacity 0.3s ease, transform 0.3s ease; backdrop-filter: blur(16px) saturate(1.2); transform-origin: center center; }
      #ch-root.hidden { opacity: 0; pointer-events: none; transform: scale(0.92); }
      #ch-root.mini #ch-body, #ch-root.mini #ch-tabs { display: none; }
      #ch-hdr { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; background: linear-gradient(-45deg, #7c5cfc, #c84cff, #5c9cff, #7c5cfc); background-size: 300% 300%; animation: ghostGradient 4s ease infinite; cursor: grab; border-radius: 9px 9px 0 0; }
      @keyframes ghostGradient { 0%{background-position:0% 50%} 50%{background-position:100% 50%} 100%{background-position:0% 50%} }
      #ch-title { font-weight: 700; font-size: 14px; color: #fff; text-shadow: 0 1px 4px rgba(0,0,0,0.3); letter-spacing: 0.5px; }
      #ch-title span { opacity: 0.7; font-weight: 500; font-size: 10px; margin-left: 6px; }
      .ch-hbtn { background: rgba(255,255,255,0.2); border: none; color: #fff; font-size: 12px; cursor: pointer; padding: 2px 8px; border-radius: 4px; transition: 0.2s; display: flex; align-items: center; justify-content: center; width: 28px; height: 24px; }
      .ch-hbtn:hover { background: rgba(255,255,255,0.4); }
      .ch-hbtn i { font-size: 13px; }

      /* === TABS === */
      #ch-tabs { display: flex; background: rgba(0,0,0,0.25); border-bottom: 1px solid var(--ghost-panel-border); padding: 0; margin: 0; gap: 0; }
      .ch-tab { flex: 1; padding: 10px 0 9px 0; text-align: center; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.8px; color: var(--ghost-text-dim); cursor: pointer; transition: color 0.2s, background 0.2s, box-shadow 0.2s; position: relative; border: none; background: transparent; outline: none; font-family: 'Inter', sans-serif; }
      .ch-tab:hover { color: var(--ghost-text); background: rgba(124,92,252,0.06); }
      .ch-tab.active { color: #fff; }
      .ch-tab.active::after { content: ''; position: absolute; bottom: 0; left: 15%; right: 15%; height: 2.5px; background: linear-gradient(90deg, var(--ghost-accent-1), var(--ghost-accent-2)); border-radius: 2px 2px 0 0; box-shadow: 0 0 8px rgba(124,92,252,0.5); }
      .ch-tab i { margin-right: 5px; font-size: 12px; }

      /* === TAB PANELS === */
      .ch-tab-panel { display: none; flex-direction: column; gap: 8px; }
      .ch-tab-panel.active { display: flex; }

      #ch-body { padding: 10px; display: flex; flex-direction: column; gap: 8px; max-height: calc(100vh - 120px); overflow-y: auto; }
      #ch-body::-webkit-scrollbar { width: 4px; }
      #ch-body::-webkit-scrollbar-thumb { background: var(--ghost-accent-1); border-radius: 4px; }
      .ch-sec { display: flex; flex-direction: column; gap: 6px; background: var(--ghost-panel); padding: 10px 12px; border-radius: 6px; border: 1px solid var(--ghost-panel-border); }
      .ch-lbl { font-size: 10px; font-weight: 700; color: var(--ghost-text-dim); text-transform: uppercase; letter-spacing: 1px; padding-bottom: 4px; border-bottom: 1px solid rgba(255,255,255,0.04); margin-bottom: 2px; }
      .mod-row { display: flex; align-items: center; justify-content: space-between; padding: 2px 0; }
      .mod-left { display: flex; align-items: center; gap: 8px; flex: 1; }
      .mod-name { font-size: 12px; font-weight: 600; color: var(--ghost-text); }
      .tg { width: 30px; height: 16px; background: var(--ghost-off); border-radius: 8px; position: relative; cursor: pointer; transition: 0.25s; flex-shrink: 0; }
      .tg::after { content: ''; width: 12px; height: 12px; background: #888; border-radius: 50%; position: absolute; top: 2px; left: 2px; transition: 0.25s; }
      .tg.on { background: var(--ghost-on); box-shadow: 0 0 10px rgba(124,92,252,0.4); }
      .tg.on::after { left: 16px; background: #fff; }
      .kb { background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.08); color: var(--ghost-text-dim); font-size: 9px; font-weight: 600; padding: 2px 6px; border-radius: 4px; cursor: pointer; transition: 0.2s; display: flex; align-items: center; gap: 4px; font-family: monospace; }
      .kb:hover { background: rgba(124,92,252,0.15); border-color: rgba(124,92,252,0.4); color: var(--ghost-text); }
      .kb.listening { border-color: var(--ghost-accent-2); color: var(--ghost-accent-2); animation: kbpulse 0.8s ease infinite; }
      @keyframes kbpulse { 0%,100%{opacity:1;} 50%{opacity:0.4;} }
      .kb-x { font-size: 8px; color: rgba(255,100,100,0.5); cursor: pointer; }
      .kb-x:hover { color: var(--ghost-danger); }
      .val-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
      .val-item { display: flex; flex-direction: column; gap: 4px; }
      .val-label { font-size: 9px; color: var(--ghost-text-dim); font-weight: 600; text-transform: uppercase; }
      .val-inp { background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.08); border-radius: 4px; color: var(--ghost-text); font-family: monospace; font-size: 11px; padding: 4px 6px; text-align: center; outline: none; transition: 0.2s; width: 100%; }
      .val-inp:focus { border-color: var(--ghost-accent-1); box-shadow: 0 0 8px rgba(124,92,252,0.2); }
      .sl-row { display: flex; align-items: center; gap: 8px; margin-top: 6px; width: 100%; }
      .sl-label { font-size: 10px; color: var(--ghost-text-dim); font-weight: 600; width: 36px; flex-shrink: 0; }
      input[type=range] { flex: 1; -webkit-appearance: none; background: transparent; cursor: pointer; }
      input[type=range]::-webkit-slider-runnable-track { height: 4px; background: rgba(255,255,255,0.08); border-radius: 2px; }
      input[type=range]::-webkit-slider-thumb { height: 12px; width: 12px; border-radius: 50%; background: #fff; cursor: pointer; -webkit-appearance: none; margin-top: -4px; box-shadow: 0 0 6px rgba(124,92,252,0.6); }
      .sl-val-inp { width: 44px; flex-shrink: 0; background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.1); color: var(--ghost-accent-1); font-family: monospace; font-size: 10px; font-weight: 600; text-align: center; border-radius: 4px; outline: none; padding: 3px 0; }
      .sl-val-inp:focus { border-color: var(--ghost-accent-1); }
      .ch-action-btn { width: 100%; padding: 8px; border-radius: 6px; border: 1px solid rgba(255,68,102,0.3); background: rgba(255,68,102,0.1); color: #ff6688; font-weight: 700; font-size: 11px; cursor: pointer; transition: 0.2s; text-transform: uppercase; letter-spacing: 0.5px; }
      .ch-action-btn:hover { background: rgba(255,68,102,0.2); border-color: #ff4466; color: #ff8899; }
      /* Text input fields */
      .ch-text-inp { background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.08); border-radius: 4px; color: var(--ghost-text); font-family: 'Inter', sans-serif; font-size: 11px; padding: 6px 8px; outline: none; transition: 0.2s; width: 100%; }
      .ch-text-inp:focus { border-color: var(--ghost-accent-1); box-shadow: 0 0 8px rgba(124,92,252,0.2); }
      .ch-text-inp::placeholder { color: var(--ghost-text-dim); opacity: 0.6; }
      #ch-status { padding: 8px 16px; background: rgba(0,0,0,0.3); border-top: 1px solid var(--ghost-panel-border); font-size: 9px; color: var(--ghost-text-dim); display: flex; justify-content: space-between; align-items: center; border-radius: 0 0 9px 9px; font-weight: 600; letter-spacing: 0.5px; }
      #ch-status .dot { width: 6px; height: 6px; border-radius: 50%; display: inline-block; margin-right: 6px; }
      #ch-status .dot.green { background: var(--ghost-success); box-shadow: 0 0 8px rgba(68,255,170,0.5); }
      #ch-status .dot.red { background: var(--ghost-danger); box-shadow: 0 0 8px rgba(255,68,102,0.5); }
	  #ch-gen { background: rgba(255,68,102,0.3) !important; color: #ff8899 !important; }
	  #ch-gen:hover { background: rgba(255,68,102,0.5) !important; }
	  #ch-root input[type="text"],
	  #ch-root input[type="number"],
	  #ch-root textarea {
		   background: rgba(0,0,0,0.3) !important;
		   border: 1px solid rgba(255,255,255,0.08) !important;
		   border-radius: 4px !important;
		   color: var(--ghost-text) !important;
		   font-family: 'Inter', monospace !important;
		   font-size: 11px !important;
		   padding: 4px 6px !important;
		   outline: none !important;
		   transition: 0.2s !important;
		   box-shadow: none !important;
	  }

	  #ch-root input[type="text"]:focus,
	  #ch-root input[type="number"]:focus,
	  #ch-root textarea:focus {
		   border-color: var(--ghost-accent-1) !important;
		   box-shadow: 0 0 8px rgba(124,92,252,0.2) !important;
	  }

	  #ch-root input::placeholder,
	  #ch-root textarea::placeholder {
		   color: var(--ghost-text-dim) !important;
		   opacity: 0.6 !important;
	  }
    `;
    document.head.appendChild(css);

    const mkToggle = (id, name, bindKey) => {
      const disp = bindDisplay(state.binds[bindKey]);
      const hasKey = !!state.binds[bindKey];
      return `
        <div class="mod-row" id="row-${id}">
          <div class="mod-left">
            <div class="tg" id="tg-${id}"></div>
            <span class="mod-name">${name}</span>
          </div>
          ${bindKey ? `
          <div class="mod-right">
            <div class="kb" id="bind-${bindKey}" data-action="${bindKey}">
              <span class="kb-text">${hasKey ? disp : '—'}</span>
              ${hasKey ? `<span class="kb-x" data-unbind="${bindKey}">✕</span>` : `<span class="kb-x" data-unbind="${bindKey}" style="display:none">✕</span>`}
            </div>
          </div>` : ''}
        </div>`;
    };

    const mkSlider = (id, label, min, max, val, step = '1') =>
      `<div class="sl-row">
        <span class="sl-label">${label}</span>
        <input type="range" id="${id}" min="${min}" max="${max}" value="${val}" step="${step}">
        <input type="number" class="sl-val-inp" id="${id}-v" value="${val}" step="${step}" min="${min}" max="${max}">
      </div>`;

    const mkTextInput = (id, placeholder, value = '') =>
      `<input type="text" class="ch-text-inp" id="${id}" placeholder="${placeholder}" value="${value}">`;

    const root = document.createElement('div');
    root.id = 'ch-root';
    root.innerHTML = `
      <div id="ch-hdr">
        <span id="ch-title">CLICK HELPER <span>v1.5.1.5</span></span>
        <div style="display:flex;gap:5px;">
          <button class="ch-hbtn" id="ch-gen" title="New Account" style="background: rgba(255,68,102,0.3); color: #ff8899;"><i class="fa-solid fa-user"></i></button>
          <button class="ch-hbtn" id="ch-inject" title="Re-Inject"><i class="fa-solid fa-syringe"></i></button>
          <button class="ch-hbtn" id="ch-min" title="Minimize">—</button>
        </div>
      </div>
      <div id="ch-tabs">
        <button class="ch-tab active" data-tab="combat"><i class="fa-solid fa-crosshairs"></i>Combat</button>
        <button class="ch-tab" data-tab="movement"><i class="fa-solid fa-person-running"></i>Move</button>
        <button class="ch-tab" data-tab="visuals"><i class="fa-solid fa-eye"></i>Visuals</button>
      </div>
      <div id="ch-body">
        <!-- COMBAT TAB -->
        <div class="ch-tab-panel active" data-panel="combat">
          <div class="ch-sec">
            <div class="ch-lbl">Kill Aura</div>
            ${mkToggle('ka', 'Kill Aura', 'ka')}
            <div style="margin-top: 6px;">
              ${mkSlider('ka-delay', 'Delay', 0, 1000, state.killaura.delay, 5)}
              ${mkSlider('ka-range', 'Range', 1, 20, state.killaura.range, 0.1)}
              ${mkSlider('ka-jitter', 'Jitter', 0, 0.6, state.killaura.jitter, 0.01)}
            </div>
          </div>
          <div class="ch-sec">
            <div class="ch-lbl">Aimbot</div>
            ${mkToggle('aim', 'Aimbot', 'aim')}
            <div style="margin-top: 6px;">
              ${mkSlider('aim-range', 'Range', 5, 100, state.aimbot.range, 1)}
              ${mkSlider('aim-smooth', 'Smooth', 0.01, 1, state.aimbot.smooth, 0.01)}
              ${mkSlider('aim-delay', 'Delay', 0, 500, state.aimbot.delay, 5)}
              ${mkSlider('aim-fov', 'FOV', 1, 180, state.aimbot.fov, 1)}
            </div>
          </div>
          <div class="ch-sec">
            <div class="ch-lbl">Auto Clicker</div>
            ${mkToggle('lc', 'Left Click', 'lc')}
            ${mkToggle('rc', 'Right Click', 'rc')}
            <div class="val-grid" style="margin-top: 6px;">
              <div class="val-item"><span class="val-label">Min CPS</span><input class="val-inp" id="inp-cmin" type="number" value="${state.clicker.cpsMin}" step="1" min="1" max="50"></div>
              <div class="val-item"><span class="val-label">Max CPS</span><input class="val-inp" id="inp-cmax" type="number" value="${state.clicker.cpsMax}" step="1" min="1" max="50"></div>
            </div>
          </div>
        </div>

        <!-- MOVEMENT TAB -->
        <div class="ch-tab-panel" data-panel="movement">
          <div class="ch-sec">
            <div class="ch-lbl">Movement</div>
            ${mkToggle('bhop', 'BHOP', 'bhop')}
            <div style="margin-top: 4px;">
              ${mkSlider('bhop-chance', '%', 1, 100, state.movement.bhopChance)}
            </div>
          </div>
        </div>

        <!-- VISUALS TAB -->
        <div class="ch-tab-panel" data-panel="visuals">
          <div class="ch-sec">
            <div class="ch-lbl">ESP</div>
            ${mkToggle('esp', 'Player ESP', 'esp')}
          </div>
          <div class="ch-sec">
            <div class="ch-lbl">ViewModel</div>
            ${mkToggle('vm', 'ViewModel', 'vm')}
            <div style="font-size: 8px; font-weight: 700; color: var(--ghost-text-dim); margin-top: 8px;">POSITION</div>
            ${mkSlider('vm-px', 'X', -3, 3, state.viewmodel.pos.x, '0.05')}
            ${mkSlider('vm-py', 'Y', -3, 3, state.viewmodel.pos.y, '0.05')}
            ${mkSlider('vm-pz', 'Z', -3, 3, state.viewmodel.pos.z, '0.05')}
            <div style="font-size: 8px; font-weight: 700; color: var(--ghost-text-dim); margin-top: 10px;">ROTATION</div>
            ${mkSlider('vm-rx', 'X', -3.14, 3.14, state.viewmodel.rot.x, '0.01')}
            ${mkSlider('vm-ry', 'Y', -3.14, 3.14, state.viewmodel.rot.y, '0.01')}
            ${mkSlider('vm-rz', 'Z', -3.14, 3.14, state.viewmodel.rot.z, '0.01')}
            <div style="border-top: 1px solid rgba(255,255,255,0.04); margin: 8px 0 4px 0;"></div>
            ${mkToggle('spin', 'Spin Tool', '')}
            <div style="font-size: 8px; font-weight: 700; color: var(--ghost-text-dim); margin-top: 6px;">SPIN SPEED</div>
            ${mkSlider('spin-sx', 'X', -10, 10, state.viewmodel.spinSpeed.x, '0.1')}
            ${mkSlider('spin-sy', 'Y', -10, 10, state.viewmodel.spinSpeed.y, '0.1')}
            ${mkSlider('spin-sz', 'Z', -10, 10, state.viewmodel.spinSpeed.z, '0.1')}
          </div>
        </div>
      </div>
      <div id="ch-status">
        <span><span class="dot red" id="st-dot"></span><span id="st-txt">Waiting for game</span></span>
        <span style="opacity:0.6">RSHIFT = MENU</span>
      </div>
    `;

    safeAppend(root);

    // === TAB SWITCHING ===
    const tabs = root.querySelectorAll('.ch-tab');
    const panels = root.querySelectorAll('.ch-tab-panel');
    tabs.forEach(tab => {
      tab.addEventListener('click', () => {
        const target = tab.dataset.tab;
        tabs.forEach(t => t.classList.remove('active'));
        panels.forEach(p => p.classList.remove('active'));
        tab.classList.add('active');
        const panel = root.querySelector(`.ch-tab-panel[data-panel="${target}"]`);
        if (panel) panel.classList.add('active');
      });
    });

    // === BINDINGS ===
    const bindToggle = (id, onChange) => {
      const row = document.getElementById(`row-${id}`);
      const tg = document.getElementById(`tg-${id}`);
      if (!row || !tg) return;
      row.addEventListener('click', e => {
        if (e.target.closest('.kb') || e.target.classList.contains('kb-x') || e.target.tagName === 'INPUT') return;
        const res = onChange();
        tg.classList.toggle('on', res);
      });
    };

    bindToggle('ka', () => state.killaura.enabled = !state.killaura.enabled);
    bindToggle('aim', () => state.aimbot.enabled = !state.aimbot.enabled);
    bindToggle('lc', () => toggleClicker('left'));
    bindToggle('rc', () => toggleClicker('right'));
    bindToggle('bhop', () => state.movement.bhop = !state.movement.bhop);
    bindToggle('esp', () => state.visuals.esp = !state.visuals.esp);
    bindToggle('vm', () => state.viewmodel.enabled = !state.viewmodel.enabled);
    bindToggle('spin', () => state.viewmodel.spin = !state.viewmodel.spin);

    let listeningBind = null;
    root.addEventListener('click', e => {
      const unbindEl = e.target.closest('[data-unbind]');
      if (unbindEl) {
        e.stopPropagation();
        const action = unbindEl.dataset.unbind;
        state.binds[action] = '';
        const kbEl = document.getElementById(`bind-${action}`);
        if (kbEl) {
          kbEl.querySelector('.kb-text').innerText = '—';
          const x = kbEl.querySelector('.kb-x'); if (x) x.style.display = 'none';
        }
        if (listeningBind === action) { kbEl?.classList.remove('listening'); listeningBind = null; }
        debouncedSave();
        return;
      }
      const kbEl = e.target.closest('.kb');
      if (!kbEl) return;
      const action = kbEl.dataset.action;
      if (!action) return;
      if (listeningBind) {
        const prev = document.getElementById(`bind-${listeningBind}`);
        if (prev) {
          prev.classList.remove('listening');
          prev.querySelector('.kb-text').innerText = bindDisplay(state.binds[listeningBind]) || '—';
        }
      }
      listeningBind = action;
      kbEl.classList.add('listening');
      kbEl.querySelector('.kb-text').innerText = '…';
    });

    const toggleUiById = (id) => {
      const tg = document.getElementById(`tg-${id}`);
      if (!tg) return;
      let res = false;
      switch (id) {
        case 'ka': res = (state.killaura.enabled = !state.killaura.enabled); break;
        case 'aim': res = (state.aimbot.enabled = !state.aimbot.enabled); break;
        case 'lc': res = toggleClicker('left'); break;
        case 'rc': res = toggleClicker('right'); break;
        case 'bhop': res = (state.movement.bhop = !state.movement.bhop); break;
        case 'esp': res = (state.visuals.esp = !state.visuals.esp); break;
        case 'vm': res = (state.viewmodel.enabled = !state.viewmodel.enabled); break;
      }
      tg.classList.toggle('on', res);
    };

    const preventInput = () => attempt(() => { if (bloxd.noa?.inputs) bloxd.noa.inputs._paused = true; });
    const restoreInput = () => attempt(() => { if (bloxd.noa?.inputs) bloxd.noa.inputs._paused = false; });

    const bindInp = (id, obj, key) => {
      const el = document.getElementById(id);
      if (!el) return;
      el.addEventListener('input', e => { obj[key] = parseFloat(e.target.value) || 0; debouncedSave(); });
      el.addEventListener('focus', preventInput);
      el.addEventListener('blur', restoreInput);
      el.addEventListener('keydown', e => e.stopPropagation(), true);
    };
    bindInp('inp-cmin', state.clicker, 'cpsMin');
    bindInp('inp-cmax', state.clicker, 'cpsMax');

    const bindSlider = (id, cb) => {
      const range = document.getElementById(id);
      const num = document.getElementById(id + '-v');
      if (!range || !num) return;
      const update = (valStr) => {
        let v = parseFloat(valStr);
        if (isNaN(v)) return;
        range.value = v; num.value = v; cb(v); debouncedSave();
      };
      range.addEventListener('input', e => update(e.target.value));
      num.addEventListener('input', e => update(e.target.value));
      num.addEventListener('focus', preventInput);
      num.addEventListener('blur', restoreInput);
      num.addEventListener('keydown', e => e.stopPropagation(), true);
    };

    bindSlider('ka-delay', v => state.killaura.delay = Math.max(0, Math.floor(v)));
    bindSlider('ka-range', v => state.killaura.range = v);
    bindSlider('ka-jitter', v => state.killaura.jitter = v);
    bindSlider('aim-range', v => state.aimbot.range = v);
    bindSlider('aim-smooth', v => state.aimbot.smooth = v);
    bindSlider('aim-delay', v => state.aimbot.delay = v);
    bindSlider('aim-fov', v => state.aimbot.fov = v);
    bindSlider('bhop-chance', v => state.movement.bhopChance = v);
    bindSlider('vm-px', v => state.viewmodel.pos.x = v);
    bindSlider('vm-py', v => state.viewmodel.pos.y = v);
    bindSlider('vm-pz', v => state.viewmodel.pos.z = v);
    bindSlider('vm-rx', v => state.viewmodel.rot.x = v);
    bindSlider('vm-ry', v => state.viewmodel.rot.y = v);
    bindSlider('vm-rz', v => state.viewmodel.rot.z = v);
    bindSlider('spin-sx', v => state.viewmodel.spinSpeed.x = v);
    bindSlider('spin-sy', v => state.viewmodel.spinSpeed.y = v);
    bindSlider('spin-sz', v => state.viewmodel.spinSpeed.z = v);

    // Bind all text inputs to pause game input on focus
    root.querySelectorAll('.ch-text-inp').forEach(el => {
      el.addEventListener('focus', preventInput);
      el.addEventListener('blur', restoreInput);
      el.addEventListener('keydown', e => e.stopPropagation(), true);
    });

    document.getElementById('ch-gen').addEventListener('click', () => {
      if (confirm("Are you sure you want to clear cookies/local storage and generate a new account?")) clearAndReload();
    });

    document.getElementById('ch-inject').addEventListener('click', () => bloxd.init());

    let mini = false, visible = true;
    document.getElementById('ch-min').addEventListener('click', () => {
      mini = !mini; root.classList.toggle('mini', mini);
    });

    document.addEventListener('keydown', e => {
      if (listeningBind) {
        e.preventDefault(); e.stopPropagation();
        const kbEl = document.getElementById(`bind-${listeningBind}`);
        if (e.code === 'Escape') {
          if (kbEl) { kbEl.querySelector('.kb-text').innerText = bindDisplay(state.binds[listeningBind]) || '—'; kbEl.classList.remove('listening'); }
        } else if (e.code === 'Backspace') {
          state.binds[listeningBind] = '';
          if (kbEl) {
            kbEl.querySelector('.kb-text').innerText = '—';
            kbEl.classList.remove('listening');
            const x = kbEl.querySelector('.kb-x'); if (x) x.style.display = 'none';
          }
        } else {
          state.binds[listeningBind] = e.code;
          if (kbEl) {
            kbEl.querySelector('.kb-text').innerText = bindDisplay(e.code);
            kbEl.classList.remove('listening');
            const x = kbEl.querySelector('.kb-x'); if (x) x.style.display = '';
          }
        }
        listeningBind = null;
        debouncedSave();
        return;
      }
      if (e.code === state.binds.menu) {
        visible = !visible;
        root.classList.toggle('hidden', !visible);
      }
      if (document.activeElement?.tagName === 'INPUT') return;
      for (const [id, key] of Object.entries(state.binds)) {
        if (id !== 'menu' && key && e.code === key) toggleUiById(id);
      }
    }, true);

    const hdr = document.getElementById('ch-hdr');
    let ox = 0, oy = 0, mx = 0, my = 0;
    hdr.addEventListener('mousedown', e => {
      if (e.target !== hdr && e.target.id !== 'ch-title' && !e.target.closest('#ch-title')) return;
      e.preventDefault();
      mx = e.clientX; my = e.clientY;
      const move = ev => {
        ox = mx - ev.clientX; oy = my - ev.clientY;
        mx = ev.clientX; my = ev.clientY;
        root.style.top = (root.offsetTop - oy) + 'px';
        root.style.left = (root.offsetLeft - ox) + 'px';
        root.style.right = 'unset';
      };
      const up = () => {
        document.removeEventListener('mousemove', move);
        document.removeEventListener('mouseup', up);
        hdr.style.cursor = 'grab';
      };
      document.addEventListener('mousemove', move);
      document.addEventListener('mouseup', up);
      hdr.style.cursor = 'grabbing';
    });
  }

  let inGame = false;
  function rafLoop(ts) {
    requestAnimationFrame(rafLoop);
    const dot = document.getElementById('st-dot');
    const txt = document.getElementById('st-txt');

    game.updateESP(state.visuals.esp);

    const nowInGame = game.inGame();
    if (nowInGame !== inGame) {
      inGame = nowInGame;
      if (dot && txt) {
        if (inGame) { dot.className = 'dot green'; txt.innerText = 'IN GAME'; }
        else { dot.className = 'dot red'; txt.innerText = 'Waiting for game'; }
      }
    }
    if (!inGame) return;

    // Killaura
    if (state.killaura.enabled && Date.now() - state.killaura._last >= state.killaura.delay) {
      const selfPos = game.getPos(1);
      if (selfPos) {
        for (const id of vals(game.getIds())) {
          if (id == 1) continue;
          const p = game.getPos(id);
          if (!p) continue;
          if (Math.hypot(p[0]-selfPos[0], p[1]-selfPos[1], p[2]-selfPos[2]) <= state.killaura.range) {
            state.killaura._last = Date.now();
            game.doAttack(id);
          }
        }
      }
    }

    // Aimbot
    if (state.aimbot.enabled && Date.now() - state.aimbot._last >= state.aimbot.delay) {
      const selfPos = game.getPos(1);
      const cam = bloxd.noa?.camera;
      if (selfPos && cam) {
        let bestTarget = null;
        let minDist = state.aimbot.range;

        for (const id of vals(game.getIds())) {
          if (id == 1) continue;
          const p = game.getPos(id);
          if (!p) continue;

          const dist = Math.hypot(p[0]-selfPos[0], p[1]-selfPos[1], p[2]-selfPos[2]);
          if (dist < minDist) {
            // --- FOV CHECK START ---
            const dx = p[0] - selfPos[0];
            const dz = p[2] - selfPos[2];
            const targetYaw = Math.atan2(dx, dz);

            const diff = Math.atan2(Math.sin(targetYaw - cam.heading), Math.cos(targetYaw - cam.heading));
            const diffDegrees = Math.abs(diff * (180 / Math.PI));

            if (diffDegrees <= state.aimbot.fov) {
              minDist = dist;
              bestTarget = p;
            }
            // --- FOV CHECK END ---
          }
        }

        if (bestTarget) {
          const dx = bestTarget[0] - selfPos[0];
          const dy = (bestTarget[1] + 1.5) - (selfPos[1] + 1.5);
          const dz = bestTarget[2] - selfPos[2];
          const yaw = Math.atan2(dx, dz);
          const pitch = -Math.atan2(dy, Math.hypot(dx, dz));

          const smooth = state.aimbot.smooth;
          cam.heading += Math.atan2(Math.sin(yaw - cam.heading), Math.cos(yaw - cam.heading)) * smooth;
          cam.pitch += (Math.max(-1.57, Math.min(1.57, pitch)) - cam.pitch) * smooth;
          state.aimbot._last = Date.now();
        }
      }
    };

    // BHOP
    const inputs = bloxd.noa?.inputs?.state;
    const mov = game.getMovement(1);
    if (state.movement.bhop && inputs && mov) {
      const isMoving = inputs.forward || inputs.backward || inputs.left || inputs.right;
      const onGround = attempt(() => mov.isOnGround());
      if (onGround && isMoving && Math.random() * 100 <= state.movement.bhopChance) {
        inputs.jump = true;
      } else {
        inputs.jump = false;
      }
    }

    // ViewModel
    if (state.viewmodel.enabled) {
      const item = game.getHeld(1);
      if (item) {
        const vdt = state.viewmodel._lastTime ? (ts - state.viewmodel._lastTime) / 1000 : 0;
        state.viewmodel._lastTime = ts;

        if (state.viewmodel.spin && item?.typeObj.constructor.name === "Tool") {
          state.viewmodel.spinAngle.x += state.viewmodel.spinSpeed.x * vdt;
          state.viewmodel.spinAngle.y += state.viewmodel.spinSpeed.y * vdt;
          state.viewmodel.spinAngle.z += state.viewmodel.spinSpeed.z * vdt;
        }

        if (item.firstPersonPosOffset) {
          item.firstPersonPosOffset.x = state.viewmodel.pos.x;
          item.firstPersonPosOffset.y = state.viewmodel.pos.y;
          item.firstPersonPosOffset.z = state.viewmodel.pos.z;
        }

        if (item.firstPersonRotation && item?.typeObj.constructor.name === "Tool") {
          item.firstPersonRotation.x = state.viewmodel.rot.x + (state.viewmodel.spin ? state.viewmodel.spinAngle.x : 0);
          item.firstPersonRotation.y = state.viewmodel.rot.y + (state.viewmodel.spin ? state.viewmodel.spinAngle.y : 0);
          item.firstPersonRotation.z = state.viewmodel.rot.z + (state.viewmodel.spin ? state.viewmodel.spinAngle.z : 0);
        }
      }
    }
  }

  let uiBuilt = false;
  function initUI() {
    if (!uiBuilt) { uiBuilt = true; buildUI(); }
  }

  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', initUI);
  else initUI();

  bloxd.init();

  let coreStarted = false;
  const poll = setInterval(() => {
    if (!uiBuilt) initUI();
    if (window.noa && !coreStarted) {
      coreStarted = true;
      bloxd.noa = window.noa;
      requestAnimationFrame(rafLoop);
      clearInterval(poll);
    }
  }, 250);
})();