Minefun.io SkyWars VIP Client

Client-side SkyWars HUD, visual controls, diagnostics, and compatible movement helpers for Minefun.io.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Minefun.io SkyWars VIP Client
// @namespace    https://greasyfork.org/userscripts/minefun-skywars-vip
// @version      4.0.1
// @description  Client-side SkyWars HUD, visual controls, diagnostics, and compatible movement helpers for Minefun.io.
// @author       Minefun Modder
// @license      MIT
// @match        https://minefun.io/*
// @match        https://*.minefun.io/*
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function () {
    'use strict';

    const VERSION = '4.0.1';
    const MENU_KEY = ',';
    const STORAGE_KEY = 'MINEFUN_SKYWARS_VIP_V4';

    const Client = {
        name: 'MINEFUN SKY WARS VIP',
        version: VERSION,
        menuOpen: false,
        ready: false,
        uiBuilt: false,
        gameDetected: false,
        lastEngineScan: 0,
        lastStatus: 'Starting...',
        modules: [],
        categories: [
            'SkyWars',
            'Visuals',
            'HUD',
            'Movement',
            'Utility',
            'Diagnostics'
        ],
        engine: null,
        player: null,
        world: null,
        store: null,
        app: null,
        raf: null
    };

    // =========================================================
    // HELPERS
    // =========================================================

    function now() {
        return performance.now();
    }

    function num(value, fallback = 0) {
        return Number.isFinite(Number(value))
            ? Number(value)
            : fallback;
    }

    function firstDefined(...values) {
        for (const value of values) {
            if (value !== undefined && value !== null) {
                return value;
            }
        }

        return undefined;
    }

    function escapeHtml(value) {
        return String(value)
            .replaceAll('&', '&')
            .replaceAll('<', '&lt;')
            .replaceAll('>', '&gt;')
            .replaceAll('"', '&quot;')
            .replaceAll("'", '&#039;');
    }

    function cssEscape(value) {
        try {
            if (
                window.CSS &&
                typeof window.CSS.escape === 'function'
            ) {
                return window.CSS.escape(String(value));
            }
        } catch (_) {}

        return String(value).replace(
            /([\\.#$%&'()*+,/:;<=>?@\[\\\]^`{|}~])/g,
            '\\$1'
        );
    }

    function getCanvas() {
        return document.querySelector('canvas');
    }

    function log(...args) {
        console.log(
            `%c[${Client.name}]`,
            'color:#ff0055;font-weight:bold',
            ...args
        );
    }

    function warn(...args) {
        console.warn(
            `%c[${Client.name}]`,
            'color:#ffaa00;font-weight:bold',
            ...args
        );
    }

    // =========================================================
    // CONFIG
    // =========================================================

    function saveConfig() {
        const config = {};

        for (const module of Client.modules) {
            config[module.name] = !!module.enabled;
        }

        try {
            localStorage.setItem(
                STORAGE_KEY,
                JSON.stringify(config)
            );
        } catch (_) {}
    }

    function loadConfig() {
        let saved = null;

        try {
            saved = localStorage.getItem(
                STORAGE_KEY
            );
        } catch (_) {}

        if (!saved) return;

        try {
            const config = JSON.parse(saved);

            for (const module of Client.modules) {
                if (
                    Object.prototype.hasOwnProperty.call(
                        config,
                        module.name
                    )
                ) {
                    module.enabled =
                        !!config[module.name];
                }
            }
        } catch (error) {
            warn('Config error:', error);
        }
    }

    // =========================================================
    // MODULE SYSTEM
    // =========================================================

    class Module {
        constructor(
            name,
            category,
            description,
            options = {}
        ) {
            this.name = name;
            this.category = category;
            this.description = description;
            this.defaultEnabled =
                !!options.defaultEnabled;
            this.enabled =
                this.defaultEnabled;
            this.status =
                this.enabled ? 'ON' : 'OFF';
            this.reason = '';
            this.settings =
                options.settings || {};
            this.onEnableHandler =
                options.onEnable || null;
            this.onDisableHandler =
                options.onDisable || null;
            this.tickHandler =
                options.onTick || null;
            this.renderHandler =
                options.onRender || null;

            Client.modules.push(this);
        }

        setStatus(status, reason = '') {
            this.status = status;
            this.reason = reason;

            const card =
                document.querySelector(
                    `[data-mod="${cssEscape(this.name)}"]`
                );

            if (!card) return;

            card.classList.toggle(
                'enabled',
                this.enabled
            );

            card.classList.toggle(
                'waiting',
                status === 'WAIT'
            );

            card.classList.toggle(
                'error',
                status === 'ERR'
            );

            const statusNode =
                card.querySelector(
                    '.vip-status'
                );

            if (statusNode) {
                statusNode.textContent =
                    this.enabled
                        ? status
                        : 'OFF';
            }

            const reasonNode =
                card.querySelector(
                    '.vip-reason'
                );

            if (reasonNode) {
                reasonNode.textContent =
                    reason;
            }
        }

        enable() {
            this.enabled = true;

            try {
                if (this.onEnableHandler) {
                    this.onEnableHandler(this);
                }

                if (
                    Client.gameDetected ||
                    this.category === 'Diagnostics'
                ) {
                    this.setStatus(
                        'ON',
                        'Active'
                    );
                } else {
                    this.setStatus(
                        'WAIT',
                        'Waiting for engine'
                    );
                }
            } catch (error) {
                this.setStatus(
                    'ERR',
                    String(error)
                );
            }

            saveConfig();
        }

        disable() {
            try {
                if (this.onDisableHandler) {
                    this.onDisableHandler(this);
                }
            } catch (_) {}

            this.enabled = false;
            this.setStatus('OFF', '');
            saveConfig();
        }

        toggle() {
            if (this.enabled) {
                this.disable();
            } else {
                this.enable();
            }

            refreshHUD();
        }

        onTick() {
            if (!this.enabled) return;

            try {
                if (this.tickHandler) {
                    this.tickHandler(this);
                }
            } catch (error) {
                this.setStatus(
                    'ERR',
                    String(error)
                );
            }
        }

        onRender() {
            if (!this.enabled) return;

            try {
                if (this.renderHandler) {
                    this.renderHandler(this);
                }
            } catch (error) {
                this.setStatus(
                    'ERR',
                    String(error)
                );
            }
        }
    }

    // =========================================================
    // ENGINE BRIDGE
    // =========================================================

    const Engine = {

        lastScan: 0,
        scanInterval: 250,

        scan() {
            const current = now();

            if (
                current - this.lastScan <
                this.scanInterval
            ) {
                return;
            }

            this.lastScan = current;

            Client.app =
                window.app || null;

            if (!Client.app) {
                Client.gameDetected = false;
                Client.engine = null;
                Client.player = null;
                Client.world = null;
                Client.store = null;
                Client.lastStatus =
                    'Waiting for window.app';
                return null;
            }

            let gameState = null;
            let store = null;

            try {
                const vnode =
                    Client.app?._vnode;

                const component =
                    vnode?.component;

                const provides =
                    component
                        ?.appContext
                        ?.provides;

                if (provides) {
                    const symbols =
                        Object.getOwnPropertySymbols(
                            provides
                        );

                    const symbol =
                        symbols.find(
                            item => {
                                try {
                                    return !!provides[item]?._s;
                                } catch (_) {
                                    return false;
                                }
                            }
                        );

                    if (symbol) {
                        store =
                            provides[symbol]._s;
                    }
                }

                if (store?.get) {
                    gameState =
                        store.get('gameState');
                }
            } catch (_) {}

            const world =
                gameState?.gameWorld ||
                gameState?.world ||
                null;

            const player =
                world?.player ||
                gameState?.player ||
                null;

            Client.store = store;
            Client.world = world;
            Client.player = player;

            Client.engine = {
                gameState,
                world,
                player
            };

            Client.gameDetected =
                !!gameState ||
                !!world ||
                !!player;

            Client.lastStatus =
                Client.gameDetected
                    ? 'SkyWars engine connected'
                    : 'Engine found, waiting for player';

            return Client.engine;
        },

        getGameState() {
            this.scan();
            return Client.engine?.gameState || null;
        },

        getWorld() {
            this.scan();
            return Client.world;
        },

        getPlayer() {
            this.scan();
            return Client.player;
        },

        getPosition() {
            const player =
                this.getPlayer();

            return (
                player?.position ||
                player?.pos ||
                player?.transform?.position ||
                null
            );
        },

        getVelocity() {
            const player =
                this.getPlayer();

            return (
                player?.velocity ||
                player?.physics?.velocity ||
                player?.motion ||
                null
            );
        },

        getCollision() {
            const player =
                this.getPlayer();

            return (
                player?.collision ||
                player?.physics?.collision ||
                null
            );
        },

        getHealth() {
            const player =
                this.getPlayer();

            return num(
                firstDefined(
                    player?.health,
                    player?.hp,
                    player?.stats?.health
                ),
                0
            );
        },

        getEntities() {
            const world =
                this.getWorld();

            const candidates = [
                world?.entities,
                world?.players,
                world?.mobs,
                world?.actors,
                world?.objects
            ];

            for (const value of candidates) {
                if (Array.isArray(value)) {
                    return value;
                }

                if (
                    value &&
                    typeof value.values ===
                    'function'
                ) {
                    return Array.from(
                        value.values()
                    );
                }
            }

            return [];
        },

        getJumpSpeed() {
            const velocity =
                this.getVelocity();

            if (!velocity) return undefined;

            return firstDefined(
                velocity.jumpSpeed,
                velocity.jumpVelocity,
                velocity.jumpPower
            );
        },

        setJumpSpeed(value) {
            const velocity =
                this.getVelocity();

            if (!velocity) return false;

            let changed = false;

            if (
                'jumpSpeed' in velocity
            ) {
                velocity.jumpSpeed = value;
                changed = true;
            }

            if (
                'jumpVelocity' in velocity
            ) {
                velocity.jumpVelocity = value;
                changed = true;
            }

            if (
                'jumpPower' in velocity
            ) {
                velocity.jumpPower = value;
                changed = true;
            }

            return changed;
        },

        getHorizontalVelocity() {
            const velocity =
                this.getVelocity();

            if (!velocity) return null;

            const vector =
                velocity.velVec3 ||
                velocity.vector ||
                velocity;

            return {
                x: num(vector.x),
                y: num(vector.y),
                z: num(vector.z)
            };
        },

        scaleHorizontalVelocity(
            multiplier
        ) {
            const velocity =
                this.getVelocity();

            if (!velocity) return false;

            const vector =
                velocity.velVec3 ||
                velocity.vector ||
                velocity;

            if (
                !Number.isFinite(vector.x) ||
                !Number.isFinite(vector.z)
            ) {
                return false;
            }

            vector.x *= multiplier;
            vector.z *= multiplier;

            return true;
        },

        getGameMode() {
            const state =
                this.getGameState();

            return String(
                firstDefined(
                    state?.mode,
                    state?.gameMode,
                    state?.gameType,
                    'unknown'
                )
            );
        },

        isSkyWars() {
            const mode =
                this.getGameMode();

            return mode
                .toLowerCase()
                .includes('sky') ||
                Client.gameDetected;
        },

        getPlayerName() {
            const player =
                this.getPlayer();

            return String(
                firstDefined(
                    player?.name,
                    player?.username,
                    player?.displayName,
                    'Player'
                )
            );
        }
    };

    // =========================================================
    // STATE
    // =========================================================

    const State = {
        originalJumpSpeed: null,
        fpsFrames: 0,
        fpsLast: now(),
        fps: 0,
        canvasFilter: ''
    };

    // =========================================================
    // MODULES
    // =========================================================

    const EngineScanner = new Module(
        'Engine Scanner',
        'Diagnostics',
        'Checks the SkyWars engine continuously.',
        {
            defaultEnabled: true
        }
    );

    EngineScanner.onTick = function () {
        Engine.scan();

        this.setStatus(
            Client.gameDetected
                ? 'ON'
                : 'WAIT',
            Client.lastStatus
        );
    };

    const PlayerInspector = new Module(
        'Player Inspector',
        'Diagnostics',
        'Checks player position, velocity and collision.',
        {}
    );

    PlayerInspector.onRender = function () {
        if (!Client.gameDetected) {
            this.setStatus(
                'WAIT',
                'Player unavailable'
            );
            return;
        }

        const parts = [];

        if (Engine.getPosition()) {
            parts.push('POS');
        }

        if (Engine.getVelocity()) {
            parts.push('VEL');
        }

        if (Engine.getCollision()) {
            parts.push('COL');
        }

        this.setStatus(
            'ON',
            parts.join(' + ') || 'PLAYER'
        );
    };

    const EntityScanner = new Module(
        'Entity Scanner',
        'Diagnostics',
        'Counts entities exposed by the game runtime.',
        {}
    );

    EntityScanner.onRender = function () {
        const entities =
            Engine.getEntities();

        this.setStatus(
            Client.gameDetected
                ? 'ON'
                : 'WAIT',
            `Entities: ${entities.length}`
        );
    };

    const SkyWarsReady = new Module(
        'SkyWars Ready',
        'SkyWars',
        'Shows when a compatible SkyWars player object is detected.',
        {
            defaultEnabled: true
        }
    );

    SkyWarsReady.onRender = function () {
        const node =
            document.getElementById(
                'vip-ready'
            );

        if (!node) return;

        node.textContent =
            Client.gameDetected
                ? 'READY'
                : 'WAITING';

        node.style.color =
            Client.gameDetected
                ? '#00ff8c'
                : '#ffaa00';
    };

    const Crosshair = new Module(
        'Crosshair',
        'Visuals',
        'Adds a simple client-side crosshair.',
        {
            defaultEnabled: true
        }
    );

    Crosshair.onEnable = function () {
        const node =
            document.getElementById(
                'vip-crosshair'
            );

        if (node) {
            node.style.display = 'block';
        }
    };

    Crosshair.onDisable = function () {
        const node =
            document.getElementById(
                'vip-crosshair'
            );

        if (node) {
            node.style.display = 'none';
        }
    };

    const Fullbright = new Module(
        'Fullbright',
        'Visuals',
        'Brightens the game canvas locally.',
        {}
    );

    Fullbright.onEnable = function () {
        applyCanvasFilter();
    };

    Fullbright.onDisable = function () {
        applyCanvasFilter();
    };

    const SaturationBoost = new Module(
        'Saturation Boost',
        'Visuals',
        'Improves color visibility locally.',
        {}
    );

    SaturationBoost.onEnable = function () {
        applyCanvasFilter();
    };

    SaturationBoost.onDisable = function () {
        applyCanvasFilter();
    };

    function applyCanvasFilter() {
        const canvas =
            getCanvas();

        if (!canvas) return;

        const filters = [];

        if (Fullbright.enabled) {
            filters.push(
                'brightness(1.5)'
            );
        }

        if (SaturationBoost.enabled) {
            filters.push(
                'saturate(1.35)'
            );
        }

        canvas.style.filter =
            filters.join(' ') ||
            State.canvasFilter ||
            '';
    }

    const FPSCounter = new Module(
        'FPS Counter',
        'HUD',
        'Displays live browser FPS.',
        {}
    );

    FPSCounter.onRender = function () {
        const current = now();

        State.fpsFrames++;

        if (
            current - State.fpsLast >=
            500
        ) {
            State.fps =
                State.fpsFrames /
                (
                    (current - State.fpsLast) /
                    1000
                );

            State.fpsFrames = 0;
            State.fpsLast = current;
        }

        const node =
            document.getElementById(
                'vip-hud-fps'
            );

        if (node) {
            node.textContent =
                `FPS: ${Math.round(State.fps)}`;
        }
    };

    const Coordinates = new Module(
        'Coordinates',
        'HUD',
        'Displays your current coordinates.',
        {}
    );

    Coordinates.onRender = function () {
        const node =
            document.getElementById(
                'vip-hud-coords'
            );

        if (!node) return;

        const position =
            Engine.getPosition();

        if (!position) {
            node.textContent =
                'X: ? Y: ? Z: ?';

            return;
        }

        node.textContent =
            `X: ${num(position.x).toFixed(1)} ` +
            `Y: ${num(position.y).toFixed(1)} ` +
            `Z: ${num(position.z).toFixed(1)}`;
    };

    const HealthHUD = new Module(
        'Health HUD',
        'HUD',
        'Displays the health value exposed by the game.',
        {}
    );

    HealthHUD.onRender = function () {
        const node =
            document.getElementById(
                'vip-hud-health'
            );

        if (node) {
            node.textContent =
                `HP: ${Engine.getHealth().toFixed(0)}`;
        }
    };

    const PlayerNameHUD = new Module(
        'Player Name',
        'SkyWars',
        'Displays the detected player name.',
        {}
    );

    PlayerNameHUD.onRender = function () {
        const node =
            document.getElementById(
                'vip-hud-name'
            );

        if (node) {
            node.textContent =
                `NAME: ${Engine.getPlayerName()}`;
        }
    };

    const HighJump = new Module(
        'High Jump',
        'Movement',
        'Uses the exposed jump speed field when available.',
        {
            settings: {
                jumpSpeed: 15
            }
        }
    );

    HighJump.onEnable = function () {
        const jump =
            Engine.getJumpSpeed();

        if (
            Number.isFinite(jump) &&
            State.originalJumpSpeed === null
        ) {
            State.originalJumpSpeed = jump;
        }

        const changed =
            Engine.setJumpSpeed(
                this.settings.jumpSpeed
            );

        this.setStatus(
            changed
                ? 'ON'
                : 'WAIT',
            changed
                ? `Jump: ${this.settings.jumpSpeed}`
                : 'Jump field unavailable'
        );
    };

    HighJump.onTick = function () {
        if (!Client.gameDetected) return;

        Engine.setJumpSpeed(
            this.settings.jumpSpeed
        );
    };

    HighJump.onDisable = function () {
        if (
            State.originalJumpSpeed !== null
        ) {
            Engine.setJumpSpeed(
                State.originalJumpSpeed
            );
        }
    };

    const Speed = new Module(
        'Speed',
        'Movement',
        'Scales exposed horizontal velocity when available.',
        {
            settings: {
                multiplier: 1.2
            }
        }
    );

    Speed.onTick = function () {
        if (!Client.gameDetected) {
            this.setStatus(
                'WAIT',
                'Waiting for player'
            );

            return;
        }

        const velocity =
            Engine.getHorizontalVelocity();

        if (!velocity) {
            this.setStatus(
                'WAIT',
                'Velocity unavailable'
            );

            return;
        }

        const changed =
            Engine.scaleHorizontalVelocity(
                this.settings.multiplier
            );

        this.setStatus(
            changed
                ? 'ON'
                : 'WAIT',
            changed
                ? `${this.settings.multiplier}x`
                : 'Velocity unavailable'
        );
    };

    const PerformanceMode = new Module(
        'Performance Mode',
        'Utility',
        'Reduces extra visual processing by the client overlay.',
        {}
    );

    PerformanceMode.onEnable = function () {
        document.documentElement
            .dataset.vipPerformance = '1';
    };

    PerformanceMode.onDisable = function () {
        delete document.documentElement
            .dataset.vipPerformance;
    };

    const AntiBlur = new Module(
        'Anti Blur',
        'Utility',
        'Keeps the client UI crisp.',
        {
            defaultEnabled: true
        }
    );

    // =========================================================
    // UI
    // =========================================================

    const STYLE = `
        #vip-client-gui {
            position:fixed;
            top:50%;
            left:50%;
            transform:translate(-50%,-50%);
            width:min(900px,94vw);
            height:min(600px,88vh);
            display:none;
            z-index:2147483646;
            overflow:hidden;
            background:rgba(8,9,14,.97);
            color:#fff;
            border:1px solid rgba(255,0,120,.8);
            border-radius:16px;
            box-shadow:
                0 0 40px rgba(255,0,120,.25),
                0 20px 80px rgba(0,0,0,.65);
            font-family:Arial,Helvetica,sans-serif;
        }

        #vip-header {
            height:70px;
            display:flex;
            align-items:center;
            justify-content:space-between;
            padding:0 20px;
            background:
                linear-gradient(
                    90deg,
                    #ff0055,
                    #8d00ff,
                    #00d4ff
                );
        }

        #vip-title {
            font-size:20px;
            font-weight:900;
        }

        #vip-engine-status {
            padding:6px 10px;
            border-radius:999px;
            background:rgba(0,0,0,.25);
            font-size:10px;
            font-weight:800;
        }

        #vip-layout {
            display:flex;
            height:calc(100% - 70px);
        }

        #vip-sidebar {
            width:180px;
            padding:12px;
            background:rgba(0,0,0,.32);
            overflow-y:auto;
        }

        .vip-tab {
            padding:12px;
            margin-bottom:6px;
            border-radius:10px;
            cursor:pointer;
            color:#aaa;
            font-size:13px;
            font-weight:800;
        }

        .vip-tab:hover {
            background:rgba(255,0,120,.12);
            color:white;
        }

        .vip-tab.active {
            background:
                linear-gradient(
                    90deg,
                    rgba(255,0,85,.35),
                    rgba(140,0,255,.22)
                );
            color:white;
        }

        #vip-main {
            flex:1;
            min-width:0;
            padding:16px;
            overflow:auto;
        }

        .vip-grid {
            display:none;
            grid-template-columns:
                repeat(2,minmax(0,1fr));
            gap:12px;
        }

        .vip-grid.active {
            display:grid;
        }

        .vip-card {
            position:relative;
            min-height:110px;
            padding:14px;
            border:1px solid rgba(255,255,255,.08);
            border-radius:12px;
            background:
                linear-gradient(
                    145deg,
                    rgba(25,25,36,.95),
                    rgba(13,13,19,.95)
                );
            cursor:pointer;
            transition:.15s;
        }

        .vip-card:hover {
            transform:translateY(-2px);
            border-color:
                rgba(255,255,255,.25);
        }

        .vip-card.enabled {
            border-color:#ff0055;
            box-shadow:
                0 0 18px rgba(255,0,85,.15);
        }

        .vip-card.waiting {
            border-color:#ffaa00;
        }

        .vip-card.error {
            border-color:#ff3030;
        }

        .vip-card-title {
            width:70%;
            margin-bottom:6px;
            font-size:14px;
            font-weight:900;
        }

        .vip-card-desc {
            color:#858592;
            font-size:11px;
            line-height:1.4;
        }

        .vip-status {
            position:absolute;
            right:12px;
            top:12px;
            padding:4px 7px;
            border-radius:999px;
            background:#272733;
            color:#888;
            font-size:9px;
            font-weight:900;
        }

        .vip-card.enabled .vip-status {
            background:#ff0055;
            color:white;
        }

        .vip-card.waiting .vip-status {
            background:#8a5a00;
            color:white;
        }

        .vip-card.error .vip-status {
            background:#b00020;
            color:white;
        }

        .vip-reason {
            position:absolute;
            right:12px;
            bottom:10px;
            max-width:60%;
            color:#666;
            font-size:8px;
            text-align:right;
        }

        #vip-hud {
            position:fixed;
            top:12px;
            left:12px;
            z-index:2147483640;
            display:none;
            min-width:150px;
            padding:10px 12px;
            border:1px solid rgba(255,0,100,.4);
            border-radius:10px;
            background:rgba(8,8,14,.72);
            color:white;
            font:11px/1.5 monospace;
            pointer-events:none;
        }

        #vip-crosshair {
            position:fixed;
            left:50%;
            top:50%;
            width:20px;
            height:20px;
            transform:translate(-50%,-50%);
            z-index:2147483639;
            display:none;
            pointer-events:none;
        }

        #vip-crosshair::before,
        #vip-crosshair::after {
            content:"";
            position:absolute;
            left:50%;
            top:50%;
            background:#fff;
        }

        #vip-crosshair::before {
            width:20px;
            height:2px;
            transform:translate(-50%,-50%);
        }

        #vip-crosshair::after {
            width:2px;
            height:20px;
            transform:translate(-50%,-50%);
        }

        @media(max-width:700px) {
            #vip-layout {
                flex-direction:column;
            }

            #vip-sidebar {
                width:100%;
                display:flex;
                overflow-x:auto;
            }

            .vip-tab {
                min-width:110px;
                margin-right:6px;
            }

            .vip-grid {
                grid-template-columns:1fr;
            }
        }
    `;

    function buildHUD() {
        if (
            document.getElementById(
                'vip-hud'
            )
        ) {
            return;
        }

        const hud =
            document.createElement('div');

        hud.id = 'vip-hud';

        hud.innerHTML = `
            <div id="vip-hud-fps">
                FPS: 0
            </div>

            <div id="vip-hud-coords">
                X: ? Y: ? Z: ?
            </div>

            <div id="vip-hud-health">
                HP: ?
            </div>

            <div id="vip-hud-name">
                NAME: ?
            </div>

            <div id="vip-ready">
                WAITING
            </div>
        `;

        document.body.appendChild(
            hud
        );

        const crosshair =
            document.createElement(
                'div'
            );

        crosshair.id =
            'vip-crosshair';

        document.body.appendChild(
            crosshair
        );
    }

    function buildUI() {
        if (
            Client.uiBuilt ||
            !document.body ||
            !document.head
        ) {
            return;
        }

        const style =
            document.createElement(
                'style'
            );

        style.id =
            'vip-client-style';

        style.textContent =
            STYLE;

        document.head.appendChild(
            style
        );

        buildHUD();

        const menu =
            document.createElement(
                'div'
            );

        menu.id =
            'vip-client-gui';

        const tabs =
            Client.categories.map(
                (category, index) => `
                    <div
                        class="vip-tab ${index === 0 ? 'active' : ''}"
                        data-category="${escapeHtml(category)}"
                    >
                        ${escapeHtml(category)}
                    </div>
                `
            ).join('');

        const grids =
            Client.categories.map(
                (category, index) => {
                    const modules =
                        Client.modules
                            .filter(
                                module =>
                                    module.category ===
                                    category
                            )
                            .map(
                                module => `
                                    <div
                                        class="vip-card ${
                                            module.enabled
                                                ? 'enabled'
                                                : ''
                                        }"
                                        data-mod="${escapeHtml(module.name)}"
                                    >
                                        <div class="vip-card-title">
                                            ${escapeHtml(module.name)}
                                        </div>

                                        <div class="vip-card-desc">
                                            ${escapeHtml(module.description)}
                                        </div>

                                        <div class="vip-status">
                                            ${
                                                module.enabled
                                                    ? 'ON'
                                                    : 'OFF'
                                            }
                                        </div>

                                        <div class="vip-reason">
                                            ${escapeHtml(module.reason)}
                                        </div>
                                    </div>
                                `
                            )
                            .join('');

                    return `
                        <section
                            class="vip-grid ${
                                index === 0
                                    ? 'active'
                                    : ''
                            }"
                            data-grid="${escapeHtml(category)}"
                        >
                            ${modules}
                        </section>
                    `;
                }
            ).join('');

        menu.innerHTML = `
            <div id="vip-header">
                <div id="vip-title">
                    ${escapeHtml(Client.name)}
                </div>

                <div id="vip-engine-status">
                    WAITING FOR GAME
                </div>
            </div>

            <div id="vip-layout">
                <aside id="vip-sidebar">
                    ${tabs}
                </aside>

                <main id="vip-main">
                    ${grids}
                </main>
            </div>
        `;

        document.body.appendChild(
            menu
        );

        setupTabs(menu);
        setupModules(menu);

        Client.uiBuilt = true;

        refreshHUD();
    }

    function setupTabs(menu) {
        menu.querySelectorAll(
            '.vip-tab'
        ).forEach(tab => {
            tab.addEventListener(
                'click',
                () => {
                    menu.querySelectorAll(
                        '.vip-tab'
                    ).forEach(
                        item =>
                            item.classList.remove(
                                'active'
                            )
                    );

                    menu.querySelectorAll(
                        '.vip-grid'
                    ).forEach(
                        grid =>
                            grid.classList.remove(
                                'active'
                            )
                    );

                    tab.classList.add(
                        'active'
                    );

                    const target =
                        menu.querySelector(
                            `[data-grid="${cssEscape(
                                tab.dataset.category
                            )}"]`
                        );

                    if (target) {
                        target.classList.add(
                            'active'
                        );
                    }
                }
            );
        });
    }

    function setupModules(menu) {
        menu.querySelectorAll(
            '.vip-card'
        ).forEach(card => {
            card.addEventListener(
                'click',
                () => {
                    const name =
                        card.dataset.mod;

                    const module =
                        Client.modules.find(
                            item =>
                                item.name ===
                                name
                        );

                    if (!module) return;

                    module.toggle();
                }
            );
        });
    }

    function refreshHUD() {
        const hud =
            document.getElementById(
                'vip-hud'
            );

        const crosshair =
            document.getElementById(
                'vip-crosshair'
            );

        if (hud) {
            hud.style.display =
                (
                    FPSCounter.enabled ||
                    Coordinates.enabled ||
                    HealthHUD.enabled ||
                    PlayerNameHUD.enabled ||
                    SkyWarsReady.enabled
                )
                    ? 'block'
                    : 'none';
        }

        if (crosshair) {
            crosshair.style.display =
                Crosshair.enabled
                    ? 'block'
                    : 'none';
        }
    }

    function refreshStatus() {
        const node =
            document.getElementById(
                'vip-engine-status'
            );

        if (!node) return;

        node.textContent =
            Client.gameDetected
                ? 'SKYWARS CONNECTED'
                : 'WAITING FOR GAME';
    }

    // =========================================================
    // MENU KEY
    // =========================================================

    function toggleMenu(force) {
        const menu =
            document.getElementById(
                'vip-client-gui'
            );

        if (!menu) return;

        Client.menuOpen =
            force === undefined
                ? !Client.menuOpen
                : !!force;

        menu.style.display =
            Client.menuOpen
                ? 'block'
                : 'none';
    }

    window.addEventListener(
        'keydown',
        event => {
            const isComma =
                event.key === ',' ||
                event.code === 'Comma';

            if (
                isComma &&
                !event.repeat
            ) {
                toggleMenu();

                event.preventDefault();
                event.stopPropagation();
            }

            if (
                event.key === 'Escape' &&
                Client.menuOpen
            ) {
                toggleMenu(false);
            }
        },
        true
    );

    // =========================================================
    // MAIN LOOP
    // =========================================================

    function gameLoop() {
        Client.raf =
            requestAnimationFrame(
                gameLoop
            );

        Engine.scan();

        for (
            const module
            of Client.modules
        ) {
            module.onTick();
            module.onRender();
        }

        refreshStatus();
        refreshHUD();

        const canvas =
            getCanvas();

        if (canvas) {
            if (
                Fullbright.enabled ||
                SaturationBoost.enabled
            ) {
                applyCanvasFilter();
            }
        }
    }

    // =========================================================
    // DOM READY
    // =========================================================

    function init() {
        log(
            'Starting ' +
            VERSION
        );

        loadConfig();

        const start =
            () => {
                try {
                    buildUI();

                    for (
                        const module
                        of Client.modules
                    ) {
                        if (
                            module.enabled
                        ) {
                            module.setStatus(
                                Client.gameDetected
                                    ? 'ON'
                                    : 'WAIT',
                                Client.gameDetected
                                    ? 'Active'
                                    : 'Waiting for engine'
                            );
                        }
                    }
                } catch (error) {
                    warn(
                        'UI initialization error:',
                        error
                    );
                }
            };

        if (
            document.readyState ===
            'loading'
        ) {
            document.addEventListener(
                'DOMContentLoaded',
                start,
                { once: true }
            );
        } else {
            start();
        }

        window.MinefunVIP = {
            version: VERSION,

            client: Client,

            engine: Engine,

            modules: Client.modules,

            openMenu() {
                toggleMenu(true);
            },

            closeMenu() {
                toggleMenu(false);
            },

            toggle(name) {
                const module =
                    Client.modules.find(
                        item =>
                            item.name === name
                    );

                if (!module) {
                    return false;
                }

                module.toggle();

                return module.enabled;
            },

            scan() {
                return Engine.scan();
            }
        };

        gameLoop();

        log(
            "Loaded. Press ',' to open."
        );

        Client.ready = true;
    }

    init();

})();