Plus Sploop.io helper: Features HUD (FPS/Ping), transparent UI, smooth health, ad-block, tracers, built-in VPN/Proxy list selector, and background music player.
// ==UserScript==
// @name Sploop.io Plus [V2.2]
// @namespace http://tampermonkey.net/
// @version 2.2.2
// @description Plus Sploop.io helper: Features HUD (FPS/Ping), transparent UI, smooth health, ad-block, tracers, built-in VPN/Proxy list selector, and background music player.
// @match *://sploop.io/*
// @icon https://i.postimg.cc/vBz07fcS/Screenshot-2025-08-28-090152.png
// @grant none
// @author Normalplayer + Hori & viper + Fizzixww
// @license MIT
// @run-at document-start
// ==/UserScript==
(function () {
'use strict';
const STORAGE_KEY = 'sploop_legit_settings_v6';
const config = {
ghostMode: false,
ghostOpacityTree: 0.3,
ghostOpacityCherryTree: 0.3,
ghostOpacityPalmTree: 0.3,
ghostOpacityWoodFarm: 0.3,
ghostOpacityWoodFarmCherry: 0.3,
ghostOpacityRock: 0.3,
ghostOpacityStoneFarm: 0.3,
ghostOpacityCaveStone0: 0.3,
ghostOpacityCaveStone1: 0.3,
ghostOpacityCaveStone2: 0.3,
ghostOpacityBush: 0.3,
ghostOpacityBerryFarm: 0.3,
ghostOpacityCactus: 0.3,
ghostOpacityGold: 0.3,
ghostOpacityRuby: 0.3,
ghostOpacityLootbox: 0.3,
ghostOpacityChest: 0.3,
ghostOpacityWall: 0.3,
ghostOpacityCastleWall: 0.3,
ghostOpacityPlatform: 0.3,
ghostOpacityRoof: 0.3,
ghostOpacitySpike: 0.3,
ghostOpacityHardSpike: 0.3,
ghostOpacityIceSpike: 0.3,
ghostOpacityBigSpike: 0.3,
ghostOpacityWindmill: 0.3,
ghostOpacityTurret: 0.3,
ghostOpacityTrap: 0.3,
ghostOpacityBoost: 0.3,
ghostOpacityHealpad: 0.3,
ghostOpacityBed: 0.3,
ghostOpacityTeleporter: 0.3,
ghostOpacityTornado: 0.3,
ghostOpacityFireball: 0.3,
ghostOpacityIce0: 0.3,
ghostOpacityIce1: 0.3,
hitbox: true,
hitboxColorPlayer: "#ff0000",
hitboxColorMob: "#e09f3e",
hitboxColorBlock: "#00cfff",
hitboxWidth: 2,
tracers: false,
tracerWidth: 1.5,
tracerOpacity: 0.7,
tracerColorEnemy: "#cc5151",
tracerColorAlly: "#a4cc4f",
tracerColorMob: "#e09f3e",
tracerPlayer: true,
tracerAlly: true,
tracerCow: true,
tracerDuck: true,
tracerWolf: true,
tracerShark: true,
tracerCrocodile: true,
tracerGcow: true,
tracerMammoth: true,
tracerDragon: true,
betterHealthBar: true,
coloredHealthBar: true,
smoothHealthBar: true,
showPercentHealth: true,
hpColorAllyHigh: "#a4cc4f",
hpColorAllyMed: "#e09f3e",
hpColorAllyLow: "#cc5151",
hpColorEnemyHigh: "#cc5151",
hpColorEnemyMed: "#e09f3e",
hpColorEnemyLow: "#a4cc4f",
transparentUI: true,
showOverlay: true,
removeAds: true,
itemCounter: true,
lightMode: false,
keyViewer: true,
statsLocked: false,
statsShowServer: true,
statsShowFps: true,
statsShowCps: true,
statsShowPing: true,
kvLocked: false,
overlayBgOpacity: 0.75,
overlayBgColor: "#0a0a0a",
overlayTextColor: "#ffffff",
overlayFontSize: 17,
kvBgOpacity: 0.75,
kvBgColor: "#0a0a0a",
kvTextColor: "#e5e7eb",
kvCellSize: 46,
musicEnabled: false,
musicPlaylist: [],
musicCurrentIndex: -1,
musicVolume: 0.5
};
try {
const saved = localStorage.getItem(STORAGE_KEY);
if (saved) {
const parsed = JSON.parse(saved);
if (parsed.ghostOpacityBuilding !== undefined) {
const legacy = parsed.ghostOpacityBuilding;
for (const k of ["ghostOpacityWindmill","ghostOpacityTurret","ghostOpacityTrap",
"ghostOpacityBoost","ghostOpacityHealpad","ghostOpacityBed","ghostOpacityTeleporter"]) {
if (parsed[k] === undefined) parsed[k] = legacy;
}
delete parsed.ghostOpacityBuilding;
}
const clusterFanOut = {
ghostOpacityTree: ["ghostOpacityCherryTree","ghostOpacityPalmTree","ghostOpacityWoodFarm","ghostOpacityWoodFarmCherry"],
ghostOpacityStone: ["ghostOpacityRock","ghostOpacityStoneFarm","ghostOpacityCaveStone0","ghostOpacityCaveStone1","ghostOpacityCaveStone2"],
ghostOpacityBush: ["ghostOpacityBerryFarm","ghostOpacityCactus"],
ghostOpacityGold: ["ghostOpacityRuby","ghostOpacityLootbox","ghostOpacityChest"],
ghostOpacityWall: ["ghostOpacityCastleWall","ghostOpacityPlatform","ghostOpacityRoof"],
ghostOpacitySpike: ["ghostOpacityHardSpike","ghostOpacityIceSpike","ghostOpacityBigSpike"],
ghostOpacityMisc: ["ghostOpacityTornado","ghostOpacityFireball","ghostOpacityIce0","ghostOpacityIce1"]
};
for (const [oldKey, newKeys] of Object.entries(clusterFanOut)) {
if (parsed[oldKey] !== undefined) {
for (const nk of newKeys) {
if (parsed[nk] === undefined) parsed[nk] = parsed[oldKey];
}
}
}
delete parsed.ghostOpacityStone;
Object.assign(config, parsed);
}
} catch (e) {
console.error('[LegitScript] Config load error:', e);
}
function saveConfig() {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(config));
} catch (e) {
console.error('[LegitScript] Config save error:', e);
}
}
const circlesToDraw = [];
const max = [0, 0, 0, 100, 30, 8, 2, 12, 32, 1, 2];
window.drawItemBar = (ctx, imageData, index) => {
if (!config.itemCounter || !window.stats || !Sploop) return;
try {
const weaponType = window.weapons[window.stats[Sploop.itemsID][index]][Sploop.weaponID2];
const limit = max[weaponType];
const crntCount = window.stats[Sploop.objCount][weaponType];
if (!limit) return;
const text = `${crntCount}/${limit}`;
ctx.save();
ctx.font = "900 19px 'Baloo Paaji'";
ctx.fillStyle = "white";
ctx.strokeStyle = "#330000";
ctx.lineWidth = 3;
ctx.strokeText(text, imageData[Sploop.x] + imageData.width - ctx.measureText(text).width - 10, imageData[Sploop.y] + 25);
ctx.fillText(text, imageData[Sploop.x] + imageData.width - ctx.measureText(text).width - 10, imageData[Sploop.y] + 25);
ctx.restore();
} catch (e) {}
};
const TYPEOF = (value) => Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
const NumberSystem = [
{ radix: 2, prefix: "0b0*" }, { radix: 8, prefix: "0+" },
{ radix: 10, prefix: "" }, { prefix: "", radix: 16 }
];
class Regex {
constructor(code, unicode, namespace) {
if (!namespace) namespace = "new_script_" + Math.random().toString(36).substr(2, 8);
this.code = code;
this.COPY_CODE = code;
this.unicode = unicode || false;
this.hooks = {};
this.namespace = namespace;
this.totalHooks = 0;
}
static parseValue(value) {
try { return Function(`return (${value})`)(); } catch { return null; }
}
isRegexp(value) { return TYPEOF(value) === "regexp"; }
generateNumberSystem(int) {
const template = NumberSystem.map(({ prefix, radix }) => prefix + int.toString(radix));
return `(?:${template.join("|")})`;
}
parseVariables(regex) {
regex = regex
.replace(/\{VAR\}/g, "(?:let|var|const)")
.replace(/\{QUOTE\}/g, "['\"`]")
.replace(/ARGS\{(\d+)\}/g, (_, n) => {
let count = Number(n), arr = [];
while (count--) arr.push("\\w+");
return arr.join("\\s*,\\s*");
})
.replace(/NUMBER\{(\d+)\}/g, (_, n) => this.generateNumberSystem(Number(n)));
return regex;
}
_hookName(name) { return `${this.namespace}:${name}`; }
format(name, inputRegex, flags) {
this.totalHooks++;
let regex = "";
if (Array.isArray(inputRegex)) {
regex = inputRegex.map(exp => this.isRegexp(exp) ? exp.source : exp).join("\\s*");
} else if (this.isRegexp(inputRegex)) {
regex = inputRegex.source;
} else {
regex = inputRegex;
}
regex = this.parseVariables(regex);
if (this.unicode) regex = regex.replace(/\\w/g, "(?:[^\\x00-\\x7F-]|\\$|\\w)");
const hasInsert = regex.includes("{INSERT}");
const cleanRegex = regex.replace(/\{INSERT\}/, "");
return hasInsert ? new RegExp(regex, flags) : new RegExp(cleanRegex, flags);
}
template(type, name, regex, substr) {
const hookName = this._hookName(name);
const expression = new RegExp(`(${this.format(hookName, regex).source})`);
const match = this.code.match(expression) || [];
this.code = this.code.replace(expression, type === 0 ? "$1" + substr : substr + "$1");
this.hooks[hookName] = { expression, match };
return match;
}
insert(name, regex, substr) {
const { source } = this.format(name, regex);
if (!source.includes("{INSERT}")) throw new Error("Your regexp must contain {INSERT} keyword");
const findExpression = new RegExp(source.replace(/^(.*)\{INSERT\}(.*)$/, "($1)($2)"));
this.code = this.code.replace(findExpression, `$1${substr}$2`);
return this.code.match(findExpression);
}
logHooks() {
console.log("%cApplied Hooks:", "font-weight: bold; font-size: 24px; color:#fff; margin-top: 20px;");
let index = 1;
for (const hookName in this.hooks) {
const hook = this.hooks[hookName];
const matches = Array.isArray(hook.match) ? hook.match : [hook.match];
const ok = matches && matches.length > 0;
console.log(
`%c${index}. ${hookName} | ${ok ? "successful" : "unsuccessful"}`,
ok ? "color:rgb(134,204,125);font-size:14px;" : "color:rgb(221,94,113);font-size:14px;"
);
index++;
}
}
match(name, regex, flags) {
const hookName = this._hookName(name);
const expression = this.format(hookName, regex, flags);
const match = this.code.match(expression) || [];
this.hooks[hookName] = { expression, match };
return match;
}
matchAll(name, regex) {
const hookName = this._hookName(name);
const expression = this.format(hookName, regex, "g");
const matches = [...this.code.matchAll(expression)];
this.hooks[hookName] = { expression, match: matches };
return matches;
}
replace(name, regex, substr, flags) {
const hookName = this._hookName(name);
const expression = this.format(hookName, regex, flags);
const preMatch = this.code.match(expression) || [];
this.code = this.code.replace(expression, substr);
this.hooks[hookName] = { expression, match: preMatch, replaced: preMatch.length > 0 };
return this.code.match(expression) || [];
}
append(name, regex, substr) { return this.template(0, name, regex, substr); }
prepend(name, regex, substr) { return this.template(1, name, regex, substr); }
}
let Sploop = {};
const applyHooks = (code) => {
try {
const Hook = new Regex(code, true);
window.COPY_CODE = (Hook.COPY_CODE.match(/^(\(function \w+\(\w+\)\{.+)\(.+?\);$/) || [])[1];
if (window.COPY_CODE) {
Hook.append("EXTERNAL fix", /\(function (\w+)\(\w+\)\{/, "let $2 = eval(`(() => ${COPY_CODE})()`);delete window.COPY_CODE;");
}
const map = Hook.match("objects", /let \w=(\w).get\(\w\)/)[1];
const X = Hook.match("playerX", /\{this\.(\w{2})=\w\|\|0/)[1];
const Y = Hook.match("playerY", /,this\.(\w{2})=\w\|\|0\}/)[1];
const weaponID2Matches = Hook.matchAll("el", /,(\w+):9,\w+:2/);
const weaponID2 = weaponID2Matches.length > 1 ? weaponID2Matches[1][1] : (weaponID2Matches[0] ? weaponID2Matches[0][1] : null);
const itemsID = Hook.match("IDs", />1\)\{.{3}(\w{2})/)[1];
const objCount = Hook.match("objCount", /\),this.(\w{2})=\w\):/)[1];
const itemBarMatch = Hook.match("defaultData1", /(\W\w+>NUMBER{1}\W.+?(\w+)\.(\w+).+?)function/);
const itemBar = itemBarMatch ? itemBarMatch[3] : null;
Sploop = { x: X, y: Y, map, itemsID, itemBar, objCount, weaponID2 };
const weaponList = Hook.match("weaponList", /\?Math\.PI\/2.+?(\w\(\))/)[1];
Hook.replace("defaultData", /(\W\w+>NUMBER{1}\W.+?(\w+)\.(\w+).+?)function/,
`$1window.stats=$2;window.weapons=${weaponList};window.sprites=tt();function`);
const argsMatch = Hook.match("drawEntityInfo", /\.\w+\),\w(\.restore|\w\(\d+\))\(\)}function \w+\((ARGS{3})\){/);
if (argsMatch && argsMatch[2]) {
let args = argsMatch[2];
Hook.append("drawEntityInfo2",
/\),\w\.drawImage\(\w,\w\.\w{2}-\w\*.{7,9}\/2.\w\.\w{2}-\w\*.{8,9}\+\w,\w\*.{7,9},\w\*.{9}\)/,
`;try{window.getEntityData(${args},${Sploop.map});}catch(e){};`);
}
Hook.append("itemCounter",
/\w\.drawImage\(\w,\.5\*\w{2}\-\.5\*.{7,9},\w\.\w{2}-.{8,9}\);for\(let \w,\w=0,\w=\w{2}\.\w{2};\w\<.{7,9};\w\+\+\)if\(\w=\w\[(\w)\],(\w)\.\w{2}\((\w)\),/,
`window.drawItemBar($4,$3,$2),`);
return Hook.code;
} catch (e) {
console.error("[LegitScript] Critical Hook Error. Game will load without item hooks to prevent crashing.", e);
return code;
}
};
window.eval = new Proxy(window.eval, {
apply(target, _this, args) {
const code = args[0];
if (typeof code === 'string' && code.length > 1e5) {
args[0] = applyHooks(code);
window.eval = target;
target.apply(_this, args);
return;
}
return target.apply(_this, args);
}
});
function lerpColor(a, b, amount) {
const ah = parseInt(a.replace(/#/g, ''), 16), ar = ah >> 16, ag = (ah >> 8) & 0xff, ab = ah & 0xff;
const bh = parseInt(b.replace(/#/g, ''), 16), br = bh >> 16, bg = (bh >> 8) & 0xff, bb = bh & 0xff;
const rr = ar + amount * (br - ar), rg = ag + amount * (bg - ag), rb = ab + amount * (bb - ab);
return '#' + (((1 << 24) + (rr << 16) + (rg << 8) + rb) | 0).toString(16).slice(1);
}
function drawHpText(ctx, text, xPos, yPos, color) {
ctx.save();
ctx.font = "900 20px 'Baloo Paaji'";
ctx.textAlign = "center";
ctx.textBaseline = "top";
ctx.lineJoin = "round";
ctx.lineWidth = 8;
ctx.strokeStyle = "#313131";
ctx.strokeText(text, xPos, yPos);
ctx.fillStyle = color;
ctx.fillText(text, xPos, yPos);
ctx.restore();
}
const hpTracker = [];
const enhanceFillRect = function (originalFillRect) {
return function (x, y, width, height) {
const fullWidth = 95;
const isHpBar = height > 5 && height < 20 && (this.fillStyle === "#a4cc4f" || this.fillStyle === "#cc5151");
let renderWidth = width;
let hpPercent = Math.max(0, Math.min(1, width / fullWidth));
let percentText = `${Math.round(hpPercent * 100)}%`;
if (isHpBar && this.canvas && this.canvas.id === "game-canvas") {
try {
const matrix = this.getTransform();
const screenX = matrix.a * (x + fullWidth / 2) + matrix.c * y + matrix.e;
const screenY = matrix.b * (x + fullWidth / 2) + matrix.d * y + matrix.f;
const now = Date.now();
window._frameHpBars = window._frameHpBars || [];
const barIsMob = width <= 45;
window._frameHpBars.push({ x: screenX, y: screenY, isAlly: this.fillStyle === "#a4cc4f", isMob: barIsMob, time: now });
if (config.betterHealthBar && config.smoothHealthBar) {
let matchedBar = null;
let minDist = 40;
for (let i = 0; i < hpTracker.length; i++) {
const bar = hpTracker[i];
const dist = Math.hypot(bar.x - screenX, bar.y - screenY);
if (dist < minDist) {
minDist = dist;
matchedBar = bar;
}
}
if (matchedBar) {
matchedBar.x = screenX;
matchedBar.y = screenY;
matchedBar.targetWidth = width;
matchedBar.lastSeen = now;
matchedBar.currentWidth += (matchedBar.targetWidth - matchedBar.currentWidth) * 0.15;
if (Math.abs(matchedBar.targetWidth - matchedBar.currentWidth) < 0.5) {
matchedBar.currentWidth = matchedBar.targetWidth;
}
} else {
matchedBar = { x: screenX, y: screenY, targetWidth: width, currentWidth: width, lastSeen: now };
hpTracker.push(matchedBar);
}
if (Math.random() < 0.05) {
for (let i = hpTracker.length - 1; i >= 0; i--) {
if (now - hpTracker[i].lastSeen > 200) hpTracker.splice(i, 1);
}
}
renderWidth = matchedBar.currentWidth;
hpPercent = Math.max(0, Math.min(1, renderWidth / fullWidth));
percentText = `${Math.round(hpPercent * 100)}%`;
}
} catch(e) {}
}
if (!config.betterHealthBar) return originalFillRect.call(this, x, y, width, height);
if (isHpBar) {
const centerX = x + fullWidth / 2;
let baseColor = this.fillStyle === "#a4cc4f" ? config.hpColorAllyHigh : config.hpColorEnemyHigh;
let color = baseColor;
if (config.coloredHealthBar) {
if (this.fillStyle === "#a4cc4f") {
color = hpPercent > 0.5 ?
lerpColor(config.hpColorAllyHigh, config.hpColorAllyMed, (1 - hpPercent) * 2) :
lerpColor(config.hpColorAllyMed, config.hpColorAllyLow, (0.5 - hpPercent) * 2);
} else if (this.fillStyle === "#cc5151") {
color = hpPercent > 0.5 ?
lerpColor(config.hpColorEnemyHigh, config.hpColorEnemyMed, (1 - hpPercent) * 2) :
lerpColor(config.hpColorEnemyMed, config.hpColorEnemyLow, (0.5 - hpPercent) * 2);
}
}
this.fillStyle = color;
originalFillRect.call(this, x, y, renderWidth, height);
if (config.showPercentHealth) {
drawHpText(this, percentText, centerX, y + height + 7, color);
}
return;
}
originalFillRect.call(this, x, y, width, height);
};
};
CanvasRenderingContext2D.prototype.fillRect = enhanceFillRect(CanvasRenderingContext2D.prototype.fillRect);
const imageRadii = new Map([
["tree.png",90],["cherry_tree.png",90],["palm_tree.png",90],
["wood_farm.png",80],["wood_farm_cherry.png",80],
["rock.png",75],["stone_farm.png",75],
["bush.png",50],["berry_farm.png",50],["cactus.png",50],
["gold.png",76],["ruby.png",100],["tornado.png",220],
["cave_stone0.png",92],["cave_stone1.png",92],["cave_stone2.png",58],
["fireball.png",100],["ice0.png",92],["ice1.png",20],["chest.png",40],
["wall.png",45],["castle_wall.png",59],["spike.png",45],
["hard_spike.png",45],["ice_spike.png",45],["big_spike.png",45],
["windmill_base.png",45],["windmill.png",0],["windmill_top.png",0],
["windmill_blade.png",0],["windmill_fan.png",0],
["trap.png",40],["boost.png",40],
["turret_base.png",45],["turret_top.png",0],["turret_gun.png",0],
["heal_pad.png",50],["platform.png",60],
["roof.png",50],["bed.png",50],["teleporter.png",35],["lootbox.png",40],
["wolf.png",50],["duck.png",20],["cow.png",90],["shark.png",90],
["mammoth_body.png",90],["dragon_2_body.png",100],["gcow.png",90],["crocodile.png",90]
]);
const resourceKeywords = ["tree","rock","bush","cactus","ruby","wood","stone","gold",
"wall","spike","windmill","trap","boost","turret","heal_pad","platform",
"roof","bed","teleporter","lootbox","tornado","inv_","ice","cave_stone"];
const ghostFileConfigMap = new Map([
["tree.png","ghostOpacityTree"],
["cherry_tree.png","ghostOpacityCherryTree"],
["palm_tree.png","ghostOpacityPalmTree"],
["wood_farm.png","ghostOpacityWoodFarm"],
["wood_farm_cherry.png","ghostOpacityWoodFarmCherry"],
["rock.png","ghostOpacityRock"],
["stone_farm.png","ghostOpacityStoneFarm"],
["cave_stone0.png","ghostOpacityCaveStone0"],
["cave_stone1.png","ghostOpacityCaveStone1"],
["cave_stone2.png","ghostOpacityCaveStone2"],
["bush.png","ghostOpacityBush"],
["berry_farm.png","ghostOpacityBerryFarm"],
["cactus.png","ghostOpacityCactus"],
["gold.png","ghostOpacityGold"],
["ruby.png","ghostOpacityRuby"],
["lootbox.png","ghostOpacityLootbox"],
["chest.png","ghostOpacityChest"],
["wall.png","ghostOpacityWall"],
["castle_wall.png","ghostOpacityCastleWall"],
["platform.png","ghostOpacityPlatform"],
["roof.png","ghostOpacityRoof"],
["spike.png","ghostOpacitySpike"],
["hard_spike.png","ghostOpacityHardSpike"],
["ice_spike.png","ghostOpacityIceSpike"],
["big_spike.png","ghostOpacityBigSpike"],
["tornado.png","ghostOpacityTornado"],
["fireball.png","ghostOpacityFireball"],
["ice0.png","ghostOpacityIce0"],
["ice1.png","ghostOpacityIce1"]
]);
const skinFragments = new Set();
for (let i = 0; i <= 105; i++) skinFragments.add(`body${i}.png`);
skinFragments.add('45body.png');
skinFragments.add('78body.png');
const origDrawImage = CanvasRenderingContext2D.prototype.drawImage;
CanvasRenderingContext2D.prototype.drawImage = function (img, ...rest) {
if (!img || !img.src) return origDrawImage.apply(this, arguments);
if (img._isProcessed === undefined) {
img._isProcessed = true;
const src = img.src;
const fileName = src.substring(src.lastIndexOf('/') + 1).split('?')[0];
img._radius = imageRadii.get(fileName) || 0;
img._isPlayer = skinFragments.has(fileName);
img._isMob = ["wolf.png","duck.png","cow.png","shark.png","mammoth_body.png","dragon_2_body.png","gcow.png","crocodile.png"].includes(fileName);
img._fileName = fileName;
img._ghostGroup = ghostFileConfigMap.get(fileName) || null;
if (img._ghostGroup === null) {
if (fileName.includes("windmill")) img._ghostGroup = "ghostOpacityWindmill";
else if (fileName.includes("turret")) img._ghostGroup = "ghostOpacityTurret";
else if (fileName.includes("trap")) img._ghostGroup = "ghostOpacityTrap";
else if (fileName.includes("boost")) img._ghostGroup = "ghostOpacityBoost";
else if (fileName.includes("heal_pad")) img._ghostGroup = "ghostOpacityHealpad";
else if (fileName.includes("bed")) img._ghostGroup = "ghostOpacityBed";
else if (fileName.includes("teleporter")) img._ghostGroup = "ghostOpacityTeleporter";
}
}
const drawImg = img;
let isUiDraw = false;
if (config.ghostMode && img._ghostGroup !== null && img._ghostGroup !== undefined) {
const src = img.src || "";
if (src.includes("inv_") || src.includes("ui_")) {
isUiDraw = true;
} else if (rest.length >= 4 && this.canvas) {
const [gx, gy] = rest;
const mh0 = this.canvas.height;
const mw0 = this.canvas.width;
const inHotbarZone = gy > mh0 * 0.8 && gx > mw0 * 0.25 && gx < mw0 * 0.75;
const inChooseZone = gy < mh0 * 0.2 && gx > mw0 * 0.2 && gx < mw0 * 0.8;
isUiDraw = inHotbarZone || inChooseZone;
}
}
const shouldGhost = config.ghostMode && img._ghostGroup !== null && img._ghostGroup !== undefined && !isUiDraw;
if (shouldGhost) {
this.save();
this.globalAlpha = config[img._ghostGroup] ?? 0.3;
}
origDrawImage.call(this, drawImg, ...rest);
if (shouldGhost) this.restore();
if (this.canvas && this.canvas.id === "game-canvas") {
const mh = this.canvas.height;
const mw = this.canvas.width;
if (config.hitbox && img._radius > 0 && rest.length >= 4 && !img._isPlayer && !img._isMob) {
const [x, y, w, h] = rest;
const src = img.src;
const isWorldEntity = (img._ghostGroup || src.includes('/entity/')) && !src.includes('inv_') && !src.includes('ui_');
const isHotbarZone = y > mh * 0.8 && x > mw * 0.25 && x < mw * 0.75;
const isChooseZone = y < mh * 0.2 && x > mw * 0.2 && x < mw * 0.8;
if (!((isHotbarZone || isChooseZone) && !isWorldEntity)) {
try {
if (img._fileName === "windmill_base.png") {
const mat = this.getTransform();
circlesToDraw.push({ x, y, width: w, height: h, radius: img._radius, transform: mat, isBlock: true });
} else {
this.beginPath();
this.arc(x + w / 2, y + h / 2, img._radius, 0, Math.PI * 2);
this.lineWidth = config.hitboxWidth;
this.strokeStyle = config.hitboxColorBlock;
this.stroke();
}
} catch(e){}
}
}
if ((config.hitbox || config.tracers) && (img._isPlayer || img._isMob) && rest.length >= 4) {
const [x, y, w, h] = rest;
try {
const mat = this.getTransform();
circlesToDraw.push({ x, y, width: w, height: h, radius: img._radius, transform: mat, isPlayer: img._isPlayer, isMob: img._isMob, fileName: img._fileName });
if (img._isMob) {
const lx = x + w / 2, ly = y + h / 2;
const sx = mat.a * lx + mat.c * ly + mat.e;
const sy = mat.b * lx + mat.d * ly + mat.f;
window._lastMobScreenPositions = window._lastMobScreenPositions || [];
window._lastMobScreenPositions.push({ x: sx, y: sy, time: Date.now() });
}
} catch(e){}
}
}
};
window.addEventListener('DOMContentLoaded', () => {
const CSS_ID = 'sploop-legit-css';
const ADS_CSS_ID = 'sploop-adblock-css';
function resetMenuStyles() {
const hatMenu = document.getElementById('hat-menu');
if (hatMenu) {
['zoom','position','transform','margin','left','top'].forEach(p => hatMenu.style.removeProperty(p));
hatMenu._legitPatched = false;
Array.from(hatMenu.children).forEach(child => {
if (child.id === 'hat_menu_content') return;
['display','position','left','right','top','transform','margin','visibility','opacity'].forEach(p => child.style.removeProperty(p));
child.childNodes.forEach(node => {
if (node.nodeType === 1) {
const t = node.textContent.trim().toUpperCase();
if (t === 'HATS') node.style.removeProperty('display');
}
});
});
}
const clanMenu = document.getElementById('clan-menu');
if (clanMenu) {
clanMenu._legitPatched = false;
Array.from(clanMenu.children).forEach(child => {
if (child.id === 'clan_menu_content') return;
['display','visibility','opacity','position','left','right','top','transform','margin'].forEach(p => child.style.removeProperty(p));
});
}
}
function updateCSS() {
const existing = document.getElementById(CSS_ID);
if (existing) existing.remove();
let adStyle = document.getElementById(ADS_CSS_ID);
if (config.removeAds) {
if (!adStyle) {
adStyle = document.createElement('style');
adStyle.id = ADS_CSS_ID;
adStyle.innerHTML =
`
#cross-promo,#bottom-wrap,#google_play,
#game-left-content-main,#game-bottom-content,
#game-right-content-main,#left-content,#right-content{display:none!important;}
#game-content{justify-content:center!important;}
#main-content{width:auto!important;}
`;
document.head.appendChild(adStyle);
}
} else {
if (adStyle) adStyle.remove();
}
if (!config.transparentUI) {
resetMenuStyles();
return;
}
const style = document.createElement('style');
style.id = CSS_ID;
style.innerHTML = `
/* ===== HAT SHOP MENU ===== */
#hat-menu {
background: transparent !important;
box-shadow: none !important;
border: none !important;
opacity: 0.55 !important;
transition: opacity 0.25s !important;
}
#hat-menu:hover { opacity: 1 !important; }
#hat_menu_content, #hat_menu_content * {
background: transparent !important;
box-shadow: none !important;
border: none !important;
outline: none !important;
}
#hat_menu_content {
scrollbar-width: none !important;
-ms-overflow-style: none !important;
max-height: 9999px !important;
height: auto !important;
overflow: visible !important;
}
#hat_menu_content::-webkit-scrollbar { display: none !important; }
#hat-menu .hat-shop-title,
#hat-menu .shop-title,
#hat-menu .menu-title {
display: none !important;
}
#clan-menu {
background: transparent !important;
box-shadow: none !important;
opacity: 0.55 !important;
transition: opacity 0.25s !important;
}
#clan-menu:hover { opacity: 1 !important; }
#clan_menu_content, #clan_menu_content * {
background: transparent !important;
box-shadow: none !important;
}
#clan_menu_content {
scrollbar-width: none !important;
-ms-overflow-style: none !important;
}
#clan_menu_content::-webkit-scrollbar { display: none !important; }
#clan_menu_content > div,
#clan_menu_content > li,
#clan_menu_content > p {
margin-bottom: 14px !important;
padding-top: 5px !important;
padding-bottom: 5px !important;
}
#clan-menu input,
#clan-menu textarea,
#clan-menu [placeholder],
#create-clan-button {
background: transparent !important;
box-shadow: none !important;
}
`;
document.head.appendChild(style);
}
const _legitMenuObserver = new MutationObserver(() => {
if (!config.transparentUI) return;
const hatMenu = document.getElementById('hat-menu');
if (hatMenu) {
hatMenu.style.setProperty('zoom', '0.78', 'important');
hatMenu.style.setProperty('position', 'fixed', 'important');
hatMenu.style.setProperty('transform', 'none', 'important');
hatMenu.style.setProperty('margin', '0', 'important');
const rect = hatMenu.getBoundingClientRect();
const vw = window.innerWidth;
const vh = window.innerHeight;
const HAT_OFFSET_X = 200;
const HAT_OFFSET_Y = -75;
const CLOSE_OFFSET_X = 243;
const CLOSE_OFFSET_Y = 6;
const newLeft = Math.round((vw - rect.width) / 2) + HAT_OFFSET_X;
const newTop = Math.round((vh - rect.height) / 2) + HAT_OFFSET_Y;
hatMenu.style.setProperty('left', newLeft + 'px', 'important');
hatMenu.style.setProperty('top', newTop + 'px', 'important');
if (!hatMenu._legitPatched) {
hatMenu._legitPatched = true;
Array.from(hatMenu.children).forEach(child => {
if (child.id === 'hat_menu_content') return;
child.childNodes.forEach(node => {
if (node.nodeType === 3) {
node.textContent = '';
} else if (node.nodeType === 1) {
const t = node.textContent.trim().toUpperCase();
if (t === 'HATS') {
node.style.setProperty('display', 'none', 'important');
}
}
});
const menuW = hatMenu.offsetWidth;
const childW = child.offsetWidth || 30;
const closePx = Math.round((menuW - childW) / 2) + CLOSE_OFFSET_X;
child.style.setProperty('display', 'flex', 'important');
child.style.setProperty('position', 'absolute', 'important');
child.style.setProperty('left', closePx + 'px', 'important');
child.style.setProperty('right', 'auto', 'important');
child.style.setProperty('top', CLOSE_OFFSET_Y + 'px', 'important');
child.style.setProperty('transform', 'none', 'important');
child.style.setProperty('margin', '0', 'important');
child.style.setProperty('visibility','visible', 'important');
child.style.setProperty('opacity', '1', 'important');
});
}
}
const clanMenu = document.getElementById('clan-menu');
if (clanMenu && !clanMenu._legitPatched) {
clanMenu._legitPatched = true;
Array.from(clanMenu.children).forEach(child => {
if (child.id === 'clan_menu_content') return;
child.style.removeProperty('display');
child.style.removeProperty('visibility');
child.style.setProperty('opacity', '1', 'important');
const txt = child.textContent.trim();
const isClose =
txt === 'X' || txt === 'x' || txt === '✕' || txt === '×' || txt === '✖' ||
child.className.toString().toLowerCase().includes('close');
if (isClose) {
child.style.setProperty('position', 'absolute', 'important');
child.style.setProperty('left', '50%', 'important');
child.style.setProperty('right', 'auto', 'important');
child.style.setProperty('top', '8px', 'important');
child.style.setProperty('transform', 'translateX(-50%)', 'important');
child.style.setProperty('margin', '0', 'important');
}
});
}
});
_legitMenuObserver.observe(document.body, { childList: true, subtree: true, attributes: true });
updateCSS();
const miniSwitchGlobalStyles = document.createElement('style');
miniSwitchGlobalStyles.innerHTML = `
.legit-mini-row {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
}
.legit-mini-label {
font-weight: 900;
transition: color 0.2s;
}
.legit-mini-switch {
position: relative;
display: inline-block;
width: 34px;
height: 20px;
cursor: pointer;
}
.legit-mini-switch input { opacity: 0; width: 0; height: 0; margin: 0; }
.legit-mini-slider {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background-color: #cc5151;
transition: .2s ease-in-out; border-radius: 20px;
box-shadow: inset 0 1px 3px rgba(0,0,0,0.4);
}
.legit-mini-slider:before {
position: absolute;
content: ""; height: 14px; width: 14px; left: 3px; bottom: 3px;
background-color: white; transition: .2s ease-in-out; border-radius: 50%;
box-shadow: 0 1px 2px rgba(0,0,0,0.4);
}
.legit-mini-switch input:checked + .legit-mini-slider { background-color: #a4cc4f; }
.legit-mini-switch input:checked + .legit-mini-slider:before { transform: translateX(14px); }
.legit-arrow-btn {
cursor: pointer;
font-size: 13px;
transition: all 0.15s;
user-select: none;
padding: 2px 7px;
border-radius: 5px;
font-weight: 900;
}
.legit-arrow-btn:hover { transform: scale(1.08); }
#stats-overlay-container, #kv-overlay-container {
transition: border-color 0.25s, color 0.25s, box-shadow 0.25s;
}
.kv-cell {
transition: background 0.06s ease, color 0.06s ease, border-color 0.06s ease, transform 0.06s ease!important;
}
#stats-drag-handle, #kv-drag-handle {
transition: background 0.2s, color 0.2s;
}
`;
document.head.appendChild(miniSwitchGlobalStyles);
const menuContainer = document.createElement('div');
menuContainer.id = 'legit-script-menu';
Object.assign(menuContainer.style, {
position: 'fixed', top: '50%', left: '50%',
transform: 'translate(-50%,-50%)',
width: '820px', height: '460px',
borderRadius: '20px', padding: '25px 20px 20px 20px',
zIndex: '1000000', fontFamily: "'Baloo Paaji', cursive, sans-serif",
fontWeight: '900', display: 'none',
boxShadow: '0 0 40px rgba(0,0,0,0.6)',
userSelect: 'none', WebkitFontSmoothing: 'antialiased',
boxSizing: 'border-box',
transition: 'background-color 0.2s, color 0.2s, border-color 0.2s'
});
const xCloseBtn = document.createElement('div');
xCloseBtn.innerText = "✕";
Object.assign(xCloseBtn.style, {
position: 'absolute', top: '18px', right: '20px',
fontSize: '20px', cursor: 'pointer',
transition: 'color 0.15s, transform 0.1s'
});
xCloseBtn.onclick = () => toggleMenu(false);
xCloseBtn.onmouseenter = () => { xCloseBtn.style.transform = "scale(1.15)"; };
xCloseBtn.onmouseleave = () => { xCloseBtn.style.transform = "scale(1)"; };
menuContainer.appendChild(xCloseBtn);
const toggleWrapper = document.createElement('div');
Object.assign(toggleWrapper.style, {
position: 'absolute', top: '15px', left: '20px',
width: '65px', height: '32px', zIndex: '1000002'
});
const toggleLabel = document.createElement('label');
Object.assign(toggleLabel.style, {
position: 'relative', display: 'inline-block',
width: '100%', height: '100%', cursor: 'pointer'
});
const toggleInput = document.createElement('input');
toggleInput.type = 'checkbox';
toggleInput.checked = config.lightMode;
Object.assign(toggleInput.style, { opacity: '0', width: '0', height: '0', margin: '0' });
toggleLabel.appendChild(toggleInput);
const toggleSlider = document.createElement('div');
Object.assign(toggleSlider.style, {
position: 'absolute', top: '0', left: '0', right: '0', bottom: '0',
borderRadius: '30px', overflow: 'hidden',
transition: 'background-color 0.4s',
boxShadow: 'inset 0 2px 5px rgba(0,0,0,0.4)'
});
toggleLabel.appendChild(toggleSlider);
const sliderDecorations = document.createElement('div');
Object.assign(sliderDecorations.style, {
width: '100%', height: '100%', position: 'relative', pointerEvents: 'none'
});
toggleSlider.appendChild(sliderDecorations);
const toggleCircle = document.createElement('div');
Object.assign(toggleCircle.style, {
position: 'absolute', height: '24px', width: '24px',
left: '4px', bottom: '4px', borderRadius: '50%', zIndex: '2',
transition: 'transform 0.4s cubic-bezier(0.4,0,0.2,1), background-color 0.4s, box-shadow 0.4s'
});
toggleLabel.appendChild(toggleCircle);
toggleWrapper.appendChild(toggleLabel);
menuContainer.appendChild(toggleWrapper);
const switchArtStyle = document.createElement('style');
switchArtStyle.innerHTML = `
.legit-slider-bg{background-color:#2c3e50;}
.legit-circle-art{background-color:#f1c40f;box-shadow:inset -3px 2px 0 0 #f39c12,0 0 10px rgba(241,196,15,0.4);}
.legit-star-1,.legit-star-2,.legit-crater{position:absolute;background:white;border-radius:50%;transition:opacity 0.3s;}
.legit-star-1{width:3px;height:3px;top:8px;right:16px;opacity:0.8;}
.legit-star-2{width:2px;height:2px;bottom:8px;right:26px;opacity:0.5;}
.legit-crater{width:4px;height:4px;background:rgba(0,0,0,0.12);opacity:1;top:6px;left:6px;}
.legit-crater-2{position:absolute;width:3px;height:3px;background:rgba(0,0,0,0.12);border-radius:50%;bottom:6px;left:10px;}
.legit-cloud-1,.legit-cloud-2{position:absolute;background:#fff;border-radius:10px;opacity:0;transform:translateY(20px);transition:transform 0.4s,opacity 0.3s;}
.legit-cloud-1{width:22px;height:8px;bottom:4px;left:8px;}
.legit-cloud-2{width:16px;height:6px;bottom:10px;left:22px;}
input:checked+div{background-color:#56ccf2!important;}
input:checked+div .legit-star-1,input:checked+div .legit-star-2{opacity:0!important;}
input:checked+div .legit-cloud-1{opacity:0.9!important;transform:translateY(0);}
input:checked+div .legit-cloud-2{opacity:0.8!important;transform:translateY(0);}
input:checked~.legit-circle-art{transform:translateX(33px)!important;background-color:#ffdb58!important;box-shadow:0 0 14px #ffb300,inset -2px -2px 0 rgba(0,0,0,0.1)!important;}
input:checked~.legit-circle-art .legit-crater,input:checked~.legit-circle-art .legit-crater-2{opacity:0!important;}
`;
document.head.appendChild(switchArtStyle);
['legit-star-1','legit-star-2','legit-cloud-1','legit-cloud-2'].forEach(cls => {
const el = document.createElement('div'); el.className = cls; sliderDecorations.appendChild(el);
});
const crater1 = document.createElement('div'); crater1.className = 'legit-crater'; toggleCircle.appendChild(crater1);
const crater2 = document.createElement('div'); crater2.className = 'legit-crater-2'; toggleCircle.appendChild(crater2);
toggleSlider.className = 'legit-slider-bg';
toggleCircle.className = 'legit-circle-art';
const title = document.createElement('h2');
title.innerText = "SPLOOP.IO LEGIT SCRIPT";
Object.assign(title.style, {
margin: '0 0 20px 0', textAlign: 'center',
paddingBottom: '12px', fontSize: '28px', letterSpacing: '1px',
transition: 'color 0.2s, border-color 0.2s'
});
menuContainer.appendChild(title);
const mainLayout = document.createElement('div');
mainLayout.style.cssText = "display:flex;width:100%;height:350px;gap:15px;box-sizing:border-box;";
menuContainer.appendChild(mainLayout);
const leftPanel = document.createElement('div');
leftPanel.style.cssText = "flex:1;display:flex;flex-direction:column;justify-content:space-between;border-radius:12px;padding:12px;box-sizing:border-box;transition:background-color 0.2s,border-color 0.2s;";
mainLayout.appendChild(leftPanel);
const navContainer = document.createElement('div');
navContainer.style.cssText = "display:flex;flex-direction:column;gap:8px;";
leftPanel.appendChild(navContainer);
const navBtnBase = "padding:10px 14px;font-weight:900;border-radius:8px;cursor:pointer;font-family:'Baloo Paaji';transition:background-color 0.2s,color 0.2s,border-color 0.2s; border:none; font-size:15px; text-align:left; width:100%;";
const navVisualsBtn = document.createElement('button');
navVisualsBtn.innerText = "Visuals";
navVisualsBtn.style.cssText = navBtnBase;
navContainer.appendChild(navVisualsBtn);
const navHudBtn = document.createElement('button');
navHudBtn.innerText = "HUD Elements";
navHudBtn.style.cssText = navBtnBase;
navContainer.appendChild(navHudBtn);
const navSystemBtn = document.createElement('button');
navSystemBtn.innerText = "System & Ads";
navSystemBtn.style.cssText = navBtnBase;
navContainer.appendChild(navSystemBtn);
const navCreditBtn = document.createElement('button');
navCreditBtn.innerText = "Credits";
navCreditBtn.style.cssText = navBtnBase + "text-align:center;";
leftPanel.appendChild(navCreditBtn);
const rightPanel = document.createElement('div');
rightPanel.id = 'legit-right-panel';
rightPanel.style.cssText = "flex:2.6;border-radius:12px;padding:15px;overflow-y:auto;box-sizing:border-box;transition:background-color 0.2s,color 0.2s,border-color 0.2s;";
mainLayout.appendChild(rightPanel);
const scrollbarStyle = document.createElement('style');
scrollbarStyle.innerHTML = `
#legit-right-panel::-webkit-scrollbar{width:6px;}
#legit-right-panel::-webkit-scrollbar-track{background:transparent;}
#legit-right-panel::-webkit-scrollbar-thumb{background:rgba(90,90,98,0.3);border-radius:10px;}
#legit-right-panel::-webkit-scrollbar-thumb:hover{background:rgba(90,90,98,0.6);}
#legit-right-panel::-webkit-scrollbar-button{display:none;}
`;
document.head.appendChild(scrollbarStyle);
const visualsContent = document.createElement('div');
visualsContent.style.cssText = "display:block;width:100%;";
rightPanel.appendChild(visualsContent);
const hudContent = document.createElement('div');
hudContent.style.cssText = "display:none;width:100%;";
rightPanel.appendChild(hudContent);
const systemContent = document.createElement('div');
systemContent.style.cssText = "display:none;width:100%;";
rightPanel.appendChild(systemContent);
const creditContent = document.createElement('div');
creditContent.style.cssText = "display:none;width:100%;height:100%;flex-direction:column;justify-content:center;align-items:center;text-align:center;gap:15px;";
rightPanel.appendChild(creditContent);
const allFloatingPanels = [];
let floatingPanelIdCounter = 0;
let floatingPanelZCounter = 1000002;
function createFloatingPanel(titleText) {
const panelIndex = floatingPanelIdCounter;
const cascade = (panelIndex % 8) * 26;
const win = document.createElement('div');
Object.assign(win.style, {
position: 'fixed', top: '50%', left: '50%',
transform: `translate(calc(-50% + ${cascade}px), calc(-50% + ${cascade}px))`,
width: '480px', maxWidth: '92vw', height: '520px', maxHeight: '86vh',
borderRadius: '18px', padding: '18px', boxSizing: 'border-box',
zIndex: '1000002', display: 'none', flexDirection: 'column',
fontFamily: "'Baloo Paaji', cursive, sans-serif", fontWeight: '900',
boxShadow: '0 0 50px rgba(0,0,0,0.7)', userSelect: 'none'
});
const header = document.createElement('div');
header.style.cssText = "display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;padding-bottom:12px;border-bottom:3px solid #3a3a42;cursor:move;";
const titleEl = document.createElement('div');
titleEl.innerText = titleText;
titleEl.style.cssText = "font-size:19px;letter-spacing:0.5px;pointer-events:none;";
header.appendChild(titleEl);
const closeBtn = document.createElement('div');
closeBtn.innerText = "✕";
closeBtn.style.cssText = "font-size:20px;cursor:pointer;transition:transform 0.1s;";
closeBtn.onmouseenter = () => { closeBtn.style.transform = "scale(1.15)"; };
closeBtn.onmouseleave = () => { closeBtn.style.transform = "scale(1)"; };
header.appendChild(closeBtn);
win.appendChild(header);
const body = document.createElement('div');
const bodyId = 'floating-panel-body-' + (floatingPanelIdCounter++);
body.id = bodyId;
body.style.cssText = "flex:1;overflow-y:auto;width:100%;padding-right:4px;";
win.appendChild(body);
const scrollStyle = document.createElement('style');
scrollStyle.innerHTML = `
#${bodyId}::-webkit-scrollbar{width:6px;}
#${bodyId}::-webkit-scrollbar-track{background:transparent;}
#${bodyId}::-webkit-scrollbar-thumb{background:rgba(90,90,98,0.3);border-radius:10px;}
#${bodyId}::-webkit-scrollbar-thumb:hover{background:rgba(90,90,98,0.6);}
`;
document.head.appendChild(scrollStyle);
let hasBeenDragged = false;
let isLogicallyOpen = false;
function open() {
isLogicallyOpen = true;
win.style.display = 'flex';
win.style.zIndex = String(++floatingPanelZCounter);
if (hasBeenDragged) {
const maxLeft = window.innerWidth - win.offsetWidth;
const maxTop = window.innerHeight - win.offsetHeight;
win.style.left = Math.max(0, Math.min(parseFloat(win.style.left), maxLeft)) + 'px';
win.style.top = Math.max(0, Math.min(parseFloat(win.style.top), maxTop)) + 'px';
}
}
function close() { isLogicallyOpen = false; win.style.display = 'none'; }
function hideForMenu() { win.style.display = 'none'; }
function restoreForMenu() { if (isLogicallyOpen) { win.style.display = 'flex'; } }
closeBtn.onclick = close;
win.addEventListener('mousedown', () => { win.style.zIndex = String(++floatingPanelZCounter); });
win.onclick = (e) => e.stopPropagation();
let isDragging = false, dragOffsetX = 0, dragOffsetY = 0;
function startDrag(clientX, clientY) {
const rect = win.getBoundingClientRect();
win.style.left = rect.left + 'px';
win.style.top = rect.top + 'px';
win.style.transform = 'none';
dragOffsetX = clientX - rect.left;
dragOffsetY = clientY - rect.top;
isDragging = true;
hasBeenDragged = true;
header.style.cursor = 'grabbing';
}
function moveDrag(clientX, clientY) {
if (!isDragging) return;
const maxLeft = window.innerWidth - win.offsetWidth;
const maxTop = window.innerHeight - win.offsetHeight;
const newLeft = Math.max(0, Math.min(clientX - dragOffsetX, maxLeft));
const newTop = Math.max(0, Math.min(clientY - dragOffsetY, maxTop));
win.style.left = newLeft + 'px';
win.style.top = newTop + 'px';
}
function endDrag() {
if (!isDragging) return;
isDragging = false;
header.style.cursor = 'move';
}
header.addEventListener('mousedown', (e) => {
if (e.target === closeBtn) return;
startDrag(e.clientX, e.clientY);
e.preventDefault();
});
window.addEventListener('mousemove', (e) => { moveDrag(e.clientX, e.clientY); });
window.addEventListener('mouseup', endDrag);
header.addEventListener('touchstart', (e) => {
if (e.target === closeBtn) return;
const t = e.touches[0];
startDrag(t.clientX, t.clientY);
}, { passive: true });
window.addEventListener('touchmove', (e) => {
if (!isDragging) return;
const t = e.touches[0];
moveDrag(t.clientX, t.clientY);
}, { passive: true });
window.addEventListener('touchend', endDrag);
function applyTheme(isLight) {
if (isLight) {
Object.assign(win.style, { backgroundColor: '#ffffff', color: '#000000', border: '2px solid #cccccc' });
titleEl.style.color = '#000000';
header.style.borderBottomColor = '#e5e5e5';
closeBtn.style.color = '#333333';
} else {
Object.assign(win.style, { backgroundColor: 'rgba(20,20,22,0.98)', color: '#ffffff', border: '2px solid #5a5a62' });
titleEl.style.color = '#e5e5e5';
header.style.borderBottomColor = '#3a3a42';
closeBtn.style.color = '#aaaaaa';
}
}
document.body.appendChild(win);
const panel = { window: win, body, open, close, hideForMenu, restoreForMenu, applyTheme };
allFloatingPanels.push(panel);
return panel;
}
function createModalToggleRow(targetBox, labelText, configKey, panel, onToggleCallback) {
const wrapper = document.createElement('div');
wrapper.style.cssText = "margin-bottom:10px;display:flex;justify-content:space-between;align-items:center;background:rgba(150,150,150,0.04);padding:8px 14px;border-radius:8px;border:1px solid rgba(150,150,150,0.06);";
const leftCluster = document.createElement('div');
leftCluster.style.cssText = "display:flex;align-items:center;gap:12px;";
const label = document.createElement('span');
label.className = 'legit-toggle-label';
label.innerText = labelText;
label.style.cssText = "font-size:16px;font-weight:900;transition:color 0.2s;";
leftCluster.appendChild(label);
const arrow = document.createElement('span');
arrow.className = 'legit-arrow-btn';
arrow.innerText = "▶";
arrow.title = "Mở bảng chỉnh chi tiết";
leftCluster.appendChild(arrow);
wrapper.appendChild(leftCluster);
const btn = document.createElement('button');
btn.style.cssText = "width:70px;height:30px;border:2px solid rgba(0,0,0,0.15);border-radius:6px;font-weight:900;cursor:pointer;font-family:'Baloo Paaji',cursive;transition:all 0.2s;font-size:14px;color:white;";
wrapper.appendChild(btn);
const updateBtnState = () => {
if (config[configKey]) {
btn.innerText = "ON";
btn.style.background = "#a4cc4f";
btn.style.boxShadow = "0 3px 0 #7ca232";
} else {
btn.innerText = "OFF";
btn.style.background = "#cc5151";
btn.style.boxShadow = "0 3px 0 #a33838";
}
btn.style.transform = "translateY(0)";
};
updateBtnState();
arrow.onclick = (e) => { e.stopPropagation(); panel.open(); };
btn.onclick = () => {
config[configKey] = !config[configKey];
updateBtnState();
saveConfig();
if (onToggleCallback) onToggleCallback(config[configKey]);
};
btn.onmousedown = () => { btn.style.boxShadow = "none"; btn.style.transform = "translateY(3px)"; };
btn.onmouseup = () => { updateBtnState(); };
targetBox.appendChild(wrapper);
return wrapper;
}
const ghostPanel = createFloatingPanel("Ghost Mode");
const ghostModalBody = ghostPanel.body;
function detectPlatform() {
const ua = navigator.userAgent || "";
const platform = navigator.platform || "";
const maxTouch = navigator.maxTouchPoints || 0;
let os = "unknown", osLabel = "Unknown OS";
if (/iPhone|iPad|iPod/.test(ua) || (platform === "MacIntel" && maxTouch > 1)) {
os = "ios"; osLabel = "iOS";
} else if (/Android/.test(ua)) {
os = "android"; osLabel = "Android";
} else if (/Windows/.test(ua)) {
os = "windows"; osLabel = "Windows";
} else if (/Macintosh|Mac OS X/.test(ua)) {
os = "mac"; osLabel = "macOS";
} else if (/Linux/.test(ua)) {
os = "linux"; osLabel = "Linux";
}
const isMobile = os === "ios" || os === "android";
let browser = "fallback", browserLabel = "Trình duyệt của bạn", store = "fallback";
if (/Edg\//.test(ua) || /EdgA\//.test(ua) || /EdgiOS\//.test(ua)) {
browser = "edge"; browserLabel = "Microsoft Edge"; store = "Microsoft Edge Add-ons";
} else if (/Firefox\//.test(ua) || /FxiOS\//.test(ua)) {
browser = "firefox"; browserLabel = "Firefox"; store = "Firefox Browser Add-ons";
} else if (/OPR\//.test(ua) || /OPT\//.test(ua) || /OPiOS\//.test(ua) || /Opera/.test(ua)) {
browser = "fallback"; browserLabel = "Opera"; store = "trang tải chính thức";
} else if (/coc_coc_browser/i.test(ua)) {
browser = "chrome"; browserLabel = "Cốc Cốc"; store = "Chrome Web Store";
} else if (typeof navigator.brave !== "undefined") {
browser = "chrome"; browserLabel = "Brave"; store = "Chrome Web Store";
} else if (/Chrome\//.test(ua) || /CriOS\//.test(ua)) {
browser = "chrome"; browserLabel = "Chrome"; store = "Chrome Web Store";
} else if (/Safari\//.test(ua)) {
browser = "fallback"; browserLabel = "Safari"; store = "trang tải chính thức";
}
const platformKey = isMobile ? "fallback" : browser;
if (isMobile) store = "trang tải chính thức";
return { os, osLabel, browser, browserLabel, isMobile, platformKey, store };
}
const detectedPlatform = detectPlatform();
const VPN_LIST_COMPUTER = [
{
name: "VeePN",
desc: "2,600+ servers, no-logs policy, built-in adblock",
urls: {
chrome: "https://chromewebstore.google.com/detail/free-vpn-for-chrome-vpn-p/majdfhpaihoncoakbjgbdhglocklcgno",
edge: "https://microsoftedge.microsoft.com/addons/detail/free-vpn-for-edge-vpn-p/panammoooggmlehahpcjckcncfeffcoi",
firefox: "https://addons.mozilla.org/en-US/firefox/addon/veepn-free-fast-security-vpn/",
fallback: "https://veepn.com/vpn-apps/"
}
},
{
name: "Browsec VPN",
desc: "8M+ users, unlimited traffic, easy to use",
urls: {
chrome: "https://chromewebstore.google.com/detail/browsec-vpn-free-vpn-for/omghfjlpggmjjaagoclmmobgdodcjboh",
edge: "https://microsoftedge.microsoft.com/addons/detail/browsec-vpn-free-vpn/fjnehcbecaggobjholekjijaaekbnlgj",
firefox: "https://addons.mozilla.org/en-US/firefox/addon/browsec/",
fallback: "https://browsec.com/en"
}
},
{
name: "Windscribe",
desc: "10GB/month free, with ad & tracker blocking",
urls: {
chrome: "https://chromewebstore.google.com/detail/free-vpn-for-chrome-vpn-e/hnmpcagpplmpfojmgmnngilcnanddlhb",
edge: "https://microsoftedge.microsoft.com/addons/detail/windscribe-free-proxy-a/dkkdbpgldnmkhcliffjpajcfdjkcaddf",
firefox: "https://addons.mozilla.org/en-US/firefox/addon/windscribe/",
fallback: "https://windscribe.com/"
}
},
{
name: "Urban VPN Proxy",
desc: "Unlimited data, global server coverage",
urls: {
chrome: "https://chromewebstore.google.com/detail/urban-vpn-proxy/eppiocemhmnlbhjplcgkofciiegomcon",
edge: "https://microsoftedge.microsoft.com/addons/detail/urban-vpn-proxy/nimlmejbmnecnaghgmbahmbaddhjbecg",
fallback: "https://www.urban-vpn.com/free-products/free-browser-extension/"
}
}
];
const VPN_LIST_MOBILE = [
{
name: "1.1.1.1 (Cloudflare)",
desc: "Free, no-logs DNS + WARP VPN by Cloudflare",
urls: {
ios: "https://apps.apple.com/us/app/1-1-1-1-faster-internet/id1423538627",
android: "https://play.google.com/store/apps/details?id=com.cloudflare.onedotonedotonedotone",
fallback: "https://1.1.1.1/"
}
}
];
function getVpnUrlForCurrentPlatform(vpn) {
return vpn.urls[detectedPlatform.platformKey] || vpn.urls.fallback;
}
function getMobileAppUrlForCurrentPlatform(app) {
return app.urls[detectedPlatform.os] || app.urls.fallback;
}
const vpnPanel = createFloatingPanel("VPN Recommendations");
(function buildVpnList() {
const note = document.createElement('div');
note.className = 'legit-mini-label';
const detectedStoreName = detectedPlatform.platformKey === "fallback"
? "official download page"
: detectedPlatform.store;
note.innerText = "Detected " + detectedPlatform.browserLabel + " on " + detectedPlatform.osLabel +
". Download opens the matching listing on the " + detectedStoreName + ".";
note.style.cssText = "font-size:13px;line-height:1.5;margin-bottom:14px;opacity:0.85;";
vpnPanel.body.appendChild(note);
function addVpnRow(item, urlFn) {
const row = document.createElement('div');
row.style.cssText = "margin-bottom:10px;display:flex;justify-content:space-between;align-items:center;background:rgba(150,150,150,0.04);padding:10px 14px;border-radius:8px;border:1px solid rgba(150,150,150,0.06);gap:10px;";
const textCol = document.createElement('div');
textCol.style.cssText = "display:flex;flex-direction:column;gap:2px;min-width:0;";
const nameEl = document.createElement('span');
nameEl.className = 'legit-toggle-label';
nameEl.innerText = item.name;
nameEl.style.cssText = "font-size:15px;font-weight:900;";
textCol.appendChild(nameEl);
const descEl = document.createElement('span');
descEl.className = 'legit-mini-label';
descEl.innerText = item.desc;
descEl.style.cssText = "font-size:12px;opacity:0.75;";
textCol.appendChild(descEl);
row.appendChild(textCol);
const dlBtn = document.createElement('button');
dlBtn.innerText = "Download";
dlBtn.style.cssText = "flex-shrink:0;padding:8px 16px;background:#5a5a62;border:none;color:#ffffff;font-weight:900;border-radius:8px;cursor:pointer;font-family:'Baloo Paaji',cursive;font-size:13px;box-shadow:0 3px 0 #3a3a42;transition:all 0.1s;";
dlBtn.onclick = () => window.open(urlFn(item), "_blank", "noopener,noreferrer");
dlBtn.onmousedown = () => { dlBtn.style.boxShadow = "none"; dlBtn.style.transform = "translateY(3px)"; };
dlBtn.onmouseup = () => { dlBtn.style.boxShadow = "0 3px 0 #3a3a42"; dlBtn.style.transform = "translateY(0)"; };
row.appendChild(dlBtn);
vpnPanel.body.appendChild(row);
}
createGroupTitle(vpnPanel.body, "Computer");
VPN_LIST_COMPUTER.forEach(vpn => addVpnRow(vpn, getVpnUrlForCurrentPlatform));
createGroupTitle(vpnPanel.body, "Mobile");
VPN_LIST_MOBILE.forEach(app => addVpnRow(app, getMobileAppUrlForCurrentPlatform));
})();
function extractYoutubeId(url) {
try {
const u = new URL(url);
const host = u.hostname.replace(/^www\./, '').replace(/^m\./, '');
if (host === 'youtu.be') return u.pathname.slice(1) || null;
if (host === 'youtube.com' || host === 'music.youtube.com') {
if (u.pathname === '/watch') return u.searchParams.get('v');
if (u.pathname.startsWith('/shorts/')) return u.pathname.split('/')[2] || null;
if (u.pathname.startsWith('/embed/')) return u.pathname.split('/')[2] || null;
if (u.pathname.startsWith('/live/')) return u.pathname.split('/')[2] || null;
}
} catch (e) { }
return null;
}
const musicAudioEl = document.createElement('audio');
musicAudioEl.id = 'music-audio-player';
musicAudioEl.style.display = 'none';
musicAudioEl.volume = config.musicVolume;
musicAudioEl.addEventListener('ended', () => playNextTrack());
document.body.appendChild(musicAudioEl);
let ytPlayer = null, ytApiReady = false, ytPendingVideoId = null, ytPendingVolume = null;
let musicPaused = false;
function loadYoutubeApiIfNeeded() {
if (window.YT && window.YT.Player) { ytApiReady = true; initYtPlayer(); return; }
if (document.getElementById('music-yt-api-script')) return;
const tag = document.createElement('script');
tag.id = 'music-yt-api-script';
tag.src = 'https://www.youtube.com/iframe_api';
document.head.appendChild(tag);
const prevCallback = window.onYouTubeIframeAPIReady;
window.onYouTubeIframeAPIReady = function () {
if (typeof prevCallback === 'function') { try { prevCallback(); } catch (e) {} }
ytApiReady = true;
initYtPlayer();
if (ytPendingVideoId) { const id = ytPendingVideoId, vol = ytPendingVolume; ytPendingVideoId = null; playYoutubeVideo(id, vol); }
};
}
function initYtPlayer() {
if (ytPlayer || !window.YT || !window.YT.Player) return;
const holder = document.createElement('div');
holder.id = 'music-yt-player-holder';
holder.style.cssText = 'position:fixed;width:1px;height:1px;opacity:0;pointer-events:none;left:-9999px;top:-9999px;';
document.body.appendChild(holder);
ytPlayer = new YT.Player(holder.id, {
height: '1', width: '1',
playerVars: { autoplay: 0, controls: 0, disablekb: 1 },
events: {
onReady: () => { ytPlayer.setVolume(Math.round(config.musicVolume * 100)); if (ytPendingVideoId) { const id = ytPendingVideoId, vol = ytPendingVolume; ytPendingVideoId = null; playYoutubeVideo(id, vol); } },
onStateChange: (e) => { if (e.data === YT.PlayerState.ENDED) playNextTrack(); }
}
});
}
function playYoutubeVideo(videoId, volume) {
loadYoutubeApiIfNeeded();
if (!ytApiReady || !ytPlayer || typeof ytPlayer.loadVideoById !== 'function') { ytPendingVideoId = videoId; ytPendingVolume = volume; return; }
ytPlayer.loadVideoById(videoId);
ytPlayer.setVolume(Math.round((volume !== undefined ? volume : config.musicVolume) * 100));
}
function stopYoutube() { if (ytPlayer && typeof ytPlayer.stopVideo === 'function') { try { ytPlayer.stopVideo(); } catch (e) {} } }
function stopAllMusicPlayback() { stopYoutube(); musicAudioEl.pause(); }
function playTrackAtIndex(idx) {
if (idx < 0 || idx >= config.musicPlaylist.length) return;
config.musicCurrentIndex = idx;
musicPaused = false;
saveConfig();
stopAllMusicPlayback();
if (config.musicEnabled) {
const track = config.musicPlaylist[idx];
const vol = track.volume !== undefined ? track.volume : config.musicVolume;
if (track.kind === 'youtube') {
playYoutubeVideo(track.videoId, vol);
} else {
musicAudioEl.src = track.src;
musicAudioEl.volume = vol;
musicAudioEl.play().catch(() => {});
}
}
renderMusicPlaylist();
}
function pauseCurrentTrack() {
const track = config.musicPlaylist[config.musicCurrentIndex];
if (!track) return;
if (track.kind === 'youtube') { if (ytPlayer && typeof ytPlayer.pauseVideo === 'function') ytPlayer.pauseVideo(); }
else { musicAudioEl.pause(); }
musicPaused = true;
renderMusicPlaylist();
}
function resumeCurrentTrack() {
const track = config.musicPlaylist[config.musicCurrentIndex];
if (!track) return;
if (!config.musicEnabled) { config.musicEnabled = true; saveConfig(); updateMusicToggleUI(); }
const vol = track.volume !== undefined ? track.volume : config.musicVolume;
if (track.kind === 'youtube') {
if (ytPlayer && typeof ytPlayer.playVideo === 'function' && ytPlayer.getVideoData && ytPlayer.getVideoData().video_id === track.videoId) {
ytPlayer.playVideo();
} else {
playYoutubeVideo(track.videoId, vol);
}
} else {
musicAudioEl.volume = vol;
musicAudioEl.play().catch(() => {});
}
musicPaused = false;
renderMusicPlaylist();
}
function playNextTrack() {
if (!config.musicPlaylist.length) return;
playTrackAtIndex((config.musicCurrentIndex + 1) % config.musicPlaylist.length);
}
const musicPanel = createFloatingPanel("Music Player");
let renderMusicPlaylist = () => {};
let updateMusicToggleUI = () => {};
(function buildMusicPanel() {
const toggleRow = document.createElement('div');
toggleRow.style.cssText = "display:flex;justify-content:space-between;align-items:center;padding:10px 14px;margin-bottom:14px;background:rgba(150,150,150,0.05);border-radius:10px;border:1px solid rgba(150,150,150,0.08);";
const toggleLabel = document.createElement('span');
toggleLabel.className = 'legit-toggle-label';
toggleLabel.innerText = "Music";
toggleLabel.style.cssText = "font-size:17px;font-weight:900;";
const toggleBtn = document.createElement('button');
toggleBtn.style.cssText = "width:80px;height:32px;border:none;border-radius:8px;font-weight:900;cursor:pointer;font-family:'Baloo Paaji',cursive;font-size:14px;color:white;transition:all 0.1s;";
updateMusicToggleUI = () => {
if (config.musicEnabled) {
toggleBtn.innerText = "ON";
toggleBtn.style.background = "#a4cc4f";
toggleBtn.style.boxShadow = "0 3px 0 #7ca232";
} else {
toggleBtn.innerText = "OFF";
toggleBtn.style.background = "#cc5151";
toggleBtn.style.boxShadow = "0 3px 0 #a33838";
}
toggleBtn.style.transform = "translateY(0)";
};
toggleBtn.onclick = () => {
config.musicEnabled = !config.musicEnabled;
saveConfig();
updateMusicToggleUI();
if (config.musicEnabled) {
if (config.musicCurrentIndex === -1 && config.musicPlaylist.length) config.musicCurrentIndex = 0;
playTrackAtIndex(config.musicCurrentIndex);
} else {
stopAllMusicPlayback();
renderMusicPlaylist();
}
};
toggleBtn.onmousedown = () => { toggleBtn.style.boxShadow = "none"; toggleBtn.style.transform = "translateY(3px)"; };
toggleBtn.onmouseup = () => updateMusicToggleUI();
updateMusicToggleUI();
toggleRow.appendChild(toggleLabel);
toggleRow.appendChild(toggleBtn);
musicPanel.body.appendChild(toggleRow);
createGroupTitle(musicPanel.body, "Playlist");
const addBox = document.createElement('div');
addBox.style.cssText = "margin-bottom:12px;border:2px dashed rgba(150,150,150,0.25);border-radius:10px;padding:10px 14px;cursor:pointer;display:flex;align-items:center;gap:10px;transition:background 0.15s;";
const addIcon = document.createElement('span');
addIcon.innerText = "+";
addIcon.style.cssText = "font-size:20px;font-weight:900;line-height:1;";
addIcon.className = 'legit-toggle-label';
const addLabel = document.createElement('span');
addLabel.className = 'legit-mini-label';
addLabel.innerText = "Add a track (YouTube link or audio URL)";
addLabel.style.fontSize = '13px';
addBox.appendChild(addIcon);
addBox.appendChild(addLabel);
musicPanel.body.appendChild(addBox);
const addForm = document.createElement('div');
addForm.style.cssText = "display:none;margin-bottom:14px;gap:8px;flex-direction:column;";
const addInput = document.createElement('input');
addInput.type = 'text';
addInput.placeholder = 'https://youtube.com/watch?v=... or https://.../song.mp3';
addInput.style.cssText = "width:100%;box-sizing:border-box;padding:9px 12px;border-radius:8px;border:1px solid rgba(150,150,150,0.25);background:rgba(150,150,150,0.06);font-family:'Baloo Paaji',cursive;font-size:13px;outline:none;";
const addFormBtnRow = document.createElement('div');
addFormBtnRow.style.cssText = "display:flex;gap:8px;justify-content:flex-end;";
const confirmBtn = document.createElement('button');
confirmBtn.innerText = "Add";
confirmBtn.style.cssText = "padding:7px 16px;background:#a4cc4f;border:none;color:#1a1a1a;font-weight:900;border-radius:8px;cursor:pointer;font-family:'Baloo Paaji',cursive;font-size:13px;box-shadow:0 3px 0 #7ca232;";
const cancelBtn = document.createElement('button');
cancelBtn.innerText = "Cancel";
cancelBtn.style.cssText = "padding:7px 16px;background:#5a5a62;border:none;color:#fff;font-weight:900;border-radius:8px;cursor:pointer;font-family:'Baloo Paaji',cursive;font-size:13px;box-shadow:0 3px 0 #3a3a42;";
addFormBtnRow.appendChild(cancelBtn);
addFormBtnRow.appendChild(confirmBtn);
addForm.appendChild(addInput);
addForm.appendChild(addFormBtnRow);
musicPanel.body.appendChild(addForm);
function openAddForm() { addBox.style.display = 'none'; addForm.style.display = 'flex'; addInput.value = ''; addInput.focus(); }
function closeAddForm() { addForm.style.display = 'none'; addBox.style.display = 'flex'; }
addBox.onclick = openAddForm;
cancelBtn.onclick = closeAddForm;
function commitAddTrack() {
const raw = addInput.value.trim();
if (!raw) return;
const videoId = extractYoutubeId(raw);
const track = videoId
? { id: 'yt_' + videoId + '_' + Date.now(), kind: 'youtube', videoId, src: raw, title: 'YouTube: ' + videoId, volume: config.musicVolume }
: { id: 'au_' + Date.now(), kind: 'audio', src: raw, title: (() => { try { return decodeURIComponent(new URL(raw).pathname.split('/').pop()) || raw; } catch (e) { return raw; } })(), volume: config.musicVolume };
config.musicPlaylist.push(track);
if (config.musicCurrentIndex === -1) config.musicCurrentIndex = 0;
saveConfig();
closeAddForm();
renderMusicPlaylist();
}
confirmBtn.onclick = commitAddTrack;
addInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') commitAddTrack(); });
const listEl = document.createElement('div');
musicPanel.body.appendChild(listEl);
renderMusicPlaylist = function () {
listEl.innerHTML = '';
if (!config.musicPlaylist.length) {
const empty = document.createElement('div');
empty.className = 'legit-mini-label';
empty.style.cssText = "font-size:13px;opacity:0.6;text-align:center;padding:10px 0;";
empty.innerText = "No tracks yet - add one above.";
listEl.appendChild(empty);
return;
}
config.musicPlaylist.forEach((track, idx) => {
const isCurrent = idx === config.musicCurrentIndex;
const isPlayingNow = isCurrent && config.musicEnabled && !musicPaused;
const row = document.createElement('div');
row.style.cssText = "margin-bottom:8px;display:flex;align-items:center;padding:9px 12px;border-radius:8px;gap:10px;border:1px solid " +
(isCurrent ? "rgba(164,204,79,0.5)" : "rgba(150,150,150,0.08)") + ";background:" +
(isCurrent ? "rgba(164,204,79,0.10)" : "rgba(150,150,150,0.04)") + ";";
const playPauseBtn = document.createElement('button');
playPauseBtn.style.cssText = "flex-shrink:0;width:26px;height:26px;border:none;border-radius:6px;background:" + (isPlayingNow ? "#e09f3e" : "#a4cc4f") + ";color:#1a1a1a;font-weight:900;cursor:pointer;font-size:11px;line-height:1;display:flex;align-items:center;justify-content:center;";
playPauseBtn.innerText = isPlayingNow ? "⏸" : "▶";
playPauseBtn.title = isPlayingNow ? "Pause" : "Play";
playPauseBtn.onclick = (e) => {
e.stopPropagation();
if (isCurrent) {
if (isPlayingNow) pauseCurrentTrack(); else resumeCurrentTrack();
} else {
playTrackAtIndex(idx);
}
};
row.appendChild(playPauseBtn);
const titleEl = document.createElement('span');
titleEl.className = 'legit-mini-label';
titleEl.innerText = track.title;
titleEl.style.cssText = "flex:1;min-width:0;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer;";
titleEl.onclick = () => playTrackAtIndex(idx);
row.appendChild(titleEl);
const volumeSlider = document.createElement('input');
volumeSlider.type = 'range';
volumeSlider.min = '0'; volumeSlider.max = '1'; volumeSlider.step = '0.05';
volumeSlider.value = String(track.volume !== undefined ? track.volume : config.musicVolume);
volumeSlider.title = "Volume";
volumeSlider.style.cssText = "flex-shrink:0;width:70px;accent-color:#a4cc4f;cursor:pointer;";
volumeSlider.oninput = (e) => {
e.stopPropagation();
const vol = parseFloat(volumeSlider.value);
track.volume = vol;
saveConfig();
if (isCurrent) {
if (track.kind === 'youtube') { if (ytPlayer && typeof ytPlayer.setVolume === 'function') ytPlayer.setVolume(Math.round(vol * 100)); }
else { musicAudioEl.volume = vol; }
}
};
volumeSlider.onclick = (e) => e.stopPropagation();
row.appendChild(volumeSlider);
const removeBtn = document.createElement('button');
removeBtn.innerText = "×";
removeBtn.style.cssText = "flex-shrink:0;width:24px;height:24px;border:none;border-radius:6px;background:#cc5151;color:#fff;font-weight:900;cursor:pointer;font-size:14px;line-height:1;";
removeBtn.onclick = (e) => {
e.stopPropagation();
config.musicPlaylist.splice(idx, 1);
if (config.musicCurrentIndex === idx) { stopAllMusicPlayback(); musicPaused = false; config.musicCurrentIndex = config.musicPlaylist.length ? 0 : -1; }
else if (config.musicCurrentIndex > idx) { config.musicCurrentIndex--; }
saveConfig();
renderMusicPlaylist();
};
row.appendChild(removeBtn);
listEl.appendChild(row);
});
};
renderMusicPlaylist();
})();
function applyGlobalTheme() {
const isLight = config.lightMode;
const statsContainer = document.getElementById('stats-overlay-container');
const kvContainer = document.getElementById('kv-overlay-container');
if (isLight) {
Object.assign(menuContainer.style, { backgroundColor: '#ffffff', color: '#000000', borderColor: '#cccccc' });
Object.assign(title.style, { color: '#000000', borderBottom: '3px solid #e5e5e5' });
xCloseBtn.style.color = '#333333';
Object.assign(leftPanel.style, { backgroundColor: '#f4f4f6', borderColor: '#e5e5e5' });
Object.assign(rightPanel.style, { backgroundColor: '#fcfcfd', borderColor: '#e5e5e5', color: '#000000' });
if (statsContainer) {
statsContainer.style.borderColor = 'rgba(0, 0, 0, 0.12)';
statsContainer.style.boxShadow = '0 10px 30px rgba(0, 0, 0, 0.15)';
document.getElementById('stats-drag-handle').style.background = 'rgba(0, 0, 0, 0.05)';
document.getElementById('stats-drag-handle').style.color = 'rgba(0, 0, 0, 0.25)';
}
if (kvContainer) {
kvContainer.style.borderColor = 'rgba(0, 0, 0, 0.12)';
kvContainer.style.boxShadow = '0 10px 30px rgba(0, 0, 0, 0.15)';
document.getElementById('kv-drag-handle').style.background = 'rgba(0, 0, 0, 0.05)';
document.getElementById('kv-drag-handle').style.color = 'rgba(0, 0, 0, 0.25)';
kvContainer.querySelectorAll('.kv-cell').forEach(cell => {
cell.style.background = 'rgba(0, 0, 0, 0.05)';
cell.style.borderColor = 'rgba(0, 0, 0, 0.04)';
});
}
} else {
Object.assign(menuContainer.style, { backgroundColor: 'rgba(20,20,22,0.98)', color: '#ffffff', borderColor: '#5a5a62' });
Object.assign(title.style, { color: '#e5e5e5', borderBottom: '3px solid #3a3a42' });
xCloseBtn.style.color = '#aaaaaa';
Object.assign(leftPanel.style, { backgroundColor: 'rgba(255,255,255,0.03)', borderColor: '#3a3a42' });
Object.assign(rightPanel.style, { backgroundColor: 'rgba(12,12,14,0.6)', borderColor: '#3a3a42', color: '#ffffff' });
if (statsContainer) {
statsContainer.style.borderColor = 'rgba(255, 255, 255, 0.08)';
statsContainer.style.boxShadow = '0 12px 40px rgba(0, 0, 0, 0.6)';
document.getElementById('stats-drag-handle').style.background = 'rgba(255, 255, 255, 0.06)';
document.getElementById('stats-drag-handle').style.color = 'rgba(255, 255, 255, 0.3)';
}
if (kvContainer) {
kvContainer.style.borderColor = 'rgba(255, 255, 255, 0.08)';
kvContainer.style.boxShadow = '0 12px 40px rgba(0, 0, 0, 0.6)';
document.getElementById('kv-drag-handle').style.background = 'rgba(255, 255, 255, 0.06)';
document.getElementById('kv-drag-handle').style.color = 'rgba(255, 255, 255, 0.3)';
kvContainer.querySelectorAll('.kv-cell').forEach(cell => {
cell.style.background = 'rgba(255, 255, 255, 0.06)';
cell.style.borderColor = 'rgba(255, 255, 255, 0.03)';
});
}
}
[visualsContent, hudContent, systemContent, creditContent, ...allFloatingPanels.map(p => p.body)].forEach(box => {
box.querySelectorAll('.legit-toggle-label').forEach(l => { l.style.color = isLight ? '#000000' : '#ffffff'; });
box.querySelectorAll('.legit-group-title').forEach(t => { t.style.color = isLight ? '#55555d' : '#8a8a92'; });
box.querySelectorAll('.legit-mini-label').forEach(l => { l.style.color = isLight ? '#44444a' : '#b5b5bd'; });
box.querySelectorAll('.legit-arrow-btn').forEach(a => {
a.style.color = isLight ? '#222225' : '#8a8a92';
a.style.background = isLight ? 'rgba(0,0,0,0.06)' : 'rgba(255,255,255,0.06)';
});
});
if (typeof applyOverlayStyle === 'function') applyOverlayStyle();
if (typeof applyKvStyle === 'function') applyKvStyle();
allFloatingPanels.forEach(p => p.applyTheme(isLight));
}
function createGroupTitle(targetBox, name) {
const el = document.createElement('div');
el.className = 'legit-group-title';
el.innerText = name;
el.style.cssText = "font-size:17px;margin:5px 0 12px 0;border-left:4px solid #5a5a62;padding-left:8px;letter-spacing:0.5px;font-weight:900;transition:color 0.2s;";
targetBox.appendChild(el);
}
function createToggle(targetBox, labelText, configKey, callback) {
const wrapper = document.createElement('div');
wrapper.style.cssText = "margin-bottom:10px;display:flex;justify-content:space-between;align-items:center;background:rgba(150,150,150,0.04);padding:8px 14px;border-radius:8px;border:1px solid rgba(150,150,150,0.06);";
const label = document.createElement('span');
label.className = 'legit-toggle-label';
label.innerText = labelText;
label.style.cssText = "font-size:16px;font-weight:900;transition:color 0.2s;";
const btn = document.createElement('button');
btn.style.cssText = "width:70px;height:30px;border:2px solid rgba(0,0,0,0.15);border-radius:6px;font-weight:900;cursor:pointer;font-family:'Baloo Paaji',cursive;transition:all 0.2s;font-size:14px;color:white;";
const updateBtnState = () => {
if (config[configKey]) {
btn.innerText = "ON";
btn.style.background = "#a4cc4f";
btn.style.boxShadow = "0 3px 0 #7ca232";
} else {
btn.innerText = "OFF";
btn.style.background = "#cc5151";
btn.style.boxShadow = "0 3px 0 #a33838";
}
btn.style.transform = "translateY(0)";
};
updateBtnState();
btn.onclick = () => {
config[configKey] = !config[configKey];
updateBtnState();
saveConfig();
if (callback) callback(config[configKey]);
};
btn.onmousedown = () => { btn.style.boxShadow = "none"; btn.style.transform = "translateY(3px)"; };
btn.onmouseup = () => { updateBtnState(); };
wrapper.appendChild(label);
wrapper.appendChild(btn);
targetBox.appendChild(wrapper);
return wrapper;
}
function createOpenRow(targetBox, labelText, onClick) {
const wrapper = document.createElement('div');
wrapper.style.cssText = "margin-bottom:10px;display:flex;justify-content:space-between;align-items:center;background:rgba(150,150,150,0.04);padding:8px 14px;border-radius:8px;border:1px solid rgba(150,150,150,0.06);";
const label = document.createElement('span');
label.className = 'legit-toggle-label';
label.innerText = labelText;
label.style.cssText = "font-size:16px;font-weight:900;transition:color 0.2s;";
const btn = document.createElement('button');
btn.innerText = "Open";
btn.style.cssText = "width:70px;height:30px;border:2px solid rgba(0,0,0,0.15);border-radius:6px;font-weight:900;cursor:pointer;font-family:'Baloo Paaji',cursive;transition:all 0.2s;font-size:14px;color:white;background:#5a5a62;box-shadow:0 3px 0 #3a3a42;";
btn.onclick = () => { if (onClick) onClick(); };
btn.onmousedown = () => { btn.style.boxShadow = "none"; btn.style.transform = "translateY(3px)"; };
btn.onmouseup = () => { btn.style.boxShadow = "0 3px 0 #3a3a42"; btn.style.transform = "translateY(0)"; };
wrapper.appendChild(label);
wrapper.appendChild(btn);
targetBox.appendChild(wrapper);
return wrapper;
}
function createMiniSwitch(parentEl, labelText, configKey, onToggle) {
const row = document.createElement('div');
row.className = 'legit-mini-row';
const label = document.createElement('span');
label.className = 'legit-mini-label';
label.innerText = labelText;
const switchContainer = document.createElement('label');
switchContainer.className = 'legit-mini-switch';
const input = document.createElement('input');
input.type = 'checkbox';
input.checked = config[configKey];
const slider = document.createElement('span');
slider.className = 'legit-mini-slider';
input.addEventListener('change', () => {
config[configKey] = input.checked;
saveConfig();
if (onToggle) onToggle(input.checked);
});
switchContainer.appendChild(input);
switchContainer.appendChild(slider);
row.appendChild(label);
row.appendChild(switchContainer);
parentEl.appendChild(row);
}
function createSlider(parentEl, labelText, configKey, min, max, step, callback) {
const row = document.createElement('div');
row.className = 'legit-mini-row';
row.style.marginBottom = '6px';
const label = document.createElement('span');
label.className = 'legit-mini-label';
label.innerText = labelText;
const valDisplay = document.createElement('span');
valDisplay.innerText = config[configKey];
valDisplay.style.cssText = "font-weight:900; font-size:13px; min-width:24px; text-align:right;";
const input = document.createElement('input');
input.type = 'range';
input.min = min;
input.max = max;
input.step = step;
input.value = config[configKey];
input.style.cssText = "width: 80px; cursor: pointer; accent-color: #a4cc4f;";
let _rafId = null;
input.addEventListener('input', () => {
config[configKey] = parseFloat(input.value);
valDisplay.innerText = config[configKey];
saveConfig();
if (callback) {
if (_rafId) cancelAnimationFrame(_rafId);
_rafId = requestAnimationFrame(() => { _rafId = null; callback(); });
}
});
const rightContainer = document.createElement('div');
rightContainer.style.cssText = "display:flex; align-items:center; gap:8px;";
rightContainer.appendChild(input);
rightContainer.appendChild(valDisplay);
row.appendChild(label);
row.appendChild(rightContainer);
parentEl.appendChild(row);
}
function createStepControl(parentEl, labelText, configKey, min, max, step, displayFn, callback) {
const row = document.createElement('div');
row.className = 'legit-mini-row';
row.style.marginBottom = '6px';
const label = document.createElement('span');
label.className = 'legit-mini-label';
label.innerText = labelText;
const fmt = displayFn || ((v) => v);
const valDisplay = document.createElement('span');
valDisplay.style.cssText = "font-weight:900; font-size:13px; min-width:36px; text-align:center; font-family:'Baloo Paaji',cursive;";
valDisplay.innerText = fmt(config[configKey]);
const btnStyle = (dir) => `
width:26px; height:26px; border-radius:6px; border:none; cursor:pointer;
font-weight:900; font-size:15px; line-height:1;
font-family:'Baloo Paaji',cursive; color:#fff;
background:${dir === '-' ? '#cc5151' : '#a4cc4f'};
box-shadow:0 2px 0 ${dir === '-' ? '#a33838' : '#7ca232'};
transition:transform 0.07s, box-shadow 0.07s;
display:flex; align-items:center; justify-content:center;
`;
const btnDec = document.createElement('button');
btnDec.innerText = '−';
btnDec.style.cssText = btnStyle('-');
const btnInc = document.createElement('button');
btnInc.innerText = '+';
btnInc.style.cssText = btnStyle('+');
const clamp = (v) => Math.round(Math.min(max, Math.max(min, v)) / step) * step;
const apply = () => {
valDisplay.innerText = fmt(config[configKey]);
saveConfig();
if (callback) callback();
};
btnDec.onmousedown = () => { btnDec.style.transform='translateY(2px)'; btnDec.style.boxShadow='none'; };
btnDec.onmouseup = () => { btnDec.style.transform=''; btnDec.style.boxShadow='0 2px 0 #a33838'; };
btnInc.onmousedown = () => { btnInc.style.transform='translateY(2px)'; btnInc.style.boxShadow='none'; };
btnInc.onmouseup = () => { btnInc.style.transform=''; btnInc.style.boxShadow='0 2px 0 #7ca232'; };
btnDec.onclick = () => { config[configKey] = parseFloat(clamp(config[configKey] - step).toFixed(10)); apply(); };
btnInc.onclick = () => { config[configKey] = parseFloat(clamp(config[configKey] + step).toFixed(10)); apply(); };
let _holdTimer = null;
const startHold = (btn, dir) => {
clearInterval(_holdTimer);
_holdTimer = setInterval(() => {
config[configKey] = parseFloat(clamp(config[configKey] + dir * step).toFixed(10));
apply();
}, 80);
};
const stopHold = () => clearInterval(_holdTimer);
btnDec.addEventListener('mousedown', () => startHold(btnDec, -1));
btnInc.addEventListener('mousedown', () => startHold(btnInc, +1));
['mouseup','mouseleave'].forEach(e => {
btnDec.addEventListener(e, stopHold);
btnInc.addEventListener(e, stopHold);
});
const rightContainer = document.createElement('div');
rightContainer.style.cssText = "display:flex; align-items:center; gap:5px;";
rightContainer.appendChild(btnDec);
rightContainer.appendChild(valDisplay);
rightContainer.appendChild(btnInc);
row.appendChild(label);
row.appendChild(rightContainer);
parentEl.appendChild(row);
return { valDisplay, fmt };
}
function createColorPicker(parentEl, labelText, configKey, callback) {
const row = document.createElement('div');
row.className = 'legit-mini-row';
row.style.marginBottom = '6px';
const label = document.createElement('span');
label.className = 'legit-mini-label';
label.innerText = labelText;
const inputContainer = document.createElement('div');
inputContainer.style.cssText = "display:flex; align-items:center; gap:8px;";
const input = document.createElement('input');
input.type = 'color';
input.value = config[configKey];
input.style.cssText = "width: 30px; height: 20px; border: none; cursor: pointer; padding: 0; background: transparent;";
input.addEventListener('input', () => {
config[configKey] = input.value;
saveConfig();
if (callback) callback(input.value);
});
inputContainer.appendChild(input);
row.appendChild(label);
row.appendChild(inputContainer);
parentEl.appendChild(row);
}
createGroupTitle(visualsContent, "VISUAL SETTINGS");
createModalToggleRow(visualsContent, "Ghost Mode", "ghostMode", ghostPanel);
const pct = (v) => Math.round(v * 100) + '%';
const ghostOpacityRefs = [];
createGroupTitle(ghostModalBody, "Trees");
ghostOpacityRefs.push({ key: "ghostOpacityTree", ...createStepControl(ghostModalBody, "Tree", "ghostOpacityTree", 0.0, 1.0, 0.05, pct) });
ghostOpacityRefs.push({ key: "ghostOpacityCherryTree", ...createStepControl(ghostModalBody, "Cherry Tree", "ghostOpacityCherryTree", 0.0, 1.0, 0.05, pct) });
ghostOpacityRefs.push({ key: "ghostOpacityPalmTree", ...createStepControl(ghostModalBody, "Palm Tree", "ghostOpacityPalmTree", 0.0, 1.0, 0.05, pct) });
ghostOpacityRefs.push({ key: "ghostOpacityWoodFarm", ...createStepControl(ghostModalBody, "Wood Farm", "ghostOpacityWoodFarm", 0.0, 1.0, 0.05, pct) });
ghostOpacityRefs.push({ key: "ghostOpacityWoodFarmCherry", ...createStepControl(ghostModalBody, "Wood Farm (Cherry)", "ghostOpacityWoodFarmCherry", 0.0, 1.0, 0.05, pct) });
createGroupTitle(ghostModalBody, "Stone");
ghostOpacityRefs.push({ key: "ghostOpacityRock", ...createStepControl(ghostModalBody, "Rock", "ghostOpacityRock", 0.0, 1.0, 0.05, pct) });
ghostOpacityRefs.push({ key: "ghostOpacityStoneFarm", ...createStepControl(ghostModalBody, "Stone Farm", "ghostOpacityStoneFarm", 0.0, 1.0, 0.05, pct) });
ghostOpacityRefs.push({ key: "ghostOpacityCaveStone0", ...createStepControl(ghostModalBody, "Cave Stone (Small)", "ghostOpacityCaveStone0", 0.0, 1.0, 0.05, pct) });
ghostOpacityRefs.push({ key: "ghostOpacityCaveStone1", ...createStepControl(ghostModalBody, "Cave Stone (Medium)", "ghostOpacityCaveStone1", 0.0, 1.0, 0.05, pct) });
ghostOpacityRefs.push({ key: "ghostOpacityCaveStone2", ...createStepControl(ghostModalBody, "Cave Stone (Large)", "ghostOpacityCaveStone2", 0.0, 1.0, 0.05, pct) });
createGroupTitle(ghostModalBody, "Bush / Cactus");
ghostOpacityRefs.push({ key: "ghostOpacityBush", ...createStepControl(ghostModalBody, "Bush", "ghostOpacityBush", 0.0, 1.0, 0.05, pct) });
ghostOpacityRefs.push({ key: "ghostOpacityBerryFarm", ...createStepControl(ghostModalBody, "Berry Farm", "ghostOpacityBerryFarm", 0.0, 1.0, 0.05, pct) });
ghostOpacityRefs.push({ key: "ghostOpacityCactus", ...createStepControl(ghostModalBody, "Cactus", "ghostOpacityCactus", 0.0, 1.0, 0.05, pct) });
createGroupTitle(ghostModalBody, "Gold / Loot");
ghostOpacityRefs.push({ key: "ghostOpacityGold", ...createStepControl(ghostModalBody, "Gold", "ghostOpacityGold", 0.0, 1.0, 0.05, pct) });
ghostOpacityRefs.push({ key: "ghostOpacityRuby", ...createStepControl(ghostModalBody, "Ruby", "ghostOpacityRuby", 0.0, 1.0, 0.05, pct) });
ghostOpacityRefs.push({ key: "ghostOpacityLootbox", ...createStepControl(ghostModalBody, "Lootbox", "ghostOpacityLootbox", 0.0, 1.0, 0.05, pct) });
ghostOpacityRefs.push({ key: "ghostOpacityChest", ...createStepControl(ghostModalBody, "Chest", "ghostOpacityChest", 0.0, 1.0, 0.05, pct) });
createGroupTitle(ghostModalBody, "Walls / Platforms");
ghostOpacityRefs.push({ key: "ghostOpacityWall", ...createStepControl(ghostModalBody, "Wall", "ghostOpacityWall", 0.0, 1.0, 0.05, pct) });
ghostOpacityRefs.push({ key: "ghostOpacityCastleWall", ...createStepControl(ghostModalBody, "Castle Wall", "ghostOpacityCastleWall", 0.0, 1.0, 0.05, pct) });
ghostOpacityRefs.push({ key: "ghostOpacityPlatform", ...createStepControl(ghostModalBody, "Platform", "ghostOpacityPlatform", 0.0, 1.0, 0.05, pct) });
ghostOpacityRefs.push({ key: "ghostOpacityRoof", ...createStepControl(ghostModalBody, "Roof", "ghostOpacityRoof", 0.0, 1.0, 0.05, pct) });
createGroupTitle(ghostModalBody, "Spikes");
ghostOpacityRefs.push({ key: "ghostOpacitySpike", ...createStepControl(ghostModalBody, "Spike", "ghostOpacitySpike", 0.0, 1.0, 0.05, pct) });
ghostOpacityRefs.push({ key: "ghostOpacityHardSpike", ...createStepControl(ghostModalBody, "Hard Spike", "ghostOpacityHardSpike", 0.0, 1.0, 0.05, pct) });
ghostOpacityRefs.push({ key: "ghostOpacityIceSpike", ...createStepControl(ghostModalBody, "Ice Spike", "ghostOpacityIceSpike", 0.0, 1.0, 0.05, pct) });
ghostOpacityRefs.push({ key: "ghostOpacityBigSpike", ...createStepControl(ghostModalBody, "Big Spike", "ghostOpacityBigSpike", 0.0, 1.0, 0.05, pct) });
createGroupTitle(ghostModalBody, "Structures");
ghostOpacityRefs.push({ key: "ghostOpacityWindmill", ...createStepControl(ghostModalBody, "Windmill", "ghostOpacityWindmill", 0.0, 1.0, 0.05, pct) });
ghostOpacityRefs.push({ key: "ghostOpacityTurret", ...createStepControl(ghostModalBody, "Turret", "ghostOpacityTurret", 0.0, 1.0, 0.05, pct) });
ghostOpacityRefs.push({ key: "ghostOpacityTrap", ...createStepControl(ghostModalBody, "Trap", "ghostOpacityTrap", 0.0, 1.0, 0.05, pct) });
ghostOpacityRefs.push({ key: "ghostOpacityBoost", ...createStepControl(ghostModalBody, "Boost Pad", "ghostOpacityBoost", 0.0, 1.0, 0.05, pct) });
ghostOpacityRefs.push({ key: "ghostOpacityHealpad", ...createStepControl(ghostModalBody, "Heal Pad", "ghostOpacityHealpad", 0.0, 1.0, 0.05, pct) });
ghostOpacityRefs.push({ key: "ghostOpacityBed", ...createStepControl(ghostModalBody, "Bed", "ghostOpacityBed", 0.0, 1.0, 0.05, pct) });
ghostOpacityRefs.push({ key: "ghostOpacityTeleporter", ...createStepControl(ghostModalBody, "Teleporter", "ghostOpacityTeleporter", 0.0, 1.0, 0.05, pct) });
createGroupTitle(ghostModalBody, "Misc");
ghostOpacityRefs.push({ key: "ghostOpacityTornado", ...createStepControl(ghostModalBody, "Tornado", "ghostOpacityTornado", 0.0, 1.0, 0.05, pct) });
ghostOpacityRefs.push({ key: "ghostOpacityFireball", ...createStepControl(ghostModalBody, "Fireball", "ghostOpacityFireball", 0.0, 1.0, 0.05, pct) });
ghostOpacityRefs.push({ key: "ghostOpacityIce0", ...createStepControl(ghostModalBody, "Ice (Small)", "ghostOpacityIce0", 0.0, 1.0, 0.05, pct) });
ghostOpacityRefs.push({ key: "ghostOpacityIce1", ...createStepControl(ghostModalBody, "Ice (Large)", "ghostOpacityIce1", 0.0, 1.0, 0.05, pct) });
let ghostAllOpacityValue = 0.3;
const ghostAllRow = document.createElement('div');
ghostAllRow.className = 'legit-mini-row';
ghostAllRow.style.cssText = "margin-bottom:6px;padding-bottom:10px;border-bottom:2px solid rgba(150,150,150,0.15);";
const ghostAllLabel = document.createElement('span');
ghostAllLabel.className = 'legit-mini-label';
ghostAllLabel.innerText = "Set All Opacity";
ghostAllLabel.style.fontWeight = '900';
const ghostAllValDisplay = document.createElement('span');
ghostAllValDisplay.style.cssText = "font-weight:900; font-size:13px; min-width:36px; text-align:center; font-family:'Baloo Paaji',cursive;";
ghostAllValDisplay.innerText = pct(ghostAllOpacityValue);
const ghostAllBtnStyle = (dir) => `
width:26px; height:26px; border-radius:6px; border:none; cursor:pointer;
font-weight:900; font-size:15px; line-height:1;
font-family:'Baloo Paaji',cursive; color:#fff;
background:${dir === '-' ? '#cc5151' : '#a4cc4f'};
box-shadow:0 2px 0 ${dir === '-' ? '#a33838' : '#7ca232'};
transition:transform 0.07s, box-shadow 0.07s;
display:flex; align-items:center; justify-content:center;
`;
const ghostAllBtnDec = document.createElement('button');
ghostAllBtnDec.innerText = '−';
ghostAllBtnDec.style.cssText = ghostAllBtnStyle('-');
const ghostAllBtnInc = document.createElement('button');
ghostAllBtnInc.innerText = '+';
ghostAllBtnInc.style.cssText = ghostAllBtnStyle('+');
const ghostAllClamp = (v) => Math.round(Math.min(1.0, Math.max(0.0, v)) / 0.05) * 0.05;
function applyGhostAllOpacity() {
ghostAllValDisplay.innerText = pct(ghostAllOpacityValue);
ghostOpacityRefs.forEach(ref => {
config[ref.key] = ghostAllOpacityValue;
ref.valDisplay.innerText = ref.fmt(ghostAllOpacityValue);
});
saveConfig();
}
ghostAllBtnDec.onclick = () => { ghostAllOpacityValue = parseFloat(ghostAllClamp(ghostAllOpacityValue - 0.05).toFixed(10)); applyGhostAllOpacity(); };
ghostAllBtnInc.onclick = () => { ghostAllOpacityValue = parseFloat(ghostAllClamp(ghostAllOpacityValue + 0.05).toFixed(10)); applyGhostAllOpacity(); };
ghostAllBtnDec.onmousedown = () => { ghostAllBtnDec.style.transform='translateY(2px)'; ghostAllBtnDec.style.boxShadow='none'; };
ghostAllBtnDec.onmouseup = () => { ghostAllBtnDec.style.transform=''; ghostAllBtnDec.style.boxShadow='0 2px 0 #a33838'; };
ghostAllBtnInc.onmousedown = () => { ghostAllBtnInc.style.transform='translateY(2px)'; ghostAllBtnInc.style.boxShadow='none'; };
ghostAllBtnInc.onmouseup = () => { ghostAllBtnInc.style.transform=''; ghostAllBtnInc.style.boxShadow='0 2px 0 #7ca232'; };
let _ghostAllHoldTimer = null;
const startGhostAllHold = (dir) => {
clearInterval(_ghostAllHoldTimer);
_ghostAllHoldTimer = setInterval(() => {
ghostAllOpacityValue = parseFloat(ghostAllClamp(ghostAllOpacityValue + dir * 0.05).toFixed(10));
applyGhostAllOpacity();
}, 80);
};
const stopGhostAllHold = () => clearInterval(_ghostAllHoldTimer);
ghostAllBtnDec.addEventListener('mousedown', () => startGhostAllHold(-1));
ghostAllBtnInc.addEventListener('mousedown', () => startGhostAllHold(+1));
['mouseup','mouseleave'].forEach(e => {
ghostAllBtnDec.addEventListener(e, stopGhostAllHold);
ghostAllBtnInc.addEventListener(e, stopGhostAllHold);
});
const ghostAllRightContainer = document.createElement('div');
ghostAllRightContainer.style.cssText = "display:flex; align-items:center; gap:5px;";
ghostAllRightContainer.appendChild(ghostAllBtnDec);
ghostAllRightContainer.appendChild(ghostAllValDisplay);
ghostAllRightContainer.appendChild(ghostAllBtnInc);
ghostAllRow.appendChild(ghostAllLabel);
ghostAllRow.appendChild(ghostAllRightContainer);
ghostModalBody.insertBefore(ghostAllRow, ghostModalBody.firstChild);
const hitboxPanel = createFloatingPanel("Hitboxes");
createModalToggleRow(visualsContent, "Hitboxes", "hitbox", hitboxPanel);
createColorPicker(hitboxPanel.body, "Player", "hitboxColorPlayer");
createColorPicker(hitboxPanel.body, "Mob", "hitboxColorMob");
createColorPicker(hitboxPanel.body, "Block", "hitboxColorBlock");
createStepControl(hitboxPanel.body, "Line Thickness", "hitboxWidth", 0.5, 8.0, 0.5, (v) => v.toFixed(1) + 'px');
const tracersPanel = createFloatingPanel("Tracers");
createModalToggleRow(visualsContent, "Tracers", "tracers", tracersPanel);
createColorPicker(tracersPanel.body, "Enemy Line Color", "tracerColorEnemy");
createColorPicker(tracersPanel.body, "Ally Line Color", "tracerColorAlly");
createColorPicker(tracersPanel.body, "Mob Line Color", "tracerColorMob");
createStepControl(tracersPanel.body, "Line Thickness", "tracerWidth", 0.5, 5.0, 0.5, (v) => v.toFixed(1) + 'px');
createStepControl(tracersPanel.body, "Line Opacity", "tracerOpacity", 0.1, 1.0, 0.1, (v) => Math.round(v * 100) + '%');
createMiniSwitch(tracersPanel.body, "Players (Enemy)", "tracerPlayer");
createMiniSwitch(tracersPanel.body, "Players (Ally)", "tracerAlly");
createMiniSwitch(tracersPanel.body, "Cow", "tracerCow");
createMiniSwitch(tracersPanel.body, "Duck", "tracerDuck");
createMiniSwitch(tracersPanel.body, "Wolf", "tracerWolf");
createMiniSwitch(tracersPanel.body, "Shark", "tracerShark");
createMiniSwitch(tracersPanel.body, "Crocodile", "tracerCrocodile");
createMiniSwitch(tracersPanel.body, "Golden Cow", "tracerGcow");
createMiniSwitch(tracersPanel.body, "Mammoth", "tracerMammoth");
createMiniSwitch(tracersPanel.body, "Dragon", "tracerDragon");
createToggle(visualsContent, "Big Shop & Transparent UI", "transparentUI", updateCSS);
createGroupTitle(hudContent, "HUD INDICATORS");
createToggle(hudContent, "Item Counter", "itemCounter");
const hpPanel = createFloatingPanel("Better Health Bar");
const hpSubPanel = hpPanel.body;
createModalToggleRow(hudContent, "Better Health Bar", "betterHealthBar", hpPanel);
(() => {
const headerRow = document.createElement('div');
headerRow.style.cssText = "display:flex;align-items:center;margin-bottom:4px;";
const headerSpacer = document.createElement('span');
headerSpacer.style.cssText = "flex:1;";
const allyHeader = document.createElement('span');
allyHeader.className = 'legit-mini-label';
allyHeader.innerText = "Ally";
allyHeader.style.cssText = "width:52px;text-align:center;font-size:12px;";
const enemyHeader = document.createElement('span');
enemyHeader.className = 'legit-mini-label';
enemyHeader.innerText = "Enemy";
enemyHeader.style.cssText = "width:52px;text-align:center;font-size:12px;";
headerRow.appendChild(headerSpacer);
headerRow.appendChild(allyHeader);
headerRow.appendChild(enemyHeader);
hpSubPanel.appendChild(headerRow);
function createDualColorRow(parentEl, labelText, allyKey, enemyKey) {
const row = document.createElement('div');
row.className = 'legit-mini-row';
row.style.marginBottom = '6px';
const label = document.createElement('span');
label.className = 'legit-mini-label';
label.innerText = labelText;
label.style.flex = '1';
const makeColorCell = (configKey) => {
const wrap = document.createElement('div');
wrap.style.cssText = "width:52px;display:flex;justify-content:center;align-items:center;";
const input = document.createElement('input');
input.type = 'color';
input.value = config[configKey];
input.style.cssText = "width:30px;height:22px;border:none;cursor:pointer;padding:0;background:transparent;border-radius:4px;";
input.addEventListener('input', () => { config[configKey] = input.value; saveConfig(); });
wrap.appendChild(input);
return wrap;
};
row.appendChild(label);
row.appendChild(makeColorCell(allyKey));
row.appendChild(makeColorCell(enemyKey));
parentEl.appendChild(row);
}
createDualColorRow(hpSubPanel, "Full HP", "hpColorAllyHigh", "hpColorEnemyHigh");
createDualColorRow(hpSubPanel, "Half HP", "hpColorAllyMed", "hpColorEnemyMed");
createDualColorRow(hpSubPanel, "Low HP", "hpColorAllyLow", "hpColorEnemyLow");
})();
createMiniSwitch(hpSubPanel, "Colored Health Bar", "coloredHealthBar");
createMiniSwitch(hpSubPanel, "Smooth Health Bar", "smoothHealthBar");
createMiniSwitch(hpSubPanel, "Show % Health", "showPercentHealth");
const statsPanel = createFloatingPanel("Stats Overlay");
const statsSubPanel = statsPanel.body;
createModalToggleRow(hudContent, "Stats Overlay", "showOverlay", statsPanel, (val) => {
const ov = document.getElementById('stats-overlay-container');
if (ov) ov.style.display = val ? 'flex' : 'none';
});
createMiniSwitch(statsSubPanel, "Lock Overlay Position", "statsLocked", (val) => {
document.getElementById('stats-drag-handle').style.display = val ? 'none' : 'flex';
});
createMiniSwitch(statsSubPanel, "Show Server Name", "statsShowServer", (val) => {
document.getElementById('stat-server').style.display = val ? 'block' : 'none';
});
createMiniSwitch(statsSubPanel, "Show FPS Counter", "statsShowFps", (val) => {
document.getElementById('stat-fps').style.display = val ? 'block' : 'none';
});
createMiniSwitch(statsSubPanel, "Show CPS Counter", "statsShowCps", (val) => {
document.getElementById('stat-cps').style.display = val ? 'block' : 'none';
});
createMiniSwitch(statsSubPanel, "Show Ping Latency", "statsShowPing", (val) => {
document.getElementById('stat-ping').style.display = val ? 'block' : 'none';
});
function applyOverlayStyle() {
const sc = document.getElementById('stats-overlay-container');
if (!sc) return;
const hex = config.overlayBgColor || '#0a0a0a';
const r = parseInt(hex.slice(1,3),16), g = parseInt(hex.slice(3,5),16), b = parseInt(hex.slice(5,7),16);
sc.style.background = `rgba(${r},${g},${b},${config.overlayBgOpacity})`;
sc.style.color = config.overlayTextColor;
sc.style.fontSize = config.overlayFontSize + 'px';
}
createStepControl(statsSubPanel, "Background Opacity", "overlayBgOpacity", 0.0, 1.0, 0.05, (v) => Math.round(v * 100) + '%', applyOverlayStyle);
createColorPicker(statsSubPanel, "Background Color", "overlayBgColor", applyOverlayStyle);
createColorPicker(statsSubPanel, "Text Color", "overlayTextColor", applyOverlayStyle);
createStepControl(statsSubPanel, "Font Size (px)", "overlayFontSize", 10, 30, 1, (v) => v + 'px', applyOverlayStyle);
const kvPanel = createFloatingPanel("Key Viewer");
const kvSubPanel = kvPanel.body;
createModalToggleRow(hudContent, "Key Viewer", "keyViewer", kvPanel, (val) => {
const kv = document.getElementById('kv-overlay-container');
if (kv) kv.style.display = val ? 'flex' : 'none';
});
createMiniSwitch(kvSubPanel, "Lock Viewer Position", "kvLocked", (val) => {
document.getElementById('kv-drag-handle').style.display = val ? 'none' : 'flex';
});
function applyKvStyle() {
const kvc = document.getElementById('kv-overlay-container');
if (!kvc) return;
const hex = config.kvBgColor || '#0a0a0a';
const r = parseInt(hex.slice(1,3),16), g = parseInt(hex.slice(3,5),16), b = parseInt(hex.slice(5,7),16);
kvc.style.background = `rgba(${r},${g},${b},${config.kvBgOpacity})`;
const base = config.kvCellSize;
const cellPx = base + 'px';
const mousePx = (base * 2 + 8) + 'px';
const spacePx = (base * 4 + 24) + 'px';
const fontSize = Math.round(base * 0.32) + 'px';
kvc.querySelectorAll('.kv-cell').forEach(cell => {
cell.style.color = config.kvTextColor;
cell.style.height = cellPx;
cell.style.fontSize = fontSize;
if (cell.classList.contains('mouse-btn')) {
cell.style.width = mousePx;
} else if (cell.classList.contains('space-btn')) {
cell.style.width = spacePx;
} else {
cell.style.width = cellPx;
}
});
}
createStepControl(kvSubPanel, "Background Opacity", "kvBgOpacity", 0.0, 1.0, 0.05, (v) => Math.round(v * 100) + '%', applyKvStyle);
createColorPicker(kvSubPanel, "Background Color", "kvBgColor", applyKvStyle);
createColorPicker(kvSubPanel, "Text Color", "kvTextColor", applyKvStyle);
createStepControl(kvSubPanel, "Key Size (px)", "kvCellSize", 28, 80, 2, (v) => v + 'px', applyKvStyle);
createGroupTitle(systemContent, "SYSTEM & AD-BLOCKING");
createToggle(systemContent, "Adblock", "removeAds", updateCSS);
createOpenRow(systemContent, "VPN", () => vpnPanel.open());
createOpenRow(systemContent, "Music", () => musicPanel.open());
const infoCard = document.createElement('div');
infoCard.style.cssText = "background:rgba(150,150,150,0.05);padding:25px;border-radius:15px;border:2px solid #5a5a62;box-shadow:0 5px 15px rgba(0,0,0,0.1);width:85%;";
creditContent.appendChild(infoCard);
const cardTitle = document.createElement('h3');
cardTitle.innerText = "SCRIPT DEVELOPMENT INFORMATION";
cardTitle.style.cssText = "margin:0 0 15px 0;font-size:20px;letter-spacing:1px;";
infoCard.appendChild(cardTitle);
const detailsText = document.createElement('p');
detailsText.innerHTML = "Made by <span style='font-size:24px;'>Normalplayer</span><br><br>Version: <span style='font-size:20px;'>v2.2.2</span><br><span style='font-size:16px; color:#a4cc4f;'></span>";
detailsText.style.cssText = "font-size:18px;line-height:1.6;margin:0 0 20px 0;";
infoCard.appendChild(detailsText);
const linkBtn = document.createElement('button');
linkBtn.innerText = "Open Sploop Source Viewer";
linkBtn.style.cssText = "padding:10px 20px;background:#5a5a62;border:none;color:#fff;font-weight:900;border-radius:8px;cursor:pointer;font-family:'Baloo Paaji';font-size:15px;box-shadow:0 4px 0 #3a3a42;transition:all 0.1s;";
linkBtn.onclick = () => window.open("https://bruhhh30092012-max.github.io/sploop-source-viewer-v0.3/", "_blank");
linkBtn.onmousedown = () => { linkBtn.style.boxShadow = "none"; linkBtn.style.transform = "translateY(4px)"; };
linkBtn.onmouseup = () => { linkBtn.style.transform = "translateY(0)"; linkBtn.style.boxShadow = "0 4px 0 #3a3a42"; };
infoCard.appendChild(linkBtn);
const compartments = [
{ btn: navVisualsBtn, box: visualsContent },
{ btn: navHudBtn, box: hudContent },
{ btn: navSystemBtn, box: systemContent },
{ btn: navCreditBtn, box: creditContent }
];
function shiftCompartment(targetIdx) {
compartments.forEach((section, idx) => {
const isActive = idx === targetIdx;
section.box.style.display = isActive ? (section.box === creditContent ? "flex" : "block") : "none";
const isLight = config.lightMode;
if (isActive) {
section.btn.style.backgroundColor = isLight ? "#000000" : "#5a5a62";
section.btn.style.color = "#ffffff";
} else {
section.btn.style.backgroundColor = isLight ? "#e5e5e7" : "#25252a";
section.btn.style.color = isLight ? "#000000" : "#b5b5bd";
}
});
}
navVisualsBtn.onclick = () => shiftCompartment(0);
navHudBtn.onclick = () => shiftCompartment(1);
navSystemBtn.onclick = () => shiftCompartment(2);
navCreditBtn.onclick = () => shiftCompartment(3);
document.body.appendChild(menuContainer);
toggleInput.onchange = () => {
config.lightMode = toggleInput.checked;
applyGlobalTheme();
shiftCompartment(compartments.findIndex(s => s.box.style.display === "block" || s.box.style.display === "flex"));
saveConfig();
};
let isMenuOpen = false;
function toggleMenu(forceState) {
isMenuOpen = (typeof forceState !== 'undefined') ? forceState : !isMenuOpen;
menuContainer.style.display = isMenuOpen ? 'block' : 'none';
if (isMenuOpen) {
allFloatingPanels.forEach(p => p.restoreForMenu());
} else {
allFloatingPanels.forEach(p => p.hideForMenu());
}
}
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
e.preventDefault();
e.stopImmediatePropagation();
toggleMenu();
}
});
const overlayCanvas = document.createElement("canvas");
overlayCanvas.id = 'hitbox-overlay-canvas';
overlayCanvas.width = window.innerWidth;
overlayCanvas.height = window.innerHeight;
Object.assign(overlayCanvas.style, {
position: "absolute", top: "0", left: "0",
pointerEvents: "none", zIndex: "50"
});
document.body.appendChild(overlayCanvas);
const octx = overlayCanvas.getContext("2d");
const homepageZStyle = document.createElement('style');
homepageZStyle.innerHTML = `
#homepage { z-index: 75 !important; }
`;
document.head.appendChild(homepageZStyle);
const statsStyle = document.createElement('style');
statsStyle.innerHTML = `
#stats-overlay-container {
position: fixed;
top: 20px; left: 20px;
border: 1px solid transparent;
padding: 10px 18px 12px 18px;
border-radius: 14px; z-index: 999999;
display: ${config.showOverlay ? 'flex' : 'none'};
flex-direction: column; gap: 4px;
font-family: 'Baloo Paaji', cursive, sans-serif;
font-weight: 900; user-select: none;
pointer-events: none; font-size: 17px;
letter-spacing: 0.5px;
}
#stats-drag-handle {
height: 16px; border-radius: 6px;
cursor: move; pointer-events: auto;
display: flex; align-items: center;
justify-content: center; font-size: 9px;
letter-spacing: 3px; margin-bottom: 3px;
}
#stats-drag-handle:hover {
background: rgba(150, 150, 150, 0.25)!important;
color: currentColor!important;
}
.stat-line { white-space: nowrap; }
`;
document.head.appendChild(statsStyle);
const statsContainer = document.createElement('div');
statsContainer.id = 'stats-overlay-container';
statsContainer.innerHTML = `
<div id="stats-drag-handle">••••</div>
<div id="stat-server" class="stat-line">SERVER: Loading...</div>
<div id="stat-fps" class="stat-line">FPS: --</div>
<div id="stat-cps" class="stat-line">CPS: 0</div>
<div id="stat-ping" class="stat-line">PING: --ms</div>
`;
document.body.appendChild(statsContainer);
const savedStatsX = localStorage.getItem('stats-pos-x');
const savedStatsY = localStorage.getItem('stats-pos-y');
if (savedStatsX && savedStatsY) {
statsContainer.style.left = savedStatsX;
statsContainer.style.top = savedStatsY;
}
document.getElementById('stats-drag-handle').style.display = config.statsLocked ? 'none' : 'flex';
document.getElementById('stat-server').style.display = config.statsShowServer ? 'block' : 'none';
document.getElementById('stat-fps').style.display = config.statsShowFps ? 'block' : 'none';
document.getElementById('stat-cps').style.display = config.statsShowCps ? 'block' : 'none';
document.getElementById('stat-ping').style.display = config.statsShowPing ? 'block' : 'none';
(() => {
const sc = document.getElementById('stats-overlay-container');
if (!sc) return;
const hex = config.overlayBgColor || '#0a0a0a';
const r = parseInt(hex.slice(1,3),16), g = parseInt(hex.slice(3,5),16), b = parseInt(hex.slice(5,7),16);
sc.style.background = `rgba(${r},${g},${b},${config.overlayBgOpacity})`;
sc.style.color = config.overlayTextColor;
sc.style.fontSize = config.overlayFontSize + 'px';
})();
const statsHandle = document.getElementById('stats-drag-handle');
let isStatsDragging = false;
let statsOffsetX = 0, statsOffsetY = 0;
statsHandle.addEventListener('mousedown', (e) => {
if(config.statsLocked) return;
isStatsDragging = true;
const rect = statsContainer.getBoundingClientRect();
statsOffsetX = e.clientX - rect.left;
statsOffsetY = e.clientY - rect.top;
e.preventDefault();
});
let frameCount = 0, fpsStartTime = performance.now(), fps = 0;
let serverName = "Unknown";
let ping = '...';
const _origWSSend = WebSocket.prototype.send;
WebSocket.prototype.send = function (...args) {
this._lastSentAt = performance.now();
if (!this._pingListenerAttached) {
this._pingListenerAttached = true;
this.addEventListener('message', () => {
if (this._lastSentAt > 0) {
const elapsed = performance.now() - this._lastSentAt;
if (elapsed < 2000) ping = Math.round(elapsed);
this._lastSentAt = 0;
}
});
}
return _origWSSend.apply(this, args);
};
setInterval(() => {
const select = document.getElementById("server-select");
if (select?.options.length > 0) serverName = select.options[select.selectedIndex].text;
}, 1000);
let clickTimestamps = [];
document.addEventListener("mousedown", () => {
const now = Date.now();
clickTimestamps.push(now);
clickTimestamps = clickTimestamps.filter(t => now - t < 1000);
});
window.addEventListener("resize", () => {
overlayCanvas.width = window.innerWidth;
overlayCanvas.height = window.innerHeight;
});
setTimeout(() => {
['#grid-toggle', '#native-friendly-indicator'].forEach(id => {
const el = document.querySelector(id);
if (el) el.click();
});
}, 2000);
{
const kvStyle = document.createElement('style');
kvStyle.innerHTML = `
#kv-overlay-container {
position: fixed; bottom: 180px; left: 20px;
border: 1px solid transparent; padding: 10px;
border-radius: 14px; z-index: 999999;
display: ${config.keyViewer ? 'flex' : 'none'};
flex-direction: column; gap: 8px;
font-family: 'Baloo Paaji', cursive, sans-serif;
font-weight: 900; user-select: none; pointer-events: none;
}
#kv-drag-handle {
height: 18px; border-radius: 6px; cursor: move; pointer-events: auto;
display: flex; align-items: center; justify-content: center;
font-size: 9px; letter-spacing: 3px;
}
#kv-drag-handle:hover {
background: rgba(150, 150, 150, 0.25)!important;
color: currentColor!important;
}
.kv-row { display: flex; gap: 8px; justify-content: center; }
.kv-cell {
width: 46px; height: 46px; border-radius: 8px;
display: flex; align-items: center; justify-content: center;
font-weight: 900; font-size: 15px; font-family: 'Baloo Paaji', cursive, sans-serif;
}
.kv-cell.mouse-btn { width: 100px; }
.kv-cell.space-btn { width: 208px; font-size: 13px; letter-spacing: 1px; }
#kv-overlay-container:not(.light-active) .kv-cell.active {
background: #ffffff !important; color: #050505 !important;
border-color: #ffffff !important; box-shadow: 0 0 18px rgba(255, 255, 255, 0.7);
transform: scale(0.92);
}
#kv-overlay-container.light-active .kv-cell.active {
background: #111111 !important; color: #ffffff !important;
border-color: #111111 !important; box-shadow: 0 0 15px rgba(0, 0, 0, 0.3);
transform: scale(0.92);
}
`;
document.head.appendChild(kvStyle);
const kvContainer = document.createElement('div');
kvContainer.id = 'kv-overlay-container';
kvContainer.innerHTML = `
<div id="kv-drag-handle">••••</div>
<div class="kv-row">
<div class="kv-cell mouse-btn" id="kv-lmb">LMB</div>
<div class="kv-cell mouse-btn" id="kv-rmb">RMB</div>
</div>
<div class="kv-row">
<div class="kv-cell" id="kv-q">Q</div>
<div class="kv-cell" id="kv-w">W</div>
<div class="kv-cell" id="kv-e">E</div>
<div class="kv-cell" id="kv-r">R</div>
</div>
<div class="kv-row">
<div class="kv-cell" id="kv-a">A</div>
<div class="kv-cell" id="kv-s">S</div>
<div class="kv-cell" id="kv-d">D</div>
<div class="kv-cell" id="kv-f">F</div>
</div>
<div class="kv-row">
<div class="kv-cell space-btn" id="kv-space">SPACE</div>
</div>
`;
document.body.appendChild(kvContainer);
const savedX = localStorage.getItem('kv-pos-x');
const savedY = localStorage.getItem('kv-pos-y');
if (savedX && savedY) {
kvContainer.style.left = savedX;
kvContainer.style.top = savedY;
kvContainer.style.bottom = 'auto';
}
document.getElementById('kv-drag-handle').style.display = config.kvLocked ? 'none' : 'flex';
(() => {
const kvc = document.getElementById('kv-overlay-container');
if (!kvc) return;
const hex = config.kvBgColor || '#0a0a0a';
const r = parseInt(hex.slice(1,3),16), g = parseInt(hex.slice(3,5),16), b = parseInt(hex.slice(5,7),16);
kvc.style.background = `rgba(${r},${g},${b},${config.kvBgOpacity})`;
const base = config.kvCellSize;
const cellPx = base + 'px';
const mousePx = (base * 2 + 8) + 'px';
const spacePx = (base * 4 + 24) + 'px';
const fontSize = Math.round(base * 0.32) + 'px';
kvc.querySelectorAll('.kv-cell').forEach(cell => {
cell.style.color = config.kvTextColor;
cell.style.height = cellPx;
cell.style.fontSize = fontSize;
if (cell.classList.contains('mouse-btn')) {
cell.style.width = mousePx;
} else if (cell.classList.contains('space-btn')) {
cell.style.width = spacePx;
} else {
cell.style.width = cellPx;
}
});
})();
const handle = document.getElementById('kv-drag-handle');
let isDragging = false;
let offsetX = 0, offsetY = 0;
handle.addEventListener('mousedown', (e) => {
if(config.kvLocked) return;
isDragging = true;
const rect = kvContainer.getBoundingClientRect();
offsetX = e.clientX - rect.left;
offsetY = e.clientY - rect.top;
e.preventDefault();
});
window.addEventListener('mousemove', (e) => {
if (isDragging) {
let x = e.clientX - offsetX;
let y = e.clientY - offsetY;
x = Math.max(0, Math.min(x, window.innerWidth - kvContainer.offsetWidth));
y = Math.max(0, Math.min(y, window.innerHeight - kvContainer.offsetHeight));
kvContainer.style.left = x + 'px';
kvContainer.style.top = y + 'px';
kvContainer.style.bottom = 'auto';
}
if (isStatsDragging) {
let x = e.clientX - statsOffsetX;
let y = e.clientY - statsOffsetY;
x = Math.max(0, Math.min(x, window.innerWidth - statsContainer.offsetWidth));
y = Math.max(0, Math.min(y, window.innerHeight - statsContainer.offsetHeight));
statsContainer.style.left = x + 'px';
statsContainer.style.top = y + 'px';
}
});
window.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
localStorage.setItem('kv-pos-x', kvContainer.style.left);
localStorage.setItem('kv-pos-y', kvContainer.style.top);
}
if (isStatsDragging) {
isStatsDragging = false;
localStorage.setItem('stats-pos-x', statsContainer.style.left);
localStorage.setItem('stats-pos-y', statsContainer.style.top);
}
});
const keyElements = {
'KeyQ': document.getElementById('kv-q'),
'KeyW': document.getElementById('kv-w'),
'KeyE': document.getElementById('kv-e'),
'KeyR': document.getElementById('kv-r'),
'KeyA': document.getElementById('kv-a'),
'KeyS': document.getElementById('kv-s'),
'KeyD': document.getElementById('kv-d'),
'KeyF': document.getElementById('kv-f'),
'Space': document.getElementById('kv-space')
};
const mouseElements = {
0: document.getElementById('kv-lmb'),
2: document.getElementById('kv-rmb')
};
window.addEventListener('keydown', (e) => {
if (!e.isTrusted) return;
const code = e.code;
if (keyElements[code]) keyElements[code].classList.add('active');
}, true);
window.addEventListener('keyup', (e) => {
if (!e.isTrusted) return;
const code = e.code;
if (keyElements[code]) keyElements[code].classList.remove('active');
}, true);
window.addEventListener('mousedown', (e) => {
if (!e.isTrusted) return;
if (mouseElements[e.button]) mouseElements[e.button].classList.add('active');
}, true);
window.addEventListener('mouseup', (e) => {
if (!e.isTrusted) return;
if (mouseElements[e.button]) mouseElements[e.button].classList.remove('active');
}, true);
window.addEventListener('blur', () => {
Object.values(keyElements).forEach(el => el.classList.remove('active'));
Object.values(mouseElements).forEach(el => el.classList.remove('active'));
});
}
applyGlobalTheme();
if (config.musicEnabled && config.musicPlaylist.length && config.musicCurrentIndex > -1) {
playTrackAtIndex(config.musicCurrentIndex);
}
shiftCompartment(0);
function loop() {
requestAnimationFrame(loop);
const now = performance.now();
frameCount++;
if (now - fpsStartTime >= 1000) {
fps = frameCount;
frameCount = 0;
fpsStartTime = now;
}
const cps = clickTimestamps.filter(t => Date.now() - t < 1000).length;
octx.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);
if (config.hitbox && circlesToDraw.length > 0) {
const nowHb = Date.now();
if (window._frameHpBars) {
window._frameHpBars = window._frameHpBars.filter(b => nowHb - b.time < 100);
}
const hpBarsHb = window._frameHpBars || [];
octx.lineWidth = config.hitboxWidth;
for (const c of circlesToDraw) {
octx.save();
octx.setTransform(c.transform);
if (c.isPlayer) {
const lx = c.x + c.width / 2, ly = c.y + c.height / 2;
const m = c.transform;
const sx = m.a * lx + m.c * ly + m.e;
const sy = m.b * lx + m.d * ly + m.f;
let isAlly = false;
let foundBar = false;
let minBD = Infinity;
for (const bar of hpBarsHb) {
if (bar.isMob) continue;
const bd = Math.hypot(bar.x - sx, bar.y - sy);
if (bd < minBD && bd < 100) { minBD = bd; isAlly = bar.isAlly; foundBar = true; }
}
octx.strokeStyle = config.hitboxColorPlayer;
octx.beginPath();
octx.arc(c.x + c.width / 2, c.y + c.height / 2, 35, 0, Math.PI * 2);
octx.stroke();
} else if (c.isMob) {
octx.strokeStyle = config.hitboxColorMob;
octx.beginPath();
octx.arc(c.x + c.width / 2, c.y + c.height / 2, c.radius || 35, 0, Math.PI * 2);
octx.stroke();
} else if (c.isBlock) {
octx.strokeStyle = config.hitboxColorBlock;
octx.beginPath();
octx.arc(c.x + c.width / 2, c.y + c.height / 2, c.radius, 0, Math.PI * 2);
octx.stroke();
}
octx.restore();
}
}
if (config.tracers && circlesToDraw.length > 0) {
octx.save();
octx.lineWidth = config.tracerWidth;
const canvasCenterX = overlayCanvas.width / 2;
const canvasCenterY = overlayCanvas.height / 2;
const targets = [];
let myPlayer = null;
let minDist = Infinity;
for (const c of circlesToDraw) {
const localX = c.x + c.width / 2;
const localY = c.y + c.height / 2;
const matrix = c.transform;
const screenX = matrix.a * localX + matrix.c * localY + matrix.e;
const screenY = matrix.b * localX + matrix.d * localY + matrix.f;
const entity = { screenX, screenY, isPlayer: c.isPlayer, fileName: c.fileName };
targets.push(entity);
if (c.isPlayer) {
const dist = Math.hypot(screenX - canvasCenterX, screenY - canvasCenterY);
if (dist < minDist) {
minDist = dist;
myPlayer = entity;
}
}
}
const startX = (myPlayer && minDist < 60) ? myPlayer.screenX : canvasCenterX;
const startY = (myPlayer && minDist < 60) ? myPlayer.screenY : canvasCenterY;
const hexToRgba = (hex, alpha) => {
let r = parseInt(hex.slice(1, 3), 16),
g = parseInt(hex.slice(3, 5), 16),
b = parseInt(hex.slice(5, 7), 16);
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
};
const nowTime = Date.now();
if (window._frameHpBars) {
window._frameHpBars = window._frameHpBars.filter(b => nowTime - b.time < 100);
}
const hpBars = window._frameHpBars || [];
for (const target of targets) {
const dist = Math.hypot(target.screenX - startX, target.screenY - startY);
if (dist > 20) {
let shouldDraw = false;
let hexColor = config.tracerColorMob;
if (target.isPlayer) {
let isAlly = false;
let foundTracerBar = false;
let minBarDist = Infinity;
for (const bar of hpBars) {
if (bar.isMob) continue;
const bDist = Math.hypot(bar.x - target.screenX, bar.y - target.screenY);
if (bDist < minBarDist && bDist < 100) {
minBarDist = bDist;
isAlly = bar.isAlly;
foundTracerBar = true;
}
}
hexColor = (foundTracerBar && isAlly) ? config.tracerColorAlly : config.tracerColorEnemy;
shouldDraw = (foundTracerBar && isAlly) ? config.tracerAlly : config.tracerPlayer;
} else {
switch(target.fileName) {
case "cow.png": shouldDraw = config.tracerCow; break;
case "duck.png": shouldDraw = config.tracerDuck; break;
case "wolf.png": shouldDraw = config.tracerWolf; break;
case "shark.png": shouldDraw = config.tracerShark; break;
case "crocodile.png": shouldDraw = config.tracerCrocodile; break;
case "gcow.png": shouldDraw = config.tracerGcow; break;
case "mammoth_body.png": shouldDraw = config.tracerMammoth; break;
case "dragon_2_body.png": shouldDraw = config.tracerDragon; break;
}
}
if (shouldDraw) {
octx.beginPath();
octx.strokeStyle = hexToRgba(hexColor, config.tracerOpacity);
octx.moveTo(startX, startY);
octx.lineTo(target.screenX, target.screenY);
octx.stroke();
}
}
}
octx.restore();
}
circlesToDraw.length = 0;
const kvContainer = document.getElementById('kv-overlay-container');
if (kvContainer) {
if (config.lightMode) kvContainer.classList.add('light-active');
else kvContainer.classList.remove('light-active');
}
if (config.showOverlay) {
const sEl = document.getElementById('stat-server');
const fEl = document.getElementById('stat-fps');
const cEl = document.getElementById('stat-cps');
const pEl = document.getElementById('stat-ping');
if(sEl) sEl.innerText = `SERVER: ${serverName}`;
if(fEl) fEl.innerText = `FPS: ${fps}`;
if(cEl) cEl.innerText = `CPS: ${cps}`;
if(pEl) pEl.innerText = `PING: ${ping}ms`;
}
}
loop();
});
})();