Minefun.io SkyWars VIP Client

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

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Greasemonkey lub Violentmonkey.

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

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana będzie instalacja rozszerzenia Tampermonkey lub Userscripts.

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

Aby zainstalować ten skrypt, musisz zainstalować rozszerzenie menedżera skryptów użytkownika.

(Mam już menedżera skryptów użytkownika, pozwól mi to zainstalować!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Musisz zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

(Mam już menedżera stylów użytkownika, pozwól mi to zainstalować!)

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

})();