Clean, lightweight Bloxd.io client with Combat, Visuals, Shader Post-Processing, and FPS Boost. This Client is Currently in Progress!
// ==UserScript==
// @name Bloxd Legacy
// @namespace bloxd.io
// @version 1.0
// @description Clean, lightweight Bloxd.io client with Combat, Visuals, Shader Post-Processing, and FPS Boost. This Client is Currently in Progress!
// @author UnknownDev
// @match https://bloxd.io/*
// @match https://staging.bloxd.io/*
// @match https://bloxdforge.com/studio/play/*
// @license MIT
// @grant none
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
if (window.__bloxdLegacyV1) return;
window.__bloxdLegacyV1 = true;
const CONFIG_KEY = 'bloxd_legacy_v1_config';
const DEFAULT_CONFIG = {
combat: {
hitboxes: false,
toggleSprint: false,
noHurtCam: false,
smallItems: false,
smallItemsScale: 0.75
},
cosmetics: {
cape: 'none',
customUrl: ''
},
capes: {
selected: 'none'
},
visuals: {
keystrokes: true,
cps: true,
fps: true,
motionBlur: false,
crosshair: 'default',
smoothZoom: false,
smoothZoomLevel: 1.0,
fov: 90,
guiOpacity: 0.80
},
shaders: {
preset: 'balanced',
brightness: 1.05,
contrast: 1.05,
saturate: 1.1,
sepia: 0,
hueRotate: 0,
bloom: 0.3,
fog: 0.1,
shadows: 0.7,
vignette: 0.2,
grain: false,
colorTemp: 2,
fpsBoost: false,
resScale: 0.75
},
uiLayout: {
keystrokes: { x: 0, y: 0, scale: 1, opacity: 1, color: '#3b82f6' },
fps: { x: 0, y: 0, scale: 1, opacity: 1, color: '#3b82f6' },
cps: { x: 0, y: 0, scale: 1, opacity: 1, color: '#3b82f6' },
sprint: { x: 0, y: 0, scale: 1, opacity: 1, color: '#3b82f6' },
hud: { x: 0, y: 0, scale: 1, opacity: 1, color: '#22c55e' },
crosshair: { x: 0, y: 0, scale: 1, opacity: 0.9, color: '#ffffff' }
},
settings: {
uiScale: 1.0,
menuKey: 'ShiftRight'
}
};
const SHADER_PRESETS = {
performance: {
brightness: 1.0, contrast: 1.0, saturate: 0.9,
sepia: 0, hueRotate: 0, bloom: 0, fog: 0,
shadows: 0.5, vignette: 0, grain: false,
colorTemp: 0, fpsBoost: true, resScale: 0.75
},
balanced: {
brightness: 1.05, contrast: 1.05, saturate: 1.1,
sepia: 0, hueRotate: 0, bloom: 0.3, fog: 0.1,
shadows: 0.7, vignette: 0.2, grain: false,
colorTemp: 2, fpsBoost: false, resScale: 1.0
},
cinematic: {
brightness: 1.1, contrast: 1.08, saturate: 1.15,
sepia: 0.05, hueRotate: 3, bloom: 0.6, fog: 0.3,
shadows: 0.85, vignette: 0.5, grain: true,
colorTemp: 5, fpsBoost: false, resScale: 1.0
}
};
const UI_DEFS = {
keystrokes: { name: 'Keystrokes', selector: '#bl-keystrokes', anchor: 'bl', defaultColor: '#3b82f6' },
fps: { name: 'FPS Counter', selector: '#bl-fps-counter', anchor: 'tl', defaultColor: '#3b82f6' },
cps: { name: 'CPS Counter', selector: '#bl-cps-counter', anchor: 'tl', defaultColor: '#3b82f6' },
sprint: { name: 'Sprint Indicator', selector: '#bl-sprint-indicator', anchor: 'bc', defaultColor: '#3b82f6' },
hud: { name: 'Legacy HUD', selector: '#bloxd-legacy-hud', anchor: 'tr', defaultColor: '#22c55e' },
crosshair: { name: 'Crosshair', selector: '#bl-crosshair', anchor: 'cc', defaultColor: '#ffffff' }
};
const ANCHORS = {
bl: (w, h) => ({ x: 20, y: window.innerHeight - 20 - h }),
tl: (w, h, id) => ({ x: 14, y: id === 'cps' ? 72 : 14 }),
tr: (w, h) => ({ x: window.innerWidth - 14 - w, y: 14 }),
bc: (w, h) => ({ x: (window.innerWidth - w) / 2, y: window.innerHeight - 100 - h }),
cc: (w, h) => ({ x: (window.innerWidth - w) / 2, y: (window.innerHeight - h) / 2 })
};
function deepMerge(target, source) {
const out = JSON.parse(JSON.stringify(target));
for (const k in source) {
if (source[k] && typeof source[k] === 'object' && !Array.isArray(source[k]) && k !== 'customUrl') {
out[k] = deepMerge(out[k] || {}, source[k]);
} else {
out[k] = source[k];
}
}
return out;
}
function loadConfig() {
try {
const raw = localStorage.getItem(CONFIG_KEY);
if (raw) return deepMerge(DEFAULT_CONFIG, JSON.parse(raw));
} catch (e) { console.warn('[BloxdLegacy] Config load failed:', e); }
return JSON.parse(JSON.stringify(DEFAULT_CONFIG));
}
function saveConfig() {
try { localStorage.setItem(CONFIG_KEY, JSON.stringify(config)); } catch (e) {}
}
let config = loadConfig();
let menuOpen = false;
let currentTab = 'combat';
let manualShift = false;
let autoShift = false;
let selectedUIElement = null;
let isDraggingUI = false;
let dragStartX = 0, dragStartY = 0, dragElStartX = 0, dragElStartY = 0;
const trackedListeners = [];
const trackedIntervals = [];
const trackedObservers = [];
const trackedRAFs = [];
function on(el, evt, fn, opts) {
el.addEventListener(evt, fn, opts);
trackedListeners.push({ el, evt, fn, opts });
}
function off(el, evt, fn, opts) {
el.removeEventListener(evt, fn, opts);
const idx = trackedListeners.findIndex(l => l.el === el && l.evt === evt && l.fn === fn);
if (idx >= 0) trackedListeners.splice(idx, 1);
}
function $1(sel, ctx) { return (ctx || document).querySelector(sel); }
function $all(sel, ctx) { return (ctx || document).querySelectorAll(sel); }
function make(tag, cls, parent) {
const el = document.createElement(tag);
if (cls) el.className = cls;
if (parent) parent.appendChild(el);
return el;
}
const CSS = `
:root {
--bl-bg: rgba(15, 15, 25, 0.92);
--bl-accent: #3b82f6;
--bl-accent-hov: #2563eb;
--bl-text: #e2e8f0;
--bl-dim: #94a3b8;
--bl-border: rgba(255,255,255,0.08);
--bl-sidebar: rgba(10, 10, 18, 0.95);
--bl-panel: rgba(25, 25, 35, 0.6);
--bl-scale: 1.0;
--bl-gui-opacity: 0.92;
--bl-gui-custom-opacity: 0.80;
--bl-item-scale: 0.75;
}
#bloxd-legacy-overlay {
position: fixed;
inset: 0;
z-index: 2147483647;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
pointer-events: none;
transition: opacity 0.25s ease;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
#bloxd-legacy-overlay.active {
opacity: 1;
pointer-events: auto;
}
#bloxd-legacy-menu {
width: 860px;
height: 620px;
max-width: 92vw;
max-height: 88vh;
background: var(--bl-bg);
border: 1px solid var(--bl-border);
border-radius: 14px;
display: flex;
flex-direction: column;
overflow: hidden;
box-shadow: 0 25px 50px -12px rgba(0,0,0,0.55);
backdrop-filter: blur(16px) saturate(1.2);
transform: scale(calc(0.96 * var(--bl-scale)));
transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
#bloxd-legacy-overlay.active #bloxd-legacy-menu {
transform: scale(var(--bl-scale));
}
.bl-header {
height: 58px;
background: var(--bl-sidebar);
border-bottom: 1px solid var(--bl-border);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 26px;
flex-shrink: 0;
}
.bl-title {
font-size: 18px;
font-weight: 800;
color: var(--bl-text);
letter-spacing: 1px;
text-transform: uppercase;
user-select: none;
}
.bl-close {
background: none;
border: none;
color: var(--bl-dim);
font-size: 26px;
line-height: 1;
cursor: pointer;
width: 34px;
height: 34px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
transition: all 0.15s ease;
}
.bl-close:hover {
background: rgba(255,255,255,0.08);
color: var(--bl-text);
}
.bl-body {
display: flex;
flex: 1;
overflow: hidden;
}
.bl-sidebar {
width: 210px;
background: var(--bl-sidebar);
border-right: 1px solid var(--bl-border);
padding: 14px;
display: flex;
flex-direction: column;
gap: 6px;
overflow-y: auto;
flex-shrink: 0;
}
.bl-tab {
background: transparent;
border: none;
color: var(--bl-dim);
padding: 12px 16px;
border-radius: 10px;
text-align: left;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.15s ease;
position: relative;
display: flex;
align-items: center;
gap: 12px;
user-select: none;
}
.bl-tab:hover {
background: rgba(255,255,255,0.04);
color: var(--bl-text);
}
.bl-tab.active {
background: rgba(59, 130, 246, 0.12);
color: var(--bl-accent);
}
.bl-tab.active::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 18px;
background: var(--bl-accent);
border-radius: 0 3px 3px 0;
}
.bl-content {
flex: 1;
padding: 26px;
overflow-y: auto;
color: var(--bl-text);
}
.bl-panel {
display: none;
animation: blFadeIn 0.2s ease;
}
.bl-panel.active {
display: block;
}
@keyframes blFadeIn {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: translateY(0); }
}
.bl-section-title {
font-size: 20px;
font-weight: 700;
margin-bottom: 6px;
color: var(--bl-text);
}
.bl-section-note {
font-size: 12px;
color: var(--bl-dim);
margin-bottom: 18px;
line-height: 1.5;
}
.bl-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 0;
border-bottom: 1px solid var(--bl-border);
gap: 20px;
}
.bl-row-info {
flex: 1;
min-width: 0;
}
.bl-row-title {
font-size: 14px;
font-weight: 600;
margin-bottom: 3px;
}
.bl-row-desc {
font-size: 12px;
color: var(--bl-dim);
line-height: 1.4;
}
.bl-toggle {
appearance: none;
width: 46px;
height: 26px;
background: rgba(255,255,255,0.1);
border-radius: 13px;
position: relative;
cursor: pointer;
transition: background 0.2s ease;
flex-shrink: 0;
}
.bl-toggle::after {
content: '';
position: absolute;
top: 3px;
left: 3px;
width: 20px;
height: 20px;
background: #fff;
border-radius: 50%;
transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1);
box-shadow: 0 1px 3px rgba(0,0,0,0.3);
}
.bl-toggle:checked {
background: var(--bl-accent);
}
.bl-toggle:checked::after {
transform: translateX(20px);
}
.bl-slider-wrap {
display: flex;
align-items: center;
gap: 14px;
flex-shrink: 0;
}
.bl-slider {
-webkit-appearance: none;
width: 140px;
height: 4px;
background: rgba(255,255,255,0.1);
border-radius: 2px;
outline: none;
}
.bl-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 16px;
height: 16px;
background: var(--bl-accent);
border-radius: 50%;
cursor: pointer;
transition: transform 0.1s;
}
.bl-slider::-webkit-slider-thumb:hover {
transform: scale(1.15);
}
.bl-slider-val {
color: var(--bl-dim);
font-size: 12px;
min-width: 36px;
text-align: right;
font-variant-numeric: tabular-nums;
}
.bl-select {
background: rgba(255,255,255,0.05);
border: 1px solid var(--bl-border);
color: var(--bl-text);
padding: 8px 12px;
border-radius: 8px;
font-size: 13px;
outline: none;
cursor: pointer;
min-width: 150px;
}
.bl-select:focus {
border-color: var(--bl-accent);
}
.bl-input {
background: rgba(255,255,255,0.05);
border: 1px solid var(--bl-border);
color: var(--bl-text);
padding: 8px 12px;
border-radius: 8px;
font-size: 13px;
outline: none;
width: 220px;
}
.bl-input:focus {
border-color: var(--bl-accent);
}
.bl-btn {
background: var(--bl-accent);
color: #fff;
border: none;
padding: 9px 18px;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: background 0.15s ease;
}
.bl-btn:hover {
background: var(--bl-accent-hov);
}
.bl-btn.secondary {
background: rgba(255,255,255,0.08);
color: var(--bl-text);
}
.bl-btn.secondary:hover {
background: rgba(255,255,255,0.14);
}
.bl-btn-row {
display: flex;
gap: 10px;
padding-top: 8px;
border-bottom: none !important;
}
.bl-cape-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 14px;
margin-top: 16px;
}
.bl-cape-item {
aspect-ratio: 2 / 1;
border-radius: 8px;
border: 2px solid transparent;
cursor: pointer;
transition: all 0.15s ease;
position: relative;
overflow: hidden;
}
.bl-cape-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
}
.bl-cape-item.active {
border-color: var(--bl-accent);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.25);
}
.bl-cape-label {
position: absolute;
bottom: 5px;
left: 50%;
transform: translateX(-50%);
font-size: 11px;
font-weight: 700;
color: #fff;
text-shadow: 0 1px 3px rgba(0,0,0,0.9);
pointer-events: none;
white-space: nowrap;
}
#bloxd-legacy-hud {
position: fixed;
top: 14px;
right: 14px;
z-index: 9997;
background: rgba(15, 15, 25, 0.85);
border: 1px solid var(--bl-border);
border-radius: 10px;
padding: 10px 16px;
color: var(--bl-text);
font-size: 12px;
font-weight: 700;
letter-spacing: 0.6px;
text-transform: uppercase;
backdrop-filter: blur(10px);
pointer-events: none;
user-select: none;
display: flex;
align-items: center;
gap: 10px;
opacity: 0;
animation: blHudIn 0.6s ease 0.5s forwards;
will-change: transform, opacity;
}
@keyframes blHudIn {
to { opacity: 1; }
}
#bloxd-legacy-hud::before {
content: '';
width: 7px;
height: 7px;
background: #22c55e;
border-radius: 50%;
box-shadow: 0 0 8px #22c55e;
}
.bl-info-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 0;
border-bottom: 1px solid var(--bl-border);
}
#bl-hitbox-overlay {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 80px;
height: 160px;
border: 2px solid rgba(255, 50, 50, 0.55);
background: rgba(255, 50, 50, 0.04);
pointer-events: none;
z-index: 9998;
display: none;
border-radius: 4px;
}
#bl-hitbox-overlay.active {
display: block;
}
#bl-hitbox-overlay::before {
content: 'HITBOX';
position: absolute;
top: -18px;
left: 50%;
transform: translateX(-50%);
font-size: 9px;
font-weight: 700;
color: rgba(255, 50, 50, 0.7);
letter-spacing: 1px;
font-family: monospace;
}
.bl-feature-small-items .item,
.bl-feature-small-items .SelectedItem {
transform: scale(var(--bl-item-scale)) !important;
transform-origin: bottom center !important;
}
.bl-feature-no-hurt-cam canvas {
filter: saturate(0.75) contrast(0.95) !important;
}
.bl-player-highlight {
outline: 2px solid rgba(255, 50, 50, 0.85) !important;
outline-offset: 2px !important;
}
.bl-sprint-indicator {
position: fixed;
bottom: 100px;
left: 50%;
transform: translateX(-50%);
background: rgba(15, 15, 25, 0.85);
border: 1px solid var(--bl-border);
color: var(--bl-accent);
padding: 6px 14px;
border-radius: 20px;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.8px;
z-index: 9997;
pointer-events: none;
user-select: none;
opacity: 0;
transition: opacity 0.2s ease;
will-change: transform, opacity;
}
.bl-sprint-indicator.active {
opacity: 1;
}
.bl-keystrokes {
position: fixed;
bottom: 20px;
left: 20px;
z-index: 9997;
display: grid;
grid-template-columns: repeat(3, 42px);
grid-template-rows: repeat(2, 42px);
gap: 4px;
pointer-events: none;
user-select: none;
will-change: transform, opacity;
}
.bl-keystrokes .ks-key {
background: rgba(15, 15, 25, var(--bl-gui-opacity, 0.80));
border: 1px solid var(--bl-border);
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
font-weight: 700;
color: var(--bl-text);
transition: all 0.08s ease;
will-change: transform, background;
}
.bl-keystrokes .ks-key.pressed {
background: var(--bl-accent);
border-color: var(--bl-accent);
color: #fff;
transform: scale(0.92);
}
.bl-keystrokes .ks-key:nth-child(1) { grid-column: 2; }
.bl-keystrokes .ks-key:nth-child(2) { grid-column: 1; grid-row: 2; }
.bl-keystrokes .ks-key:nth-child(3) { grid-column: 2; grid-row: 2; }
.bl-keystrokes .ks-key:nth-child(4) { grid-column: 3; grid-row: 2; }
.bl-counter {
position: fixed;
z-index: 9997;
background: rgba(15, 15, 25, var(--bl-gui-opacity, 0.80));
border: 1px solid var(--bl-border);
border-radius: 8px;
padding: 8px 14px;
font-size: 13px;
font-weight: 700;
color: var(--bl-text);
pointer-events: none;
user-select: none;
font-family: monospace;
letter-spacing: 0.5px;
will-change: transform, opacity;
}
.bl-counter .bl-counter-label {
font-size: 10px;
color: var(--bl-dim);
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 2px;
}
.bl-counter .bl-counter-val {
font-size: 18px;
font-weight: 800;
color: var(--bl-accent);
}
#bl-fps-counter { top: 14px; left: 14px; }
#bl-cps-counter { top: 72px; left: 14px; }
.bl-crosshair {
position: fixed;
top: 50%;
left: 50%;
z-index: 9997;
pointer-events: none;
will-change: transform, opacity;
}
.bl-crosshair-default::before,
.bl-crosshair-default::after {
content: '';
position: absolute;
background: rgba(255,255,255,0.9);
}
.bl-crosshair-default::before {
width: 2px;
height: 24px;
left: 50%;
top: calc(50% - 12px);
transform: translateX(-50%);
}
.bl-crosshair-default::after {
height: 2px;
width: 24px;
top: 50%;
left: calc(50% - 12px);
transform: translateY(-50%);
}
.bl-crosshair-circle::before {
content: '';
position: absolute;
width: 18px;
height: 18px;
border: 2px solid rgba(255,255,255,0.9);
border-radius: 50%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.bl-crosshair-dot::before {
content: '';
position: absolute;
width: 5px;
height: 5px;
background: rgba(255,255,255,0.9);
border-radius: 50%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.bl-crosshair-cross::before,
.bl-crosshair-cross::after {
content: '';
position: absolute;
background: rgba(255,255,255,0.9);
}
.bl-crosshair-cross::before {
width: 2px;
height: 14px;
left: 50%;
top: calc(50% - 7px);
transform: translateX(-50%);
}
.bl-crosshair-cross::after {
height: 2px;
width: 14px;
top: 50%;
left: calc(50% - 7px);
transform: translateY(-50%);
}
.bl-motion-blur {
filter: blur(0px) !important;
transition: filter 0.15s ease-out;
}
.bl-motion-blur.active {
filter: blur(2.5px) !important;
}
.bl-gui-transparent #bloxd-legacy-hud,
.bl-gui-transparent .bl-counter,
.bl-gui-transparent .bl-keystrokes .ks-key,
.bl-gui-transparent .bl-sprint-indicator {
--bl-gui-opacity: var(--bl-gui-custom-opacity, 0.80);
}
#bl-vignette {
position: fixed;
inset: 0;
z-index: 9995;
pointer-events: none;
display: none;
}
#bl-fog {
position: fixed;
inset: 0;
z-index: 9995;
pointer-events: none;
display: none;
}
#bl-grain {
position: fixed;
inset: 0;
z-index: 9996;
pointer-events: none;
display: none;
opacity: 0.06;
background: repeating-radial-gradient(#000 0 0.0001%, #fff 0 0.0002%) 50% 0/2500px 2500px;
animation: blGrain 0.2s steps(5) infinite;
}
@keyframes blGrain {
0% { background-position: 0 0; }
100% { background-position: 100px 100px; }
}
.bl-shader-sep {
height: 1px;
background: var(--bl-border);
margin: 8px 0;
}
#bl-ui-editor {
position: fixed;
inset: 0;
z-index: 2147483645;
background: rgba(0,0,0,0.25);
background-image:
linear-gradient(rgba(255,255,255,0.04) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,0.04) 1px, transparent 1px);
background-size: 24px 24px;
display: none;
pointer-events: auto;
}
#bl-ui-editor.active { display: block; }
.bl-ui-editor-el {
cursor: move !important;
pointer-events: auto !important;
outline: 2px dashed rgba(59, 130, 246, 0.5) !important;
outline-offset: 6px !important;
transition: none !important;
}
.bl-ui-editor-el.bl-ui-selected {
outline: 2px solid #3b82f6 !important;
outline-offset: 6px !important;
z-index: 2147483646 !important;
}
#bl-ui-editor-panel {
position: fixed;
top: 24px;
right: 24px;
width: 300px;
max-height: calc(100vh - 48px);
overflow-y: auto;
background: rgba(15, 15, 25, 0.96);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 14px;
padding: 22px;
z-index: 2147483647;
color: #e2e8f0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
display: none;
box-shadow: 0 25px 50px -12px rgba(0,0,0,0.6);
backdrop-filter: blur(12px);
}
#bl-ui-editor-panel.active { display: block; }
#bl-ui-editor-panel .bl-slider { width: 100%; }
#bl-ui-editor-panel .bl-slider-val { min-width: 48px; text-align: right; }
#bl-ui-editor-hint {
position: fixed;
bottom: 24px;
left: 50%;
transform: translateX(-50%);
background: rgba(15,15,25,0.9);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 20px;
padding: 8px 20px;
color: #94a3b8;
font-size: 12px;
z-index: 2147483647;
pointer-events: none;
display: none;
}
#bl-ui-editor-hint.active { display: block; }
#bl-canvas-wrapper {
position: fixed;
inset: 0;
z-index: 0;
pointer-events: none;
overflow: hidden;
}
`;
let overlayEl, menuEl;
function buildUI() {
try {
const style = make('style', '', document.head);
style.textContent = CSS;
const hudEl = make('div', '', document.body);
hudEl.id = 'bloxd-legacy-hud';
hudEl.textContent = 'Bloxd Legacy';
const sprintInd = make('div', 'bl-sprint-indicator', document.body);
sprintInd.id = 'bl-sprint-indicator';
sprintInd.textContent = 'SPRINTING';
const hitboxOverlay = make('div', '', document.body);
hitboxOverlay.id = 'bl-hitbox-overlay';
const fpsEl = make('div', 'bl-counter', document.body);
fpsEl.id = 'bl-fps-counter';
fpsEl.innerHTML = '<div class="bl-counter-label">FPS</div><div class="bl-counter-val" id="bl-fps-val">0</div>';
const cpsEl = make('div', 'bl-counter', document.body);
cpsEl.id = 'bl-cps-counter';
cpsEl.innerHTML = '<div class="bl-counter-label">CPS</div><div class="bl-counter-val" id="bl-cps-val">0</div>';
const ksEl = make('div', 'bl-keystrokes', document.body);
ksEl.id = 'bl-keystrokes';
['W','A','S','D'].forEach(k => {
const keyDiv = make('div', 'ks-key', ksEl);
keyDiv.dataset.key = k;
keyDiv.textContent = k;
});
const crosshairEl = make('div', 'bl-crosshair bl-crosshair-default', document.body);
crosshairEl.id = 'bl-crosshair';
const vignetteEl = make('div', '', document.body);
vignetteEl.id = 'bl-vignette';
const fogEl = make('div', '', document.body);
fogEl.id = 'bl-fog';
const grainEl = make('div', '', document.body);
grainEl.id = 'bl-grain';
const uiEditor = make('div', '', document.body);
uiEditor.id = 'bl-ui-editor';
on(uiEditor, 'mousedown', (e) => {
if (e.target === uiEditor && selectedUIElement) {
const prev = $1(UI_DEFS[selectedUIElement]?.selector);
if (prev) prev.classList.remove('bl-ui-selected');
selectedUIElement = null;
refreshUIEditorPanel();
}
});
const uiPanel = make('div', '', document.body);
uiPanel.id = 'bl-ui-editor-panel';
const uiHint = make('div', '', document.body);
uiHint.id = 'bl-ui-editor-hint';
uiHint.textContent = 'Click an element to select it. Drag to move. Use the panel to adjust properties.';
overlayEl = make('div', '', document.body);
overlayEl.id = 'bloxd-legacy-overlay';
overlayEl.tabIndex = -1;
menuEl = make('div', '', overlayEl);
menuEl.id = 'bloxd-legacy-menu';
const header = make('div', 'bl-header', menuEl);
const title = make('span', 'bl-title', header);
title.textContent = 'Bloxd Legacy';
const closeBtn = make('button', 'bl-close', header);
closeBtn.innerHTML = '✕';
closeBtn.addEventListener('click', closeMenu);
const body = make('div', 'bl-body', menuEl);
const sidebar = make('div', 'bl-sidebar', body);
const tabs = [
{ id: 'combat', label: '[WIP] Combat', icon: '⚔' },
{ id: 'cosmetics',label: '[WIP] Cosmetics', icon: '👤' },
{ id: 'capes', label: '[WIP] Capes', icon: '👑' },
{ id: 'visuals', label: 'Visuals', icon: '👁' },
{ id: 'shaders', label: '[WIP] Shaders', icon: '✨' },
{ id: 'settings', label: 'Settings', icon: '⚙' }
];
tabs.forEach(t => {
const btn = make('button', 'bl-tab', sidebar);
btn.innerHTML = '<span style="font-size:16px;width:20px;text-align:center;display:inline-block">' + t.icon + '</span> ' + t.label;
btn.dataset.tab = t.id;
if (t.id === currentTab) btn.classList.add('active');
btn.addEventListener('click', () => switchTab(t.id));
});
const content = make('div', 'bl-content', body);
const combat = createPanel(content, 'combat');
make('div', 'bl-section-title', combat).textContent = 'Combat Modules';
make('div', 'bl-section-note', combat).textContent =
'Real combat enhancements. Hitboxes are a visual aid. Sprint is simulated via synthetic Shift events.';
addToggle(combat, 'Player Hitboxes', 'combat', 'hitboxes', 'Visual hitbox overlay + player DOM highlighting');
addToggle(combat, 'Toggle Sprint', 'combat', 'toggleSprint', 'Auto-hold Shift when W is pressed (7 BPS sprint)');
addToggle(combat, 'No Hurt Cam', 'combat', 'noHurtCam', 'Dampens red damage flashes via canvas filter');
addToggle(combat, 'Small Items', 'combat', 'smallItems', 'Shrinks hotbar item slots for cleaner PvP view');
addSlider(combat, 'Item Scale', 'combat', 'smallItemsScale', 0.5, 1.0, 0.05, 'Hotbar slot scale multiplier');
const cosmetics = createPanel(content, 'cosmetics');
make('div', 'bl-section-title', cosmetics).textContent = 'Player Cosmetics';
addSelect(cosmetics, 'Cape Style', 'cosmetics', 'cape', [
{ value: 'none', label: 'None' },
{ value: 'classic', label: 'Classic' },
{ value: 'mojang', label: 'Mojang' },
{ value: 'custom', label: 'Custom URL' }
], 'Choose a built-in cape or supply your own');
addInput(cosmetics, 'Custom Cape URL', 'cosmetics', 'customUrl', 'https://example.com/cape.png', 'Direct link to a PNG image');
const capes = createPanel(content, 'capes');
make('div', 'bl-section-title', capes).textContent = 'Cape Gallery';
const capeGrid = make('div', 'bl-cape-grid', capes);
const capeDefs = [
{ id: 'none', color: '#334155', label: 'None' },
{ id: 'red', color: '#ef4444', label: 'Red' },
{ id: 'blue', color: '#3b82f6', label: 'Blue' },
{ id: 'green', color: '#22c55e', label: 'Green' },
{ id: 'yellow', color: '#eab308', label: 'Yellow' },
{ id: 'purple', color: '#a855f7', label: 'Purple' },
{ id: 'orange', color: '#f97316', label: 'Orange' },
{ id: 'cyan', color: '#06b6d4', label: 'Cyan' }
];
capeDefs.forEach(c => {
const item = make('div', 'bl-cape-item', capeGrid);
item.style.background = c.color;
item.dataset.cape = c.id;
const lbl = make('span', 'bl-cape-label', item);
lbl.textContent = c.label;
item.addEventListener('click', () => {
config.capes.selected = c.id;
saveConfig();
refreshCapeGrid(capeGrid);
});
});
refreshCapeGrid(capeGrid);
const visuals = createPanel(content, 'visuals');
make('div', 'bl-section-title', visuals).textContent = 'Visual Enhancements';
make('div', 'bl-section-note', visuals).textContent =
'HUD overlays, counters, crosshair styles, and viewport effects.';
addToggle(visuals, 'Keystrokes Display', 'visuals', 'keystrokes', 'Shows W/A/S/D key states in bottom-left corner');
addToggle(visuals, 'CPS Counter', 'visuals', 'cps', 'Clicks per second counter in top-left');
addToggle(visuals, 'FPS Counter', 'visuals', 'fps', 'Frames per second counter in top-left');
addToggle(visuals, 'Motion Blur', 'visuals', 'motionBlur', 'Simulated motion blur on fast camera movement');
addSelect(visuals, '[WIP] Crosshair Style', 'visuals', 'crosshair', [
{ value: 'none', label: 'None (Game Default)' },
{ value: 'default', label: 'Default Cross' },
{ value: 'circle', label: 'Circle' },
{ value: 'dot', label: 'Dot' },
{ value: 'cross', label: 'Small Cross' }
], 'Replaces the in-game crosshair with a custom overlay');
addToggle(visuals, 'Smooth Zoom', 'visuals', 'smoothZoom', 'Hold Z to smoothly zoom in; release to zoom out');
addSlider(visuals, 'Zoom Level', 'visuals', 'smoothZoomLevel', 1.0, 3.0, 0.1, 'Maximum zoom multiplier when holding Z');
addSlider(visuals, 'Field of View', 'visuals', 'fov', 60, 120, 1, 'Horizontal camera FOV (requires game rejoin to apply)');
addSlider(visuals, 'GUI Opacity', 'visuals', 'guiOpacity', 0.3, 1.0, 0.05, 'Transparency of HUD overlays');
make('div', 'bl-shader-sep', visuals);
const editRow = make('div', 'bl-row', visuals);
const editInfo = make('div', 'bl-row-info', editRow);
make('div', 'bl-row-title', editInfo).textContent = 'Edit UI Layout';
make('div', 'bl-row-desc', editInfo).textContent = 'Drag, resize, recolor, and reposition HUD overlays';
const editBtn = make('button', 'bl-btn', editRow);
editBtn.textContent = 'Open Editor';
editBtn.addEventListener('click', () => {
closeMenu();
enterUIEditMode();
});
const shaders = createPanel(content, 'shaders');
make('div', 'bl-section-title', shaders).textContent = 'Shader Post-Processing';
make('div', 'bl-section-note', shaders).textContent =
'GPU-accelerated CSS filter stack + overlay effects. True GLSL injection into Unity is not possible from a userscript. This is the closest working alternative.';
const presetRow = make('div', 'bl-row', shaders);
const presetInfo = make('div', 'bl-row-info', presetRow);
make('div', 'bl-row-title', presetInfo).textContent = 'Shader Preset';
make('div', 'bl-row-desc', presetInfo).textContent = 'Load a preset or customize manually';
const presetSel = make('select', 'bl-select', presetRow);
[
{ value: 'performance', label: 'Performance' },
{ value: 'balanced', label: 'Balanced' },
{ value: 'cinematic', label: 'Cinematic' },
{ value: 'custom', label: 'Custom' }
].forEach(o => {
const opt = make('option', '', presetSel);
opt.value = o.value;
opt.textContent = o.label;
});
presetSel.value = config.shaders.preset;
presetSel.addEventListener('change', () => {
const name = presetSel.value;
if (name !== 'custom' && SHADER_PRESETS[name]) {
Object.assign(config.shaders, SHADER_PRESETS[name]);
config.shaders.preset = name;
saveConfig();
updateShaderUI();
applyShaderFilters();
} else {
config.shaders.preset = 'custom';
saveConfig();
}
});
addShaderToggle(shaders, 'FPS Boost', 'fpsBoost', 'Lowers render resolution to boost frame rate on low-end PCs');
addShaderSlider(shaders, 'Resolution Scale', 'resScale', 0.5, 1.0, 0.05);
make('div', 'bl-shader-sep', shaders);
addShaderSlider(shaders, 'Brightness', 'brightness', 0.5, 2.0, 0.05);
addShaderSlider(shaders, 'Contrast', 'contrast', 0.5, 2.0, 0.05);
addShaderSlider(shaders, 'Saturation', 'saturate', 0.0, 2.0, 0.05);
addShaderSlider(shaders, 'Color Temp', 'hueRotate', -30, 30, 1);
addShaderSlider(shaders, 'Bloom', 'bloom', 0, 1.0, 0.05);
addShaderSlider(shaders, 'Fog', 'fog', 0, 1.0, 0.05);
addShaderSlider(shaders, 'Shadows', 'shadows', 0, 1.0, 0.05);
addShaderSlider(shaders, 'Vignette', 'vignette', 0, 1.0, 0.05);
addShaderToggle(shaders, 'Film Grain', 'grain', 'Subtle noise overlay for cinematic look');
const settings = createPanel(content, 'settings');
make('div', 'bl-section-title', settings).textContent = 'Client Settings';
addSlider(settings, 'UI Scale', 'settings', 'uiScale', 0.8, 1.5, 0.05, 'Resize the entire menu interface');
addInfoRow(settings, 'Menu Keybind', 'Right Shift');
const btnRow = make('div', 'bl-row bl-btn-row', settings);
const resetBtn = make('button', 'bl-btn secondary', btnRow);
resetBtn.textContent = 'Reset Defaults';
resetBtn.addEventListener('click', () => {
if (confirm('Reset all Bloxd Legacy settings to default?')) {
config = JSON.parse(JSON.stringify(DEFAULT_CONFIG));
saveConfig();
location.reload();
}
});
const exportBtn = make('button', 'bl-btn', btnRow);
exportBtn.textContent = 'Export Config';
exportBtn.addEventListener('click', () => {
const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'bloxd-legacy-config.json';
a.click();
URL.revokeObjectURL(url);
});
applyUIScale();
overlayEl.addEventListener('click', e => {
if (e.target === overlayEl) closeMenu();
});
buildUIEditorPanel();
initFeatureStates();
} catch (e) {
console.error('[BloxdLegacy] buildUI failed:', e);
}
}
function createPanel(parent, id) {
const p = make('div', 'bl-panel', parent);
p.dataset.panel = id;
if (id === currentTab) p.classList.add('active');
return p;
}
function addToggle(parent, title, cat, key, desc) {
const row = make('div', 'bl-row', parent);
const info = make('div', 'bl-row-info', row);
make('div', 'bl-row-title', info).textContent = title;
make('div', 'bl-row-desc', info).textContent = desc;
const t = make('input', 'bl-toggle', row);
t.type = 'checkbox';
t.checked = !!config[cat][key];
t.addEventListener('change', () => {
config[cat][key] = t.checked;
saveConfig();
applyFeatureState(cat, key, t.checked);
});
}
function addSlider(parent, title, cat, key, min, max, step, desc) {
const row = make('div', 'bl-row', parent);
const info = make('div', 'bl-row-info', row);
make('div', 'bl-row-title', info).textContent = title;
make('div', 'bl-row-desc', info).textContent = desc;
const wrap = make('div', 'bl-slider-wrap', row);
const s = make('input', 'bl-slider', wrap);
s.type = 'range';
s.min = min; s.max = max; s.step = step;
s.value = config[cat][key];
const val = make('span', 'bl-slider-val', wrap);
val.textContent = fmtSlider(config[cat][key], step);
s.addEventListener('input', () => {
const v = parseFloat(s.value);
config[cat][key] = v;
val.textContent = fmtSlider(v, step);
saveConfig();
if (cat === 'settings' && key === 'uiScale') applyUIScale();
if (cat === 'combat' && key === 'smallItemsScale') applySmallItemsScale(v);
if (cat === 'visuals' && key === 'guiOpacity') applyGUIOpacity(v);
if (cat === 'visuals' && key === 'smoothZoomLevel') applyZoomLevel(v);
});
}
function fmtSlider(v, step) {
return step < 1 ? v.toFixed(2) : Math.round(v).toString();
}
function addSelect(parent, title, cat, key, opts, desc) {
const row = make('div', 'bl-row', parent);
const info = make('div', 'bl-row-info', row);
make('div', 'bl-row-title', info).textContent = title;
make('div', 'bl-row-desc', info).textContent = desc;
const sel = make('select', 'bl-select', row);
opts.forEach(o => {
const opt = make('option', '', sel);
opt.value = o.value;
opt.textContent = o.label;
});
sel.value = config[cat][key];
sel.addEventListener('change', () => {
config[cat][key] = sel.value;
saveConfig();
if (cat === 'visuals' && key === 'crosshair') applyCrosshair(sel.value);
});
}
function addInput(parent, title, cat, key, placeholder, desc) {
const row = make('div', 'bl-row', parent);
const info = make('div', 'bl-row-info', row);
make('div', 'bl-row-title', info).textContent = title;
make('div', 'bl-row-desc', info).textContent = desc;
const inp = make('input', 'bl-input', row);
inp.type = 'text';
inp.placeholder = placeholder;
inp.value = config[cat][key] || '';
inp.addEventListener('input', () => {
config[cat][key] = inp.value;
saveConfig();
});
}
function addInfoRow(parent, title, value) {
const row = make('div', 'bl-info-row', parent);
make('div', 'bl-row-title', row).textContent = title;
const v = make('div', '', row);
v.style.color = 'var(--bl-dim)';
v.style.fontSize = '13px';
v.textContent = value;
}
function addShaderToggle(parent, title, key, desc) {
const row = make('div', 'bl-row', parent);
const info = make('div', 'bl-row-info', row);
make('div', 'bl-row-title', info).textContent = title;
make('div', 'bl-row-desc', info).textContent = desc;
const t = make('input', 'bl-toggle', row);
t.type = 'checkbox';
t.checked = !!config.shaders[key];
t.dataset.shaderKey = key;
t.addEventListener('change', () => {
config.shaders[key] = t.checked;
config.shaders.preset = 'custom';
saveConfig();
applyShaderFilters();
});
}
function addShaderSlider(parent, title, key, min, max, step) {
const row = make('div', 'bl-row', parent);
const info = make('div', 'bl-row-info', row);
make('div', 'bl-row-title', info).textContent = title;
const wrap = make('div', 'bl-slider-wrap', row);
const s = make('input', 'bl-slider', wrap);
s.type = 'range';
s.min = min; s.max = max; s.step = step;
s.value = config.shaders[key];
s.dataset.shaderKey = key;
const val = make('span', 'bl-slider-val', wrap);
val.textContent = fmtSlider(config.shaders[key], step);
s.addEventListener('input', () => {
const v = parseFloat(s.value);
config.shaders[key] = v;
config.shaders.preset = 'custom';
val.textContent = fmtSlider(v, step);
saveConfig();
applyShaderFilters();
});
}
function updateShaderUI() {
document.querySelectorAll('[data-shader-key]').forEach(el => {
const key = el.dataset.shaderKey;
if (el.type === 'checkbox') {
el.checked = !!config.shaders[key];
} else if (el.type === 'range') {
el.value = config.shaders[key];
const valEl = el.parentElement.querySelector('.bl-slider-val');
if (valEl) valEl.textContent = fmtSlider(config.shaders[key], parseFloat(el.step) || 0.05);
}
});
}
function refreshCapeGrid(grid) {
$all('.bl-cape-item', grid).forEach(item => {
item.classList.toggle('active', item.dataset.cape === config.capes.selected);
});
}
function applyUIScale() {
document.documentElement.style.setProperty('--bl-scale', config.settings.uiScale);
}
function initFeatureStates() {
applyFeatureState('combat', 'hitboxes', config.combat.hitboxes);
applyFeatureState('combat', 'toggleSprint', config.combat.toggleSprint);
applyFeatureState('combat', 'noHurtCam', config.combat.noHurtCam);
applyFeatureState('combat', 'smallItems', config.combat.smallItems);
applySmallItemsScale(config.combat.smallItemsScale);
applyFeatureState('visuals', 'keystrokes', config.visuals.keystrokes);
applyFeatureState('visuals', 'cps', config.visuals.cps);
applyFeatureState('visuals', 'fps', config.visuals.fps);
applyFeatureState('visuals', 'motionBlur', config.visuals.motionBlur);
applyCrosshair(config.visuals.crosshair);
applyFeatureState('visuals', 'smoothZoom', config.visuals.smoothZoom);
applyZoomLevel(config.visuals.smoothZoomLevel);
applyGUIOpacity(config.visuals.guiOpacity);
applyShaderFilters();
applyUILayout();
}
function applyFeatureState(cat, key, enabled) {
if (cat === 'combat') {
switch (key) {
case 'hitboxes': setHitboxes(enabled); break;
case 'toggleSprint': break;
case 'noHurtCam': setNoHurtCam(enabled); break;
case 'smallItems': setSmallItems(enabled); break;
}
} else if (cat === 'visuals') {
switch (key) {
case 'keystrokes': setKeystrokes(enabled); break;
case 'cps': setCPSCounter(enabled); break;
case 'fps': setFPSCounter(enabled); break;
case 'motionBlur': setMotionBlur(enabled); break;
case 'smoothZoom': setSmoothZoom(enabled); break;
}
}
}
/* Player Hitboxes */
let playerHighlightObserver = null;
let highlightDebounce = null;
function setHitboxes(enabled) {
const overlay = document.getElementById('bl-hitbox-overlay');
if (overlay) overlay.classList.toggle('active', enabled);
if (enabled) startPlayerHighlighting();
else stopPlayerHighlighting();
}
function startPlayerHighlighting() {
const doHighlight = () => {
const selectors = ['[class*="name"]','[class*="player"]','[class*="health"]','[class*="nametag"]','[class*="entity"]'];
selectors.forEach(sel => {
document.querySelectorAll(sel).forEach(el => {
const text = el.textContent?.toLowerCase() || '';
if (text.length > 0 && text.length < 30 && !el.dataset.blHighlighted) {
el.dataset.blHighlighted = 'true';
el.classList.add('bl-player-highlight');
}
});
});
};
doHighlight();
playerHighlightObserver = new MutationObserver(() => {
if (highlightDebounce) clearTimeout(highlightDebounce);
highlightDebounce = setTimeout(doHighlight, 500);
});
trackedObservers.push(playerHighlightObserver);
playerHighlightObserver.observe(document.body, { childList: true, subtree: true });
}
function stopPlayerHighlighting() {
if (playerHighlightObserver) { playerHighlightObserver.disconnect(); playerHighlightObserver = null; }
if (highlightDebounce) { clearTimeout(highlightDebounce); highlightDebounce = null; }
document.querySelectorAll('.bl-player-highlight').forEach(el => {
el.classList.remove('bl-player-highlight');
delete el.dataset.blHighlighted;
});
}
/* No Hurt Cam */
function setNoHurtCam(enabled) {
document.body.classList.toggle('bl-feature-no-hurt-cam', enabled);
}
/* Small Items */
let itemMutationObserver = null;
function setSmallItems(enabled) {
document.body.classList.toggle('bl-feature-small-items', enabled);
applySmallItemsToExisting(enabled);
if (enabled) {
if (!itemMutationObserver) {
itemMutationObserver = new MutationObserver(mutations => {
if (!config.combat.smallItems) return;
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType !== 1) return;
if (node.classList?.contains('item') || node.classList?.contains('SelectedItem')) {
applySmallItemsToElement(node);
}
node.querySelectorAll?.('.item, .SelectedItem').forEach(applySmallItemsToElement);
});
});
});
trackedObservers.push(itemMutationObserver);
itemMutationObserver.observe(document.body, { childList: true, subtree: true });
}
} else {
if (itemMutationObserver) { itemMutationObserver.disconnect(); itemMutationObserver = null; }
document.querySelectorAll('.item, .SelectedItem').forEach(el => {
el.style.transform = '';
el.style.transformOrigin = '';
});
}
}
function applySmallItemsToExisting(enabled) {
document.querySelectorAll('.item, .SelectedItem').forEach(el => {
if (enabled) applySmallItemsToElement(el);
else { el.style.transform = ''; el.style.transformOrigin = ''; }
});
}
function applySmallItemsToElement(el) {
el.style.transform = 'scale(' + config.combat.smallItemsScale + ')';
el.style.transformOrigin = 'bottom center';
}
function applySmallItemsScale(scale) {
document.documentElement.style.setProperty('--bl-item-scale', scale);
if (config.combat.smallItems) applySmallItemsToExisting(true);
}
/* Keystrokes */
function setKeystrokes(enabled) {
const el = document.getElementById('bl-keystrokes');
if (el) el.style.display = enabled ? 'grid' : 'none';
}
on(document, 'keydown', function(e) {
const keyMap = { 'KeyW': 'W', 'KeyA': 'A', 'KeyS': 'S', 'KeyD': 'D' };
const mapped = keyMap[e.code];
if (mapped) {
const keyEl = document.querySelector('.bl-keystrokes .ks-key[data-key="' + mapped + '"]');
if (keyEl) keyEl.classList.add('pressed');
}
}, { passive: true });
on(document, 'keyup', function(e) {
const keyMap = { 'KeyW': 'W', 'KeyA': 'A', 'KeyS': 'S', 'KeyD': 'D' };
const mapped = keyMap[e.code];
if (mapped) {
const keyEl = document.querySelector('.bl-keystrokes .ks-key[data-key="' + mapped + '"]');
if (keyEl) keyEl.classList.remove('pressed');
}
}, { passive: true });
/* CPS Counter */
let cpsInterval = null;
const clickTimes = [];
function setCPSCounter(enabled) {
const el = document.getElementById('bl-cps-counter');
if (el) el.style.display = enabled ? 'block' : 'none';
if (enabled && !cpsInterval) {
cpsInterval = setInterval(() => {
const now = performance.now();
const cutoff = now - 1000;
let i = 0;
while (i < clickTimes.length && clickTimes[i] < cutoff) i++;
if (i > 0) clickTimes.splice(0, i);
const valEl = document.getElementById('bl-cps-val');
if (valEl) valEl.textContent = clickTimes.length;
}, 100);
trackedIntervals.push(cpsInterval);
} else if (!enabled && cpsInterval) {
clearInterval(cpsInterval);
cpsInterval = null;
}
}
on(document, 'mousedown', () => {
if (config.visuals.cps) clickTimes.push(performance.now());
}, { passive: true });
/* FPS Counter */
let fpsRaf = null;
let frameCount = 0;
let lastFpsTime = performance.now();
function setFPSCounter(enabled) {
const el = document.getElementById('bl-fps-counter');
if (el) el.style.display = enabled ? 'block' : 'none';
if (enabled && !fpsRaf) {
const tick = () => {
frameCount++;
const now = performance.now();
if (now - lastFpsTime >= 1000) {
const valEl = document.getElementById('bl-fps-val');
if (valEl) valEl.textContent = frameCount;
frameCount = 0;
lastFpsTime = now;
}
fpsRaf = requestAnimationFrame(tick);
};
fpsRaf = requestAnimationFrame(tick);
trackedRAFs.push(fpsRaf);
} else if (!enabled && fpsRaf) {
cancelAnimationFrame(fpsRaf);
fpsRaf = null;
}
}
/* Motion Blur */
let motionBlurTimeout = null;
let lastMouseX = 0;
let lastMouseY = 0;
let lastMouseMove = 0;
function setMotionBlur(enabled) {
const canvas = document.querySelector('canvas');
if (!canvas) return;
if (enabled) {
canvas.classList.add('bl-motion-blur');
on(document, 'mousemove', onMouseMoveForBlur, { passive: true });
} else {
canvas.classList.remove('bl-motion-blur', 'active');
off(document, 'mousemove', onMouseMoveForBlur, { passive: true });
}
}
function onMouseMoveForBlur(e) {
const now = performance.now();
if (now - lastMouseMove < 16) return;
lastMouseMove = now;
const dx = e.clientX - lastMouseX;
const dy = e.clientY - lastMouseY;
const dist = Math.sqrt(dx * dx + dy * dy);
lastMouseX = e.clientX;
lastMouseY = e.clientY;
const canvas = document.querySelector('canvas');
if (!canvas) return;
if (dist > 8) {
canvas.classList.add('active');
if (motionBlurTimeout) clearTimeout(motionBlurTimeout);
motionBlurTimeout = setTimeout(() => { canvas.classList.remove('active'); }, 120);
}
}
/* Crosshair */
function applyCrosshair(style) {
const el = document.getElementById('bl-crosshair');
if (!el) return;
el.className = 'bl-crosshair';
if (style === 'none') { el.style.display = 'none'; }
else { el.style.display = 'block'; el.classList.add('bl-crosshair-' + style); }
}
/* Smooth Zoom */
let zoomActive = false;
let currentZoom = 1.0;
let targetZoom = 1.0;
let zoomRaf = null;
function setSmoothZoom(enabled) {
if (enabled) {
on(document, 'keydown', onZoomKeyDown, { passive: true });
on(document, 'keyup', onZoomKeyUp, { passive: true });
} else {
off(document, 'keydown', onZoomKeyDown, { passive: true });
off(document, 'keyup', onZoomKeyUp, { passive: true });
targetZoom = 1.0; currentZoom = 1.0;
applyCanvasZoom(1.0);
if (zoomRaf) { cancelAnimationFrame(zoomRaf); zoomRaf = null; }
}
}
function onZoomKeyDown(e) {
if (e.code === 'KeyZ' && !zoomActive && config.visuals.smoothZoom) {
zoomActive = true;
targetZoom = config.visuals.smoothZoomLevel;
startZoomLoop();
}
}
function onZoomKeyUp(e) {
if (e.code === 'KeyZ' && zoomActive) { zoomActive = false; targetZoom = 1.0; }
}
function startZoomLoop() {
if (zoomRaf) return;
const loop = () => {
const diff = targetZoom - currentZoom;
if (Math.abs(diff) < 0.005) {
currentZoom = targetZoom;
applyCanvasZoom(currentZoom);
if (targetZoom === 1.0) { zoomRaf = null; return; }
} else {
currentZoom += diff * 0.12;
applyCanvasZoom(currentZoom);
}
zoomRaf = requestAnimationFrame(loop);
};
zoomRaf = requestAnimationFrame(loop);
trackedRAFs.push(zoomRaf);
}
function applyCanvasZoom(zoom) {
const canvas = document.querySelector('canvas');
if (!canvas) return;
if (zoom === 1.0) { canvas.style.transform = ''; canvas.style.transformOrigin = ''; }
else { canvas.style.transform = 'scale(' + (1 / zoom) + ')'; canvas.style.transformOrigin = 'center center'; }
}
function applyZoomLevel(level) {}
/* GUI Opacity */
function applyGUIOpacity(opacity) {
document.documentElement.style.setProperty('--bl-gui-custom-opacity', opacity);
if (opacity < 1.0) document.body.classList.add('bl-gui-transparent');
else document.body.classList.remove('bl-gui-transparent');
}
/* [WIP] Shader System */
function applyShaderFilters() {
const s = config.shaders;
const canvas = document.querySelector('canvas');
if (!canvas) return;
const filters = [];
filters.push('brightness(' + s.brightness + ')');
filters.push('contrast(' + s.contrast + ')');
filters.push('saturate(' + s.saturate + ')');
if (s.sepia > 0) filters.push('sepia(' + s.sepia + ')');
if (s.hueRotate !== 0) filters.push('hue-rotate(' + s.hueRotate + 'deg)');
if (s.bloom > 0) {
const bloomBright = 1 + (s.bloom * 0.3);
filters.push('brightness(' + bloomBright + ')');
}
canvas.style.filter = filters.join(' ');
const vignetteEl = document.getElementById('bl-vignette');
if (vignetteEl) {
if (s.vignette > 0) {
vignetteEl.style.display = 'block';
const opacity = s.vignette * 0.8;
vignetteEl.style.background = 'radial-gradient(circle, transparent ' + (60 - s.vignette * 20) + '%, rgba(0,0,0,' + opacity + ') 100%)';
} else {
vignetteEl.style.display = 'none';
}
}
const fogEl = document.getElementById('bl-fog');
if (fogEl) {
if (s.fog > 0) {
fogEl.style.display = 'block';
fogEl.style.background = 'linear-gradient(to bottom, rgba(200,210,230,0) 0%, rgba(200,210,230,' + (s.fog * 0.5) + ') 100%)';
} else {
fogEl.style.display = 'none';
}
}
const grainEl = document.getElementById('bl-grain');
if (grainEl) {
grainEl.style.display = s.grain ? 'block' : 'none';
}
if (s.fpsBoost) {
let wrapper = document.getElementById('bl-canvas-wrapper');
if (!wrapper) {
wrapper = document.createElement('div');
wrapper.id = 'bl-canvas-wrapper';
document.body.insertBefore(wrapper, document.body.firstChild);
}
if (canvas.parentElement !== wrapper) {
wrapper.appendChild(canvas);
}
const scale = s.resScale;
canvas.style.width = (window.innerWidth / scale) + 'px';
canvas.style.height = (window.innerHeight / scale) + 'px';
canvas.style.transform = 'scale(' + scale + ')';
canvas.style.transformOrigin = 'top left';
} else {
let wrapper = document.getElementById('bl-canvas-wrapper');
if (wrapper && canvas.parentElement === wrapper) {
document.body.insertBefore(canvas, wrapper);
wrapper.remove();
}
canvas.style.width = '';
canvas.style.height = '';
canvas.style.transform = '';
canvas.style.transformOrigin = '';
}
}
/* UI Editor System */
function buildUIEditorPanel() {
const panel = document.getElementById('bl-ui-editor-panel');
if (!panel) return;
panel.innerHTML = `
<div style="font-size:16px;font-weight:700;margin-bottom:4px;" id="bl-ui-panel-title">Select an element</div>
<div style="font-size:12px;color:#94a3b8;margin-bottom:16px;" id="bl-ui-panel-sub">Click a HUD element to edit it.</div>
<div id="bl-ui-controls" style="display:none;">
<div style="margin-bottom:12px;">
<div style="font-size:12px;font-weight:600;margin-bottom:6px;">Position X</div>
<div style="display:flex;align-items:center;gap:10px;">
<input type="range" class="bl-slider" id="bl-ui-x" min="-500" max="500" step="1" style="flex:1;">
<span class="bl-slider-val" id="bl-ui-x-val">0</span>
</div>
</div>
<div style="margin-bottom:12px;">
<div style="font-size:12px;font-weight:600;margin-bottom:6px;">Position Y</div>
<div style="display:flex;align-items:center;gap:10px;">
<input type="range" class="bl-slider" id="bl-ui-y" min="-500" max="500" step="1" style="flex:1;">
<span class="bl-slider-val" id="bl-ui-y-val">0</span>
</div>
</div>
<div style="margin-bottom:12px;">
<div style="font-size:12px;font-weight:600;margin-bottom:6px;">Scale</div>
<div style="display:flex;align-items:center;gap:10px;">
<input type="range" class="bl-slider" id="bl-ui-scale" min="0.5" max="2.0" step="0.05" style="flex:1;">
<span class="bl-slider-val" id="bl-ui-scale-val">1.00</span>
</div>
</div>
<div style="margin-bottom:12px;">
<div style="font-size:12px;font-weight:600;margin-bottom:6px;">Opacity</div>
<div style="display:flex;align-items:center;gap:10px;">
<input type="range" class="bl-slider" id="bl-ui-opacity" min="0.1" max="1.0" step="0.05" style="flex:1;">
<span class="bl-slider-val" id="bl-ui-opacity-val">100%</span>
</div>
</div>
<div style="margin-bottom:16px;">
<div style="font-size:12px;font-weight:600;margin-bottom:6px;">Accent Color</div>
<div style="display:flex;align-items:center;gap:10px;">
<input type="color" id="bl-ui-color" style="width:40px;height:32px;border:none;border-radius:6px;cursor:pointer;background:none;">
<span style="font-size:12px;color:#94a3b8;" id="bl-ui-color-val">#3b82f6</span>
</div>
</div>
<div style="display:flex;gap:8px;">
<button class="bl-btn secondary" style="flex:1;" id="bl-ui-reset">Reset</button>
<button class="bl-btn" style="flex:1;" id="bl-ui-done">Done</button>
</div>
</div>
`;
panel.querySelector('#bl-ui-x').addEventListener('input', (e) => {
if (!selectedUIElement) return;
const v = parseInt(e.target.value);
config.uiLayout[selectedUIElement].x = v;
document.getElementById('bl-ui-x-val').textContent = v;
updateUIElementEditPos(selectedUIElement);
saveConfig();
});
panel.querySelector('#bl-ui-y').addEventListener('input', (e) => {
if (!selectedUIElement) return;
const v = parseInt(e.target.value);
config.uiLayout[selectedUIElement].y = v;
document.getElementById('bl-ui-y-val').textContent = v;
updateUIElementEditPos(selectedUIElement);
saveConfig();
});
panel.querySelector('#bl-ui-scale').addEventListener('input', (e) => {
if (!selectedUIElement) return;
const v = parseFloat(e.target.value);
config.uiLayout[selectedUIElement].scale = v;
document.getElementById('bl-ui-scale-val').textContent = v.toFixed(2);
applyUILayout();
saveConfig();
});
panel.querySelector('#bl-ui-opacity').addEventListener('input', (e) => {
if (!selectedUIElement) return;
const v = parseFloat(e.target.value);
config.uiLayout[selectedUIElement].opacity = v;
document.getElementById('bl-ui-opacity-val').textContent = Math.round(v*100) + '%';
applyUILayout();
saveConfig();
});
panel.querySelector('#bl-ui-color').addEventListener('input', (e) => {
if (!selectedUIElement) return;
const v = e.target.value;
config.uiLayout[selectedUIElement].color = v;
document.getElementById('bl-ui-color-val').textContent = v;
applyUILayout();
saveConfig();
});
panel.querySelector('#bl-ui-reset').addEventListener('click', () => {
if (!selectedUIElement) return;
const def = UI_DEFS[selectedUIElement];
config.uiLayout[selectedUIElement] = { x: 0, y: 0, scale: 1, opacity: 1, color: def.defaultColor };
applyUILayout();
saveConfig();
refreshUIEditorPanel();
const el = document.querySelector(def.selector);
if (el) {
const rect = el.getBoundingClientRect();
const base = ANCHORS[def.anchor](rect.width, rect.height, selectedUIElement);
el.style.left = base.x + 'px';
el.style.top = base.y + 'px';
}
});
panel.querySelector('#bl-ui-done').addEventListener('click', exitUIEditMode);
}
function refreshUIEditorPanel() {
if (!selectedUIElement) {
const title = document.getElementById('bl-ui-panel-title');
const sub = document.getElementById('bl-ui-panel-sub');
const controls = document.getElementById('bl-ui-controls');
if (title) title.textContent = 'Select an element';
if (sub) { sub.style.display = 'block'; sub.textContent = 'Click a HUD element to edit it.'; }
if (controls) controls.style.display = 'none';
return;
}
const def = UI_DEFS[selectedUIElement];
const layout = config.uiLayout[selectedUIElement];
const title = document.getElementById('bl-ui-panel-title');
const sub = document.getElementById('bl-ui-panel-sub');
const controls = document.getElementById('bl-ui-controls');
if (title) title.textContent = def.name;
if (sub) sub.style.display = 'none';
if (controls) controls.style.display = 'block';
const xSlider = document.getElementById('bl-ui-x');
const xVal = document.getElementById('bl-ui-x-val');
if (xSlider) xSlider.value = layout.x;
if (xVal) xVal.textContent = layout.x;
const ySlider = document.getElementById('bl-ui-y');
const yVal = document.getElementById('bl-ui-y-val');
if (ySlider) ySlider.value = layout.y;
if (yVal) yVal.textContent = layout.y;
const scaleSlider = document.getElementById('bl-ui-scale');
const scaleVal = document.getElementById('bl-ui-scale-val');
if (scaleSlider) scaleSlider.value = layout.scale;
if (scaleVal) scaleVal.textContent = layout.scale.toFixed(2);
const opSlider = document.getElementById('bl-ui-opacity');
const opVal = document.getElementById('bl-ui-opacity-val');
if (opSlider) opSlider.value = layout.opacity;
if (opVal) opVal.textContent = Math.round(layout.opacity*100) + '%';
const colorInp = document.getElementById('bl-ui-color');
const colorVal = document.getElementById('bl-ui-color-val');
if (colorInp) colorInp.value = layout.color;
if (colorVal) colorVal.textContent = layout.color;
}
function enterUIEditMode() {
const overlay = document.getElementById('bl-ui-editor');
if (overlay) overlay.classList.add('active');
const hint = document.getElementById('bl-ui-editor-hint');
if (hint) hint.classList.add('active');
Object.keys(UI_DEFS).forEach(id => {
const def = UI_DEFS[id];
const el = document.querySelector(def.selector);
if (!el) return;
const rect = el.getBoundingClientRect();
el.style.left = rect.left + 'px';
el.style.top = rect.top + 'px';
el.style.right = 'auto';
el.style.bottom = 'auto';
el.style.transform = 'none';
el.classList.add('bl-ui-editor-el');
el.dataset.uiId = id;
el.addEventListener('mousedown', onUIElementMouseDown);
});
const panel = document.getElementById('bl-ui-editor-panel');
if (panel) panel.classList.add('active');
selectUIElement('keystrokes');
}
function exitUIEditMode() {
const overlay = document.getElementById('bl-ui-editor');
if (overlay) overlay.classList.remove('active');
const hint = document.getElementById('bl-ui-editor-hint');
if (hint) hint.classList.remove('active');
const panel = document.getElementById('bl-ui-editor-panel');
if (panel) panel.classList.remove('active');
Object.keys(UI_DEFS).forEach(id => {
const def = UI_DEFS[id];
const el = document.querySelector(def.selector);
if (!el) return;
el.classList.remove('bl-ui-editor-el', 'bl-ui-selected');
el.removeEventListener('mousedown', onUIElementMouseDown);
delete el.dataset.uiId;
});
selectedUIElement = null;
isDraggingUI = false;
applyUILayout();
}
function onUIElementMouseDown(e) {
if (!e.currentTarget.dataset.uiId) return;
e.stopPropagation();
e.preventDefault();
selectUIElement(e.currentTarget.dataset.uiId);
isDraggingUI = true;
dragStartX = e.clientX;
dragStartY = e.clientY;
const rect = e.currentTarget.getBoundingClientRect();
dragElStartX = rect.left;
dragElStartY = rect.top;
on(document, 'mousemove', onUIElementMouseMove, { passive: true });
on(document, 'mouseup', onUIElementMouseUp, { passive: true });
}
function onUIElementMouseMove(e) {
if (!isDraggingUI || !selectedUIElement) return;
const el = document.querySelector(UI_DEFS[selectedUIElement].selector);
if (!el) return;
const dx = e.clientX - dragStartX;
const dy = e.clientY - dragStartY;
el.style.left = (dragElStartX + dx) + 'px';
el.style.top = (dragElStartY + dy) + 'px';
updateUILayoutFromElement(selectedUIElement, el);
const layout = config.uiLayout[selectedUIElement];
const xVal = document.getElementById('bl-ui-x-val');
const yVal = document.getElementById('bl-ui-y-val');
const xSlider = document.getElementById('bl-ui-x');
const ySlider = document.getElementById('bl-ui-y');
if (xVal) xVal.textContent = layout.x;
if (yVal) yVal.textContent = layout.y;
if (xSlider) xSlider.value = layout.x;
if (ySlider) ySlider.value = layout.y;
}
function onUIElementMouseUp() {
isDraggingUI = false;
off(document, 'mousemove', onUIElementMouseMove, { passive: true });
off(document, 'mouseup', onUIElementMouseUp, { passive: true });
saveConfig();
}
function updateUILayoutFromElement(id, el) {
const def = UI_DEFS[id];
const rect = el.getBoundingClientRect();
const layout = config.uiLayout[id];
const base = ANCHORS[def.anchor](rect.width, rect.height, id);
if (def.anchor === 'tr') {
layout.x = base.x - rect.left;
} else {
layout.x = rect.left - base.x;
}
layout.y = rect.top - base.y;
}
function selectUIElement(id) {
if (selectedUIElement) {
const prev = document.querySelector(UI_DEFS[selectedUIElement]?.selector);
if (prev) prev.classList.remove('bl-ui-selected');
}
selectedUIElement = id;
const el = document.querySelector(UI_DEFS[id].selector);
if (el) el.classList.add('bl-ui-selected');
refreshUIEditorPanel();
}
function updateUIElementEditPos(id) {
const def = UI_DEFS[id];
const el = document.querySelector(def.selector);
if (!el) return;
const layout = config.uiLayout[id];
const rect = el.getBoundingClientRect();
const base = ANCHORS[def.anchor](rect.width, rect.height, id);
if (def.anchor === 'tr') {
el.style.left = (base.x - layout.x) + 'px';
} else {
el.style.left = (base.x + layout.x) + 'px';
}
el.style.top = (base.y + layout.y) + 'px';
}
function applyUILayout() {
const l = config.uiLayout;
const ks = document.getElementById('bl-keystrokes');
if (ks) {
ks.style.transform = 'translate(' + l.keystrokes.x + 'px, ' + l.keystrokes.y + 'px) scale(' + l.keystrokes.scale + ')';
ks.style.opacity = l.keystrokes.opacity;
}
const fps = document.getElementById('bl-fps-counter');
if (fps) {
fps.style.transform = 'translate(' + l.fps.x + 'px, ' + l.fps.y + 'px) scale(' + l.fps.scale + ')';
fps.style.opacity = l.fps.opacity;
}
const cps = document.getElementById('bl-cps-counter');
if (cps) {
cps.style.transform = 'translate(' + l.cps.x + 'px, ' + l.cps.y + 'px) scale(' + l.cps.scale + ')';
cps.style.opacity = l.cps.opacity;
}
const sprint = document.getElementById('bl-sprint-indicator');
if (sprint) {
sprint.style.transform = 'translate(calc(-50% + ' + l.sprint.x + 'px), ' + l.sprint.y + 'px) scale(' + l.sprint.scale + ')';
sprint.style.opacity = l.sprint.opacity;
}
const hud = document.getElementById('bloxd-legacy-hud');
if (hud) {
hud.style.transform = 'translate(' + (-l.hud.x) + 'px, ' + l.hud.y + 'px) scale(' + l.hud.scale + ')';
hud.style.opacity = l.hud.opacity;
}
const ch = document.getElementById('bl-crosshair');
if (ch) {
ch.style.transform = 'translate(calc(-50% + ' + l.crosshair.x + 'px), calc(-50% + ' + l.crosshair.y + 'px)) scale(' + l.crosshair.scale + ')';
ch.style.opacity = l.crosshair.opacity;
}
applyUIColors();
}
function applyUIColors() {
const l = config.uiLayout;
let css = '';
css += '#bl-keystrokes .ks-key { border-color: ' + l.keystrokes.color + '33 !important; color: ' + l.keystrokes.color + ' !important; }';
css += '#bl-keystrokes .ks-key.pressed { background: ' + l.keystrokes.color + ' !important; border-color: ' + l.keystrokes.color + ' !important; color: #fff !important; }';
css += '#bl-fps-counter .bl-counter-val { color: ' + l.fps.color + ' !important; }';
css += '#bl-fps-counter .bl-counter-label { color: ' + l.fps.color + 'aa !important; }';
css += '#bl-cps-counter .bl-counter-val { color: ' + l.cps.color + ' !important; }';
css += '#bl-cps-counter .bl-counter-label { color: ' + l.cps.color + 'aa !important; }';
css += '#bl-sprint-indicator { color: ' + l.sprint.color + ' !important; }';
css += '#bloxd-legacy-hud { color: ' + l.hud.color + ' !important; }';
css += '#bloxd-legacy-hud::before { background: ' + l.hud.color + ' !important; box-shadow: 0 0 8px ' + l.hud.color + ' !important; }';
css += '#bl-crosshair.bl-crosshair-default::before, #bl-crosshair.bl-crosshair-default::after { background: ' + l.crosshair.color + ' !important; }';
css += '#bl-crosshair.bl-crosshair-circle::before { border-color: ' + l.crosshair.color + ' !important; }';
css += '#bl-crosshair.bl-crosshair-dot::before { background: ' + l.crosshair.color + ' !important; }';
css += '#bl-crosshair.bl-crosshair-cross::before, #bl-crosshair.bl-crosshair-cross::after { background: ' + l.crosshair.color + ' !important; }';
let style = document.getElementById('bl-ui-custom-colors');
if (!style) {
style = document.createElement('style');
style.id = 'bl-ui-custom-colors';
document.head.appendChild(style);
}
style.textContent = css;
}
/* Menu Control */
function switchTab(tab) {
currentTab = tab;
$all('.bl-tab').forEach(b => b.classList.toggle('active', b.dataset.tab === tab));
$all('.bl-panel').forEach(p => p.classList.toggle('active', p.dataset.panel === tab));
}
function openMenu() {
menuOpen = true;
overlayEl.classList.add('active');
setTimeout(() => overlayEl.focus(), 30);
}
function closeMenu() {
menuOpen = false;
overlayEl.classList.remove('active');
}
function toggleMenu() {
menuOpen ? closeMenu() : openMenu();
}
/* Input Handling */
on(document, 'keydown', function(e) {
if (e.code === 'ShiftRight') {
e.preventDefault();
e.stopImmediatePropagation();
toggleMenu();
return;
}
if (menuOpen && e.code === 'Escape') {
e.preventDefault();
e.stopImmediatePropagation();
closeMenu();
return;
}
if (e.code === 'ShiftLeft' || e.code === 'ShiftRight') {
manualShift = true;
}
if (config.combat.toggleSprint && e.code === 'KeyW' && !e.repeat && !manualShift && !autoShift) {
autoShift = true;
const shiftEvent = new KeyboardEvent('keydown', {
key: 'Shift',
code: 'ShiftLeft',
shiftKey: true,
bubbles: true,
cancelable: true
});
document.dispatchEvent(shiftEvent);
const ind = document.getElementById('bl-sprint-indicator');
if (ind) ind.classList.add('active');
}
const uiEditor = document.getElementById('bl-ui-editor');
if (uiEditor && uiEditor.classList.contains('active') && e.code === 'Escape') {
e.preventDefault();
e.stopImmediatePropagation();
exitUIEditMode();
return;
}
if (menuOpen) {
const isSystem = e.ctrlKey || e.metaKey || e.altKey || (e.key && e.key.startsWith('F'));
if (!isSystem) {
e.stopImmediatePropagation();
}
}
}, true);
on(document, 'keyup', function(e) {
if (e.code === 'ShiftLeft' || e.code === 'ShiftRight') {
manualShift = false;
}
if (config.combat.toggleSprint && e.code === 'KeyW' && autoShift && !manualShift) {
autoShift = false;
const shiftEvent = new KeyboardEvent('keyup', {
key: 'Shift',
code: 'ShiftLeft',
shiftKey: false,
bubbles: true,
cancelable: true
});
document.dispatchEvent(shiftEvent);
const ind = document.getElementById('bl-sprint-indicator');
if (ind) ind.classList.remove('active');
}
if (menuOpen) {
const isSystem = e.ctrlKey || e.metaKey || e.altKey || (e.key && e.key.startsWith('F'));
if (!isSystem) {
e.stopImmediatePropagation();
}
}
}, true);
/* INIT Config */
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', buildUI);
} else {
buildUI();
}
})();