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!

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==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;'
  );

})();