Bloxd Client

Bloxd's Most Safest Secure Client ever made! Client is still on Work! This can make u banned! We will try and make it unbannable!

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Bloxd Client
// @namespace    https://bloxd.io/
// @version      1.0.0
// @description  Bloxd's Most Safest Secure Client ever made! Client is still on Work! This can make u banned! We will try and make it unbannable!
// @author       Nullscape
// @match        https://bloxd.io/*
// @match        https://staging.bloxd.io/*
// @match        https://now.gg/apps/bloxd/51240/bloxd-io.html/*
// @match        https://www.crazygames.com/game/bloxdhop-io/*
// @grant        none
// @icon         https://i.postimg.cc/x8QH5nD3/bloxd.jpg
// @run-at       document-end
// @license      CC-BY-4.0
// ==/UserScript==
(function () {
  'use strict';
  const CLIENT_NAME = 'Bloxd Client';
  const CLIENT_VERSION = '1.0.0';
  const STORAGE_KEY = 'bloxd_client_v1';
  const THEMES = {
    blue: {
      accent: '#5ab4ff',
      accentHover: '#3a9aee',
      border: '#2a6aaa',
      bg: 'rgba(12, 22, 40, 0.93)',
      tabBg: 'rgba(0,0,0,0.18)',
    },
    brown: {
      accent: '#c49a6c',
      accentHover: '#a87d52',
      border: '#7a5c3a',
      bg: 'rgba(28, 18, 12, 0.93)',
      tabBg: 'rgba(0,0,0,0.18)',
    },
    purple: {
      accent: '#b36bff',
      accentHover: '#9a50ee',
      border: '#7a2acc',
      bg: 'rgba(20, 12, 38, 0.93)',
      tabBg: 'rgba(0,0,0,0.18)',
    },
  };

  const state = {
    menuOpen: false,
    activeTab: 'combat',
    editMode: false,
    theme: 'blue',
    shaderMode: null,

    widgets: {
      autoSprint:     false,
      keystrokes:     false,
      cps:            false,
      ping:           false,
      fps:            false,
      combo:          false,
      blockOverlay:   false,
      zoom:           false,
      hitbox:         false,
      inventoryBlur:  false,
      armorStatus:    false,
    },

    blockOverlay: {
      outlineEnabled:  true,
      outlineColor:    '#ffffff',
      outlineThickness: 2,
      fillEnabled:     false,
      fillColor:       '#ffffff',
    },

    capes: {
      enabled: false,
      color:   '#3a8ef6',
    },

    widgetPositions: {},
  };

  function loadState() {
    try {
      const saved = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
      if (saved.widgets) Object.assign(state.widgets, saved.widgets);
      if (saved.blockOverlay) Object.assign(state.blockOverlay, saved.blockOverlay);
      if (saved.capes) Object.assign(state.capes, saved.capes);
      if (saved.widgetPositions) state.widgetPositions = saved.widgetPositions;
      if (saved.shaderMode !== undefined) state.shaderMode = saved.shaderMode;
      if (saved.theme) state.theme = saved.theme;
    } catch (e) { /* ignore corrupt storage */ }
  }

  function saveState() {
    try {
      localStorage.setItem(STORAGE_KEY, JSON.stringify({
        widgets:         state.widgets,
        blockOverlay:    state.blockOverlay,
        capes:           state.capes,
        widgetPositions: state.widgetPositions,
        shaderMode:      state.shaderMode,
        theme:           state.theme,
      }));
    } catch (e) {}
  }

  function T() { return THEMES[state.theme] || THEMES.blue; }

  const tabTitleManager = {
    focused:   '🔥 Bloxd Client',
    unfocused: '👋 Come back to Bloxd!',
    interval:  null,

    init() {
      this.interval = setInterval(() => {
        document.title = document.hasFocus()
          ? this.focused
          : this.unfocused;
      }, 1000);
    },
  };

  const trackerBlocker = {
    blocklist: [
      'google-analytics.com',
      'googletagmanager.com',
      'doubleclick.net',
      'connect.facebook.net',
      'facebook.com/tr',
      'hotjar.com',
      'mixpanel.com',
      'segment.io',
      'segment.com',
      'amplitude.com',
      'clarity.ms',
      'adservice.google.com',
      'pagead2.googlesyndication.com',
      'ad.doubleclick.net',
    ],

    isBlocked(url) {
      const s = String(url);
      return this.blocklist.some(domain => s.includes(domain));
    },

    init() {
      const self = this;

      // Override XMLHttpRequest
      const origOpen = XMLHttpRequest.prototype.open;
      XMLHttpRequest.prototype.open = function (method, url, ...rest) {
        if (self.isBlocked(url)) {
          console.debug('[BloxdClient] Tracker blocked (XHR):', url);
          return; // silently drop
        }
        return origOpen.call(this, method, url, ...rest);
      };

      // Override fetch
      const origFetch = window.fetch.bind(window);
      window.fetch = function (input, ...args) {
        const url = typeof input === 'string'
          ? input
          : (input && typeof input.url === 'string' ? input.url : '');
        if (self.isBlocked(url)) {
          console.debug('[BloxdClient] Tracker blocked (fetch):', url);
          return Promise.resolve(new Response('', { status: 200 }));
        }
        return origFetch(input, ...args);
      };
    },
  };

  const SHADER_CONFIGS = {
    sunny: {
      label: '☀️ Sunny',
      filterId: 'bc-f-sunny',
      matrix: '1.20 -0.05  0.00 0  0.08 ' +
              '0.00  1.10  0.00 0  0.03 ' +
              '0.00  0.00  0.90 0 -0.05 ' +
              '0     0     0    1  0',
      transfer: { r: { type:'gamma', amp:1, exp:0.90, off:0.00 },
                  g: { type:'gamma', amp:1, exp:0.93, off:0.00 },
                  b: { type:'gamma', amp:1, exp:1.05, off:0.00 } },
      vignette: null,
      desc: '6500K · Brightness +15% · Warm temperature shift',
    },
    sunset: {
      label: '🌇 Sunset',
      filterId: 'bc-f-sunset',
      matrix: '1.35 -0.05  0.00 0  0.10 ' +
              '0.05  1.05  0.00 0  0.02 ' +
              '0.00  0.00  0.78 0 -0.10 ' +
              '0     0     0    1  0',
      transfer: { r: { type:'gamma', amp:1, exp:0.85, off:0.00 },
                  g: { type:'gamma', amp:1, exp:0.92, off:0.00 },
                  b: { type:'gamma', amp:1, exp:1.12, off:-0.04 } },
      vignette: null,
      desc: '4200K · High contrast · Deep orange tone',
    },
    night: {
      label: '🌙 Night',
      filterId: 'bc-f-night',
      matrix: '0.58  0.05  0.05 0 -0.02 ' +
              '0.00  0.68  0.05 0 -0.02 ' +
              '0.08  0.05  0.92 0  0.06 ' +
              '0     0     0    1  0',
      transfer: { r: { type:'linear', slope:0.88, inter:-0.02 },
                  g: { type:'linear', slope:0.92, inter:-0.02 },
                  b: { type:'linear', slope:1.05, inter: 0.02 } },
      vignette: 'radial-gradient(ellipse 70% 70% at 50% 50%, transparent 40%, rgba(0,0,12,0.72) 100%)',
      desc: '9000K · Darkness −30% · Cool blue moonlight',
    },
  };

  const shaderManager = {
    _svgEl: null,
    _vignetteEl: null,
    _injectSVG() {
      if (this._svgEl) return;
      const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
      svg.setAttribute('style', 'position:absolute;width:0;height:0;overflow:hidden;pointer-events:none;');
      svg.setAttribute('aria-hidden', 'true');
      const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');

      for (const [, cfg] of Object.entries(SHADER_CONFIGS)) {
        const filter = document.createElementNS('http://www.w3.org/2000/svg', 'filter');
        filter.setAttribute('id', cfg.filterId);
        filter.setAttribute('color-interpolation-filters', 'sRGB');
        filter.setAttribute('x', '0%'); filter.setAttribute('y', '0%');
        filter.setAttribute('width', '100%'); filter.setAttribute('height', '100%');

        const cm = document.createElementNS('http://www.w3.org/2000/svg', 'feColorMatrix');
        cm.setAttribute('type', 'matrix');
        cm.setAttribute('values', cfg.matrix);
        filter.appendChild(cm);

        const ct = document.createElementNS('http://www.w3.org/2000/svg', 'feComponentTransfer');
        const channels = { r: 'feFuncR', g: 'feFuncG', b: 'feFuncB' };
        for (const [ch, tag] of Object.entries(channels)) {
          const fn = document.createElementNS('http://www.w3.org/2000/svg', tag);
          const t = cfg.transfer[ch];
          fn.setAttribute('type', t.type);
          if (t.type === 'gamma') {
            fn.setAttribute('amplitude', t.amp);
            fn.setAttribute('exponent', t.exp);
            fn.setAttribute('offset', t.off);
          } else {
            fn.setAttribute('slope', t.slope);
            fn.setAttribute('intercept', t.inter);
          }
          ct.appendChild(fn);
        }
        filter.appendChild(ct);
        defs.appendChild(filter);
      }

      svg.appendChild(defs);
      document.body.appendChild(svg);
      this._svgEl = svg;

      this._vignetteEl = document.createElement('div');
      this._vignetteEl.id = 'bc-vignette';
      this._vignetteEl.style.cssText = `
        position: fixed; top: 0; left: 0;
        width: 100%; height: 100%;
        pointer-events: none;
        z-index: 9996;
        display: none;
        opacity: 0;
        transition: opacity 0.4s ease;
      `;
      document.body.appendChild(this._vignetteEl);
    },

    _getCanvas() {
      return document.querySelector('canvas');
    },

    apply(mode) {
      this._injectSVG();
      const canvas = this._getCanvas();
      if (!canvas) return;

      if (!mode || !SHADER_CONFIGS[mode]) {
        canvas.style.filter = '';
        if (this._vignetteEl) {
          this._vignetteEl.style.opacity = '0';
          setTimeout(() => {
            if (this._vignetteEl) this._vignetteEl.style.display = 'none';
          }, 420);
        }
        state.shaderMode = null;
      } else {
        const cfg = SHADER_CONFIGS[mode];
        canvas.style.filter = `url(#${cfg.filterId})`;

        if (this._vignetteEl) {
          if (cfg.vignette) {
            this._vignetteEl.style.background = cfg.vignette;
            this._vignetteEl.style.display = 'block';
            requestAnimationFrame(() => {
              if (this._vignetteEl) this._vignetteEl.style.opacity = '1';
            });
          } else {
            this._vignetteEl.style.opacity = '0';
            setTimeout(() => {
              if (this._vignetteEl) this._vignetteEl.style.display = 'none';
            }, 420);
          }
        }
        state.shaderMode = mode;
      }
      saveState();
    },

    init() {
      if (!state.shaderMode) return;
      const tryApply = setInterval(() => {
        if (document.querySelector('canvas')) {
          this.apply(state.shaderMode);
          clearInterval(tryApply);
        }
      }, 600);
    },
  };

  const metrics = {
    fps:         0,
    cps:         0,
    ping:        0,
    combo:       0,
    _clicks:     [],
    _lastCombo:  Date.now(),
    _frameCount: 0,
    _lastSecond: performance.now(),

    init() {
      const tick = () => {
        this._frameCount++;
        const now = performance.now();
        if (now - this._lastSecond >= 1000) {
          this.fps = this._frameCount;
          this._frameCount = 0;
          this._lastSecond = now;
        }
        requestAnimationFrame(tick);
      };
      requestAnimationFrame(tick);

      document.addEventListener('mousedown', () => {
        const now = Date.now();
        this._clicks.push(now);
        this._clicks = this._clicks.filter(t => now - t < 1000);
        this.cps = this._clicks.length;
        this.combo++;
        this._lastCombo = now;
      }, { passive: true });

      setInterval(() => {
        if (Date.now() - this._lastCombo > 1000) this.combo = 0;
      }, 500);

      const measurePing = async () => {
        try {
          const start = performance.now();
          await fetch('https://bloxd.io/favicon.ico', {
            method: 'HEAD',
            cache:  'no-store',
          });
          this.ping = Math.round(performance.now() - start);
        } catch (_) {
          this.ping = -1;
        }
      };
      setTimeout(() => {
        measurePing();
        setInterval(measurePing, 5000);
      }, 8000);
    },
  };

  const keys = { W: false, A: false, S: false, D: false, SPACE: false, LMB: false, RMB: false };

  function initKeystrokes() {
    const MAP = { w: 'W', a: 'A', s: 'S', d: 'D', ' ': 'SPACE' };
    document.addEventListener('keydown', e => {
      const k = MAP[e.key.toLowerCase()];
      if (k) keys[k] = true;
    }, { passive: true });
    document.addEventListener('keyup', e => {
      const k = MAP[e.key.toLowerCase()];
      if (k) keys[k] = false;
    }, { passive: true });
    document.addEventListener('mousedown', e => {
      if (e.button === 0) keys.LMB = true;
      if (e.button === 2) keys.RMB = true;
    }, { passive: true });
    document.addEventListener('mouseup', e => {
      if (e.button === 0) keys.LMB = false;
      if (e.button === 2) keys.RMB = false;
    }, { passive: true });
  }

  function initAutoSprint() {
    let sprintActive = false;
    let isSynthetic = false;

    document.addEventListener('keydown', e => {
      if (isSynthetic || e.repeat) return;
      if (!state.widgets.autoSprint) return;
      if ((e.key === 'w' || e.key === 'W') && !sprintActive) {
        sprintActive = true;
        isSynthetic = true;
        document.dispatchEvent(new KeyboardEvent('keyup', {
          key: 'w', code: 'KeyW', keyCode: 87, which: 87,
          bubbles: true, cancelable: true,
        }));
        setTimeout(() => {
          document.dispatchEvent(new KeyboardEvent('keydown', {
            key: 'w', code: 'KeyW', keyCode: 87, which: 87,
            bubbles: true, cancelable: true,
          }));
          isSynthetic = false;
        }, 25);
      }
    });

    document.addEventListener('keyup', e => {
      if (isSynthetic) return;
      if (e.key === 'w' || e.key === 'W') sprintActive = false;
    });
  }

  const zoom = { level: 1, target: 1 };

  function initZoom() {
    document.addEventListener('keydown', e => {
      if (state.widgets.zoom && e.code === 'KeyC') zoom.target = 0.55;
    }, { passive: true });
    document.addEventListener('keyup', e => {
      if (e.code === 'KeyC') zoom.target = 1;
    }, { passive: true });

    const animateZoom = () => {
      if (Math.abs(zoom.level - zoom.target) > 0.001) {
        zoom.level += (zoom.target - zoom.level) * 0.13;
        const canvas = document.querySelector('canvas');
        if (canvas && state.widgets.zoom) {
          canvas.style.transform = zoom.level !== 1 ? `scale(${1 / zoom.level})` : '';
          canvas.style.transformOrigin = 'center center';
        }
      }
      requestAnimationFrame(animateZoom);
    };
    requestAnimationFrame(animateZoom);
  }

  function applyInventoryBlur() {
    const style = document.createElement('style');
    style.id = 'bc-inv-blur';
    style.textContent = `
      .bc-inv-active { filter: blur(6px) brightness(0.7) !important; transition: filter 0.2s; }
    `;
    document.head.appendChild(style);

    document.addEventListener('keydown', e => {
      if (!state.widgets.inventoryBlur) return;
      if (e.code === 'KeyE' || e.code === 'Tab') {
        document.querySelector('canvas')?.classList.add('bc-inv-active');
      }
    }, { passive: true });
    document.addEventListener('keyup', e => {
      if (e.code === 'KeyE' || e.code === 'Tab') {
        document.querySelector('canvas')?.classList.remove('bc-inv-active');
      }
    }, { passive: true });
  }

  function applyFPSOptimizations() {
    const style = document.createElement('style');
    style.id = 'bc-fps-opts';
    style.textContent = `
      canvas {
        /* NOTE: will-change: transform is REMOVED — it creates a redundant
           compositor layer for WebGL canvases and hurts, not helps, performance */
        image-rendering: pixelated;
      }
      #bc-widgets {
        contain: strict;
      }
      #bc-widgets > div {
        contain: layout style paint;
        backface-visibility: hidden;
        /* backdrop-filter REMOVED from widgets — GPU-expensive and multiplies
           per visible widget. Using solid semi-transparent bg instead. */
      }
    `;
    document.head.appendChild(style);
  }

  const widgetContainer = document.createElement('div');
  widgetContainer.id = 'bc-widgets';
  widgetContainer.style.cssText = `
    position: fixed; top: 0; left: 0;
    width: 100vw; height: 100vh;
    pointer-events: none; z-index: 9998;
    font-family: 'Inter', 'Segoe UI', sans-serif;
  `;

  const widgetRegistry = {};

  const WIDGET_DEFAULTS = {
    fps:         { x: 10, y: 10 },
    ping:        { x: 10, y: 48 },
    cps:         { x: 10, y: 86 },
    combo:       { x: 10, y: 124 },
    keystrokes:  { x: 10, y: 200 },
    armorStatus: { x: 10, y: 340 },
    hitbox:      { x: 10, y: 390 },
  };

  function createWidget(id, renderFn) {
    const el = document.createElement('div');
    el.id = `bc-w-${id}`;
    el.style.cssText = `
      position: absolute;
      background: rgba(0, 0, 0, 0.68);
      color: #ffffff;
      padding: 5px 10px;
      border-radius: 6px;
      font-size: 12px;
      font-weight: 600;
      min-width: 76px;
      pointer-events: auto;
      user-select: none;
      border: 1px solid rgba(255,255,255,0.09);
      display: none;
      line-height: 1.5;
    `;

    const pos = state.widgetPositions[id] || WIDGET_DEFAULTS[id] || { x: 10, y: 10 };
    el.style.left = pos.x + 'px';
    el.style.top = pos.y + 'px';

    widgetContainer.appendChild(el);
    widgetRegistry[id] = { el, renderFn };

    let dragging = false, ox = 0, oy = 0;
    el.addEventListener('mousedown', e => {
      if (!state.editMode) return;
      dragging = true;
      ox = e.clientX - el.offsetLeft;
      oy = e.clientY - el.offsetTop;
      el.style.cursor = 'grabbing';
      e.stopPropagation();
    });
    document.addEventListener('mousemove', e => {
      if (!dragging) return;
      const nx = Math.max(0, Math.min(window.innerWidth - el.offsetWidth, e.clientX - ox));
      const ny = Math.max(0, Math.min(window.innerHeight - el.offsetHeight, e.clientY - oy));
      el.style.left = nx + 'px';
      el.style.top = ny + 'px';
      state.widgetPositions[id] = { x: nx, y: ny };
    });
    document.addEventListener('mouseup', () => {
      if (dragging) { dragging = false; el.style.cursor = ''; saveState(); }
    });

    return el;
  }

  function buildWidgets() {
    createWidget('fps', () => `FPS: ${metrics.fps}`);
    createWidget('ping', () => `Ping: ${metrics.ping < 0 ? '???ms' : metrics.ping + 'ms'}`);
    createWidget('cps', () => `CPS: ${metrics.cps}`);
    createWidget('combo', () => `Combo: ${metrics.combo}`);
    createWidget('armorStatus', () => `🛡 Armor HUD<br><span style="font-size:10px;opacity:0.55">Bloxd API N/A</span>`);
    createWidget('hitbox', () => `📦 Hitbox<br><span style="font-size:10px;opacity:0.55">Visual only</span>`);

    // Keystroke widget
    createWidget('keystrokes', () => {
      const s = (k) => `background:${k ? 'rgba(255,255,255,0.28)' : 'rgba(0,0,0,0.4)'};`;
      return `
        <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:3px;text-align:center;font-size:11px;">
          <div></div>
          <div style="padding:3px 4px;border-radius:3px;border:1px solid rgba(255,255,255,0.12);${s(keys.W)}">W</div>
          <div></div>
          <div style="padding:3px 4px;border-radius:3px;border:1px solid rgba(255,255,255,0.12);${s(keys.A)}">A</div>
          <div style="padding:3px 4px;border-radius:3px;border:1px solid rgba(255,255,255,0.12);${s(keys.S)}">S</div>
          <div style="padding:3px 4px;border-radius:3px;border:1px solid rgba(255,255,255,0.12);${s(keys.D)}">D</div>
          <div style="padding:3px 4px;border-radius:3px;border:1px solid rgba(255,255,255,0.12);${s(keys.LMB)}">LMB</div>
          <div style="padding:3px 4px;border-radius:3px;border:1px solid rgba(255,255,255,0.12);${s(keys.SPACE)}">SPC</div>
          <div style="padding:3px 4px;border-radius:3px;border:1px solid rgba(255,255,255,0.12);${s(keys.RMB)}">RMB</div>
        </div>
      `;
    });
  }

  function refreshWidgets() {
    const WIDGET_MAP = {
      fps:         'fps',
      ping:        'ping',
      cps:         'cps',
      combo:       'combo',
      keystrokes:  'keystrokes',
      armorStatus: 'armorStatus',
      hitbox:      'hitbox',
    };

    for (const [wid, sid] of Object.entries(WIDGET_MAP)) {
      const entry = widgetRegistry[wid];
      if (!entry) continue;
      const on = !!state.widgets[sid];
      entry.el.style.display = on ? 'block' : 'none';
      if (on) entry.el.innerHTML = entry.renderFn();
      // Edit mode highlight
      entry.el.style.boxShadow = state.editMode && on ? '0 0 0 1.5px rgba(255,255,255,0.35)' : '';
      entry.el.style.cursor = state.editMode ? 'grab' : '';
      entry.el.style.pointerEvents = state.editMode ? 'auto' : 'none';
    }
  }

  const capeManager = {
    el:          null,
    capeDiv:     null,
    logoDiv:     null,
    // Spring physics state
    _angle:      0, // current rotation angle (degrees)
    _velocity:   0, // angular velocity
    _target:     0, // rest angle
    _rafId:      null,
    _lastTime:   0,

    init() {
      const style = document.createElement('style');
      style.textContent = `
        #bc-cape-wrap {
          position: fixed;
          /* Center-bottom of screen — where a player's back would be
             if you were looking at them from a slight top-down angle */
          bottom: 52px;
          left: 50%;
          transform: translateX(-50%);
          width: 40px;
          height: 64px;
          pointer-events: none;
          z-index: 9997;
          perspective: 120px;
          display: none;
        }
        #bc-cape-body {
          width: 100%;
          height: 100%;
          transform-origin: top center;
          transform-style: preserve-3d;
          border-radius: 0 0 4px 4px;
          position: relative;
          box-shadow: inset -3px 0 6px rgba(0,0,0,0.35),
                      inset  3px 0 6px rgba(0,0,0,0.20),
                      inset  0 -4px 8px rgba(0,0,0,0.30);
        }
        #bc-cape-logo {
          position: absolute;
          top: 50%; left: 50%;
          transform: translate(-50%, -50%);
          font-size: 9px;
          font-weight: 900;
          color: rgba(255,255,255,0.45);
          letter-spacing: 0.5px;
          user-select: none;
          text-shadow: 0 1px 2px rgba(0,0,0,0.5);
        }
        /* Top attachment bar — simulates where cape attaches to shoulders */
        #bc-cape-wrap::before {
          content: '';
          position: absolute;
          top: -3px;
          left: -4px; right: -4px;
          height: 5px;
          background: rgba(0,0,0,0.5);
          border-radius: 3px 3px 0 0;
          z-index: 1;
        }
      `;
      document.head.appendChild(style);

      this.el = document.createElement('div');
      this.el.id = 'bc-cape-wrap';

      this.capeDiv = document.createElement('div');
      this.capeDiv.id = 'bc-cape-body';

      this.logoDiv = document.createElement('div');
      this.logoDiv.id = 'bc-cape-logo';
      this.logoDiv.textContent = 'BLOXD';

      this.capeDiv.appendChild(this.logoDiv);
      this.el.appendChild(this.capeDiv);
      document.body.appendChild(this.el);

      this._startPhysics();
      this.update();
    },

    _startPhysics() {
      const SPRING = 0.06;
      const DAMPING = 0.88;
      const SWAY_AMP = 5;
      const SWAY_SPD = 0.0012;

      const tick = (time) => {
        if (!state.capes.enabled) {
          this._rafId = requestAnimationFrame(tick);
          return;
        }
        this._target = Math.sin(time * SWAY_SPD) * SWAY_AMP;

        const force = (this._target - this._angle) * SPRING;
        this._velocity = (this._velocity + force) * DAMPING;
        this._angle += this._velocity;

        const rx = this._angle * 0.6;
        const ry = this._angle * 0.4;
        if (this.capeDiv) {
          this.capeDiv.style.transform =
            `rotateX(${8 + rx}deg) rotateY(${ry}deg)`;
        }

        this._rafId = requestAnimationFrame(tick);
      };
      this._rafId = requestAnimationFrame(tick);
    },

    update() {
      if (!this.el || !this.capeDiv) return;
      this.el.style.display = state.capes.enabled ? 'block' : 'none';
      this.capeDiv.style.background = state.capes.color;
      this.logoDiv.style.color = 'rgba(255,255,255,0.40)';
    },
  };

  let menuEl = null;
  let menuOpen = false;

  function row(id, label, extraHTML = '') {
    const on = (id === 'capeEnabled') ? state.capes.enabled : !!state.widgets[id];
    return `
      <div class="bc-toggle-row" data-tid="${id}" style="
        display:flex; align-items:center; justify-content:space-between;
        padding:8px 12px; border-radius:7px; margin-bottom:5px;
        background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.07);
        cursor:pointer; transition:background 0.13s;
      ">
        <span style="font-size:12px;font-weight:500;flex:1;">${label}</span>
        ${extraHTML}
        <span id="bc-badge-${id}" style="
          font-size:11px; font-weight:700;
          color:${on ? '#5aff5a' : '#ff5a5a'};
          min-width:28px; text-align:right;
        ">${on ? 'ON' : 'OFF'}</span>
      </div>
    `;
  }

  function sectionLabel(text) {
    return `<div style="
      font-size:10px; letter-spacing:1px; text-transform:uppercase;
      opacity:0.4; margin:14px 0 7px; font-weight:700;
    ">${text}</div>`;
  }

  function infoBox(text, color = '#5ab4ff') {
    return `<div style="
      background:${color}14; border:1px solid ${color}30;
      border-radius:8px; padding:9px 12px; font-size:11px;
      opacity:0.85; line-height:1.6; margin-bottom:10px;
    ">${text}</div>`;
  }

  function tabCombat() {
    const bo = state.blockOverlay;
    return `
      ${sectionLabel('Combat Modules')}
      ${row('autoSprint', '⚡ Auto Sprint')}
      ${row('keystrokes', '⌨️ Keystrokes')}
      ${row('cps', '🖱️ CPS Counter')}
      ${row('ping', '📡 Ping')}
      ${row('fps', '📈 FPS')}
      ${row('combo', '🔥 Combo Counter')}
      ${row('zoom', '🔍 Smooth Zoom (Hold C)')}
      ${row('hitbox', '📦 Hitbox Overlay')}
      ${row('inventoryBlur', '🎒 Inventory Blur')}
      ${row('armorStatus', '🛡️ Armor Status')}

      ${sectionLabel('Block Outline')}
      ${row('blockOverlay', '🟦 Block Outline Overlay')}
      <div style="
        padding:12px; border-radius:8px;
        background:rgba(255,255,255,0.03);
        border:1px solid rgba(255,255,255,0.07);
        margin-top:4px;
      ">
        <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;">
          <span style="font-size:11px;">Outline Color</span>
          <input type="color" id="bc-oc" value="${bo.outlineColor}"
            style="width:34px;height:22px;border:none;border-radius:4px;cursor:pointer;padding:0;">
        </div>
        <div style="display:flex;align-items:center;gap:10px;margin-bottom:8px;">
          <span style="font-size:11px;flex:1;">Thickness: <span id="bc-ot-label">${bo.outlineThickness}px</span></span>
          <input type="range" id="bc-ot" min="1" max="8" value="${bo.outlineThickness}" style="width:100px;">
        </div>
        <div style="display:flex;align-items:center;justify-content:space-between;">
          <span style="font-size:11px;">Fill Color</span>
          <input type="color" id="bc-fc" value="${bo.fillColor}"
            style="width:34px;height:22px;border:none;border-radius:4px;cursor:pointer;padding:0;">
        </div>
      </div>
    `;
  }

  function tabFPS() {
    return `
      ${sectionLabel('Auto-Applied Optimizations')}
      <div style="
        background:rgba(255,255,255,0.04);border-radius:8px;
        border:1px solid rgba(255,255,255,0.07);padding:12px;margin-bottom:10px;
        font-size:11px;opacity:0.75;line-height:1.8;
      ">
        ✅ <b style="opacity:1">will-change removed</b> from canvas (WebGL doesn't need it)<br>
        ✅ <b style="opacity:1">backdrop-filter removed</b> from all HUD widgets<br>
        ✅ <b style="opacity:1">CSS contain: strict</b> on widget overlays<br>
        ✅ <b style="opacity:1">Passive event listeners</b> where possible<br>
        ✅ <b style="opacity:1">Ping check delayed 8s</b> — no network hit at startup<br>
        ✅ <b style="opacity:1">Backface culling</b> on overlay elements
      </div>
      ${infoBox('ℹ️ FPS gains are browser-side only. Bloxd\'s engine rendering is not altered. Expect modest, real improvements — not fabricated numbers.', '#5ab4ff')}
      ${sectionLabel('Live Stats')}
      <div style="
        background:rgba(255,255,255,0.04);border-radius:8px;
        border:1px solid rgba(255,255,255,0.07);padding:12px;
      ">
        <div style="font-size:14px;font-weight:600;margin-bottom:4px;">
          📈 FPS: <span id="bc-lfps" style="color:#5aff5a;">${metrics.fps}</span>
        </div>
        <div style="font-size:14px;font-weight:600;">
          📡 Ping: <span id="bc-lping" style="color:#5ab4ff;">${metrics.ping < 0 ? '???ms' : metrics.ping + 'ms'}</span>
        </div>
      </div>
    `;
  }

  function tabShaders() {
    const t = T();
    return `
      ${sectionLabel('GPU Color-Grading Shaders')}
      ${infoBox('⚡ Uses SVG <b>feColorMatrix</b> + <b>feComponentTransfer</b> — real GPU-executed per-pixel color matrices applied to the game canvas. Equivalent to GLSL color-grading fragment shaders.', '#b36bff')}
      ${Object.entries(SHADER_CONFIGS).map(([key, cfg]) => {
        const active = state.shaderMode === key;
        return `
          <div class="bc-shader-row" data-skey="${key}" style="
            display:flex; align-items:center; justify-content:space-between;
            padding:11px 14px; border-radius:8px; margin-bottom:7px;
            background:${active ? 'rgba(255,255,255,0.09)' : 'rgba(255,255,255,0.04)'};
            border:1px solid ${active ? t.accent : 'rgba(255,255,255,0.07)'};
            cursor:pointer; transition:all 0.15s;
          ">
            <div>
              <div style="font-size:13px;font-weight:600;">${cfg.label}</div>
              <div style="font-size:10px;opacity:0.45;margin-top:2px;">${cfg.desc}</div>
            </div>
            <span style="font-size:11px;font-weight:700;color:${active ? '#5aff5a' : 'rgba(255,255,255,0.3)'}">
              ${active ? 'ACTIVE' : 'APPLY'}
            </span>
          </div>
        `;
      }).join('')}
      <div class="bc-shader-row" data-skey="none" style="
        display:flex;align-items:center;justify-content:space-between;
        padding:9px 14px;border-radius:8px;
        background:rgba(255,80,80,0.06);border:1px solid rgba(255,80,80,0.2);
        cursor:pointer;
      ">
        <span style="font-size:12px;">❌ Remove Shader</span>
        <span style="font-size:10px;opacity:0.45;">Reset all filters</span>
      </div>
    `;
  }

  function tabEdit() {
    const t = T();
    return `
      ${sectionLabel('Widget Layout Editor')}
      <p style="font-size:11px;opacity:0.6;margin:0 0 14px;">
        Toggle Edit Mode to freely drag any HUD widget. Positions save automatically.
      </p>
      <div id="bc-edit-btn" style="
        padding:13px; border-radius:8px; text-align:center;
        font-size:13px; font-weight:700; cursor:pointer;
        background:${state.editMode ? 'rgba(90,255,90,0.13)' : 'rgba(90,180,255,0.12)'};
        border:1px solid ${state.editMode ? 'rgba(90,255,90,0.35)' : 'rgba(90,180,255,0.28)'};
        color:${state.editMode ? '#5aff5a' : t.accent};
        transition:all 0.2s;
        user-select:none;
      ">
        ${state.editMode ? '🔒 STOP EDIT — Lock Positions' : '✏️ START EDIT — Drag Widgets'}
      </div>
      <div id="bc-reset-pos" style="
        margin-top:10px; padding:9px; border-radius:7px; text-align:center;
        font-size:11px; cursor:pointer;
        background:rgba(255,80,80,0.08); border:1px solid rgba(255,80,80,0.2);
        color:#ff9090; user-select:none;
      ">🔄 Reset Widget Positions</div>
      <div style="
        margin-top:10px; padding:9px 12px; border-radius:7px;
        background:rgba(255,255,255,0.03); border:1px solid rgba(255,255,255,0.06);
        font-size:10px; opacity:0.5;
      ">Positions stored in localStorage — persist across sessions.</div>
    `;
  }

  function tabCapes() {
    return `
      ${sectionLabel('Cape Cosmetics')}
      ${infoBox('🎭 Cape renders as a DOM overlay centered at screen-bottom with 3D perspective + spring physics. Visible as a cosmetic HUD element. Full in-game 3D requires native Bloxd support.', '#ffcc44')}
      ${row('capeEnabled', '🦸 Enable Cape')}
      <div style="margin-top:10px;padding:12px;background:rgba(255,255,255,0.04);border-radius:8px;border:1px solid rgba(255,255,255,0.07);">
        <div style="font-size:11px;margin-bottom:8px;opacity:0.7;">Cape Color</div>
        <input type="color" id="bc-ccolor" value="${state.capes.color}"
          style="width:100%;height:30px;border:none;border-radius:6px;cursor:pointer;background:transparent;">
      </div>

      ${sectionLabel('Live Preview')}
      <div style="padding:24px 20px;background:rgba(255,255,255,0.03);border-radius:8px;border:1px solid rgba(255,255,255,0.06);text-align:center;">
        <div style="display:inline-block;position:relative;perspective:120px;">
          <!-- Head -->
          <div style="width:20px;height:20px;background:#c8a97a;border-radius:3px;margin:0 auto 1px;box-shadow:inset -2px -2px 4px rgba(0,0,0,0.3);"></div>
          <!-- Body + Cape (cape sits BEHIND body, z-index lower) -->
          <div style="position:relative;width:26px;height:34px;margin:0 auto;">
            <!-- Cape: behind body, hanging from top, 3D rotated -->
            <div id="bc-cape-prv" style="
              position:absolute; top:0; left:-5px;
              width:36px; height:38px;
              background:${state.capes.color};
              border-radius:0 0 5px 5px;
              transform-origin:top center;
              transform:rotateX(12deg) rotateY(3deg);
              box-shadow:inset -3px 0 6px rgba(0,0,0,0.35),
                         inset  3px 0 6px rgba(0,0,0,0.20);
              z-index:0;
              display:${state.capes.enabled ? 'block' : 'none'};
            "></div>
            <!-- Body: in front of cape -->
            <div style="
              position:relative;
              width:26px;height:34px;
              background:#4a7abf;border-radius:2px;
              z-index:1;
              box-shadow:inset -2px 0 4px rgba(0,0,0,0.3);
            "></div>
          </div>
          <!-- Legs -->
          <div style="display:flex;gap:2px;width:26px;margin:0 auto;">
            <div style="flex:1;height:16px;background:#3a6aaf;border-radius:0 0 2px 2px;"></div>
            <div style="flex:1;height:16px;background:#3a6aaf;border-radius:0 0 2px 2px;"></div>
          </div>
        </div>
        <div style="font-size:9px;opacity:0.35;margin-top:10px;">Cape hangs from player back · Spring physics</div>
      </div>
    `;
  }

  function buildMenu() {
    if (menuEl) menuEl.remove();
    const t = T();

    menuEl = document.createElement('div');
    menuEl.id = 'bc-menu';
    menuEl.style.cssText = `
      position: fixed;
      top: 50%; left: 50%;
      transform: translate(-50%, -50%);
      width: 500px; max-height: 82vh;
      background: ${t.bg};
      backdrop-filter: blur(18px);
      -webkit-backdrop-filter: blur(18px);
      border: 1px solid ${t.border};
      border-radius: 14px;
      z-index: 99999;
      color: #fff;
      font-family: 'Inter', 'Segoe UI', sans-serif;
      box-shadow: 0 10px 50px rgba(0,0,0,0.75), 0 0 0 1px rgba(255,255,255,0.04);
      display: flex; flex-direction: column;
      overflow: hidden;
    `;

    menuEl.innerHTML = `
      <!-- HEADER (draggable) -->
      <div id="bc-mheader" style="
        display:flex; align-items:center; gap:10px;
        padding:13px 16px;
        background:rgba(0,0,0,0.28);
        border-bottom:1px solid ${t.border};
        cursor:move; user-select:none; flex-shrink:0;
      ">
        <img src="https://i.postimg.cc/x8QH5nD3/bloxd.jpg"
          style="width:30px;height:30px;border-radius:6px;object-fit:cover;"
          onerror="this.style.display='none'" />
        <div>
          <div style="font-size:15px;font-weight:700;letter-spacing:0.4px;">${CLIENT_NAME}</div>
          <div style="font-size:9px;opacity:0.4;margin-top:1px;">v${CLIENT_VERSION} · Bloxd's Safest Client · Currently on Work... +</div>
        </div>
        <div style="margin-left:auto;display:flex;align-items:center;gap:8px;">
          <span style="font-size:9px;opacity:0.45;">Theme:</span>
          ${['blue','brown','purple'].map(th => `
            <div data-btheme="${th}" style="
              width:13px; height:13px; border-radius:50%; cursor:pointer;
              background:${THEMES[th].accent};
              border:2px solid ${state.theme === th ? '#fff' : 'transparent'};
              transition:border 0.15s;
            "></div>
          `).join('')}
          <div id="bc-close" style="
            width:21px; height:21px; border-radius:50%;
            background:rgba(255,70,70,0.7);
            display:flex; align-items:center; justify-content:center;
            cursor:pointer; font-size:11px; margin-left:4px;
            transition:background 0.15s;
          ">✕</div>
        </div>
      </div>

      <!-- TABS -->
      <div id="bc-tabs" style="
        display:flex;
        background:${t.tabBg};
        border-bottom:1px solid ${t.border};
        flex-shrink:0;
      ">
        ${['combat','fps','shaders','edit','capes'].map(tab => `
          <div data-btab="${tab}" style="
            flex:1; text-align:center; padding:9px 0;
            font-size:10px; font-weight:700; text-transform:uppercase;
            letter-spacing:0.8px; cursor:pointer;
            color:${state.activeTab === tab ? t.accent : 'rgba(255,255,255,0.42)'};
            border-bottom:2px solid ${state.activeTab === tab ? t.accent : 'transparent'};
            transition:all 0.15s;
          ">${tab}</div>
        `).join('')}
      </div>

      <!-- CONTENT AREA -->
      <div id="bc-mcontent" style="
        flex:1; overflow-y:auto; padding:14px 16px;
        scrollbar-width:thin;
        scrollbar-color:rgba(255,255,255,0.15) transparent;
      ">
        ${getTabContent(state.activeTab)}
      </div>
    `;

    document.body.appendChild(menuEl);
    bindMenuEvents();
    makeDraggable(menuEl, document.getElementById('bc-mheader'));
  }

  function getTabContent(tab) {
    switch (tab) {
      case 'combat': return tabCombat();
      case 'fps': return tabFPS();
      case 'shaders': return tabShaders();
      case 'edit': return tabEdit();
      case 'capes': return tabCapes();
      default: return '';
    }
  }

  function refreshContent() {
    const cont = document.getElementById('bc-mcontent');
    if (cont) {
      cont.innerHTML = getTabContent(state.activeTab);
      bindContentEvents();
    }
    const t = T();
    document.querySelectorAll('[data-btab]').forEach(el => {
      const active = el.dataset.btab === state.activeTab;
      el.style.color = active ? t.accent : 'rgba(255,255,255,0.42)';
      el.style.borderBottom = `2px solid ${active ? t.accent : 'transparent'}`;
    });
  }

  function bindMenuEvents() {
    document.getElementById('bc-close')?.addEventListener('click', closeMenu);

    document.querySelectorAll('[data-btab]').forEach(el => {
      el.addEventListener('click', () => {
        state.activeTab = el.dataset.btab;
        refreshContent();
      });
    });

    document.querySelectorAll('[data-btheme]').forEach(el => {
      el.addEventListener('click', () => {
        state.theme = el.dataset.btheme;
        saveState();
        buildMenu();
      });
    });

    bindContentEvents();
  }

  function bindContentEvents() {
    document.querySelectorAll('.bc-toggle-row').forEach(el => {
      el.addEventListener('click', () => {
        const id = el.dataset.tid;
        let on;
        if (id === 'capeEnabled') {
          state.capes.enabled = !state.capes.enabled;
          on = state.capes.enabled;
          capeManager.update();
          const prv = document.getElementById('bc-cape-prv');
          if (prv) prv.style.display = on ? 'block' : 'none';
        } else {
          state.widgets[id] = !state.widgets[id];
          on = state.widgets[id];
          refreshWidgets();
        }
        const badge = document.getElementById(`bc-badge-${id}`);
        if (badge) {
          badge.textContent = on ? 'ON' : 'OFF';
          badge.style.color = on ? '#5aff5a' : '#ff5a5a';
        }
        saveState();
      });
    });

    document.querySelectorAll('.bc-shader-row').forEach(el => {
      el.addEventListener('click', () => {
        const k = el.dataset.skey === 'none' ? null : el.dataset.skey;
        shaderManager.apply(k);
        refreshContent();
      });
    });

    document.getElementById('bc-edit-btn')?.addEventListener('click', () => {
      state.editMode = !state.editMode;
      refreshWidgets();
      refreshContent();
    });

    document.getElementById('bc-reset-pos')?.addEventListener('click', () => {
      state.widgetPositions = {};
      saveState();
      Object.entries(widgetRegistry).forEach(([id, { el }]) => {
        const p = WIDGET_DEFAULTS[id] || { x: 10, y: 10 };
        el.style.left = p.x + 'px';
        el.style.top = p.y + 'px';
      });
    });

    document.getElementById('bc-oc')?.addEventListener('input', e => {
      state.blockOverlay.outlineColor = e.target.value; saveState();
    });
    document.getElementById('bc-ot')?.addEventListener('input', e => {
      state.blockOverlay.outlineThickness = +e.target.value;
      const lbl = document.getElementById('bc-ot-label');
      if (lbl) lbl.textContent = e.target.value + 'px';
      saveState();
    });
    document.getElementById('bc-fc')?.addEventListener('input', e => {
      state.blockOverlay.fillColor = e.target.value; saveState();
    });

    document.getElementById('bc-ccolor')?.addEventListener('input', e => {
      state.capes.color = e.target.value;
      capeManager.update();
      const prv = document.getElementById('bc-cape-prv');
      if (prv) prv.style.background = e.target.value;
      saveState();
    });

    if (state.activeTab === 'fps') {
      const fpsInterval = setInterval(() => {
        if (!document.getElementById('bc-lfps')) { clearInterval(fpsInterval); return; }
        const f = document.getElementById('bc-lfps');
        const p = document.getElementById('bc-lping');
        if (f) f.textContent = metrics.fps;
        if (p) p.textContent = metrics.ping < 0 ? '???ms' : metrics.ping + 'ms';
      }, 500);
    }
  }

  function makeDraggable(el, handle) {
    let drag = false, ox = 0, oy = 0;
    handle.addEventListener('mousedown', e => {
      drag = true;
      const rect = el.getBoundingClientRect();
      el.style.transform = 'none';
      el.style.top = rect.top + 'px';
      el.style.left = rect.left + 'px';
      ox = e.clientX - rect.left;
      oy = e.clientY - rect.top;
      e.preventDefault();
    });
    document.addEventListener('mousemove', e => {
      if (!drag) return;
      el.style.left = Math.max(0, Math.min(window.innerWidth - el.offsetWidth, e.clientX - ox)) + 'px';
      el.style.top = Math.max(0, Math.min(window.innerHeight - el.offsetHeight, e.clientY - oy)) + 'px';
    });
    document.addEventListener('mouseup', () => { drag = false; });
  }

  function closeMenu() {
    if (menuEl) menuEl.remove();
    menuEl = null;
    menuOpen = false;
    state.menuOpen = false;
  }

  function openMenu() {
    menuOpen = true;
    state.menuOpen = true;
    buildMenu();
  }

  document.addEventListener('keydown', e => {
    if (e.code === 'ShiftRight') {
      e.preventDefault();
      menuOpen ? closeMenu() : openMenu();
    }
  });

  function injectGlobalStyles() {
    const style = document.createElement('style');
    style.id = 'bc-global-styles';
    style.textContent = `
      /* Scrollbar style for menu */
      #bc-mcontent::-webkit-scrollbar { width: 4px; }
      #bc-mcontent::-webkit-scrollbar-track { background: transparent; }
      #bc-mcontent::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.15); border-radius: 4px; }

      /* Hover for toggle rows */
      .bc-toggle-row:hover { background: rgba(255,255,255,0.08) !important; }

      /* Shader row hover */
      .bc-shader-row:hover { background: rgba(255,255,255,0.08) !important; }
    `;
    document.head.appendChild(style);
  }

  function init() {
    loadState();

    tabTitleManager.init();
    trackerBlocker.init();
    metrics.init();
    initKeystrokes();
    initAutoSprint();

    const domReady = () => {
      injectGlobalStyles();
      applyFPSOptimizations();
      applyInventoryBlur();

      document.body.appendChild(widgetContainer);
      buildWidgets();
      refreshWidgets();

      capeManager.init();
      shaderManager.init();
      initZoom();

      setInterval(refreshWidgets, 100);
    };

    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', domReady);
    } else {
      domReady();
    }
  }

  init();

  console.log(
    `%c ${CLIENT_NAME} v${CLIENT_VERSION} %c Loaded! Press Right Shift to open menu. `,
    'background:#5ab4ff;color:#000;font-weight:bold;padding:2px 6px;border-radius:4px 0 0 4px;',
    'background:#1a2a40;color:#5ab4ff;padding:2px 6px;border-radius:0 4px 4px 0;'
  );

})();