SHAM UI

Evil UI made by shamans used by shamans. - With felis help, pickle and mutant

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         SHAM UI
// @namespace    https://greasyfork.org/users/paozzh
// @match        https://hordes.io/play
// @icon         https://hordes.io/data/ui/classes/3.avif
// @grant        none
// @version      1.2.1
// @description  Evil UI made by shamans used by shamans. - With felis help, pickle and mutant
// @author       Paozzh
// ==/UserScript==

(function() {
    'use strict';
    const AUTO_CC_ICONS = ["stunBuff.avif", "deepFrozen.avif", "root.avif", "14.avif", "37.avif", "49.avif", "50.avif"];
    const MANAGED_BUFFS = {
        "25.avif": "Temp",
        "19.avif": "WC",
        "20.avif": "Crus",
        "22.avif": "Aura",
        "24.avif": "Ench",
        "18.avif": "Bleed",
        "29.avif": "Poison",
        "obeliskbuff.avif": "Obelisk Buff"
    };
    const IMPORTANT_BUFFS = {
        "11.avif": "Invig",
        "27.avif": "Pathfind",
        "31.avif": "Swift",
        "38.avif": "Dash",
        "16.avif": "Hypo",
        "extraBolt.avif": "Bolt Passive",
        "13.avif": "Mimirs",
        "17.avif": "Enrage"
    };
    const DEFAULTS = {
        isEnabled: true,
        mouseOverTarget: false,
        hpThreshold: 80,
        pulseSpeed: 0.3,
        pulseStrength: 0.9,
        borderSize: 2,
        activeCCs: { "25.avif": true, "19.avif": true, "20.avif": true, "22.avif": true, "24.avif": true, "18.avif": true, "29.avif": true, "obeliskbuff.avif": true },
        activeImportants: { "11.avif": true, "27.avif": true, "31.avif": true, "38.avif": true, "16.avif": true, "extraBolt.avif": true, "13.avif": true, "17.avif": true }
    };
    let config = JSON.parse(localStorage.getItem('shamConfig')) || DEFAULTS;

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

    function updateStyles() {
        const str = config.pulseStrength || 0.9;
        const bdr = config.borderSize || 2;
        style.innerHTML = `
            @keyframes shamRedPulse { 0% { background-color: rgba(255,0,0,${str * 0.2}); } 50% { background-color: rgba(255,0,0,${str}); } 100% { background-color: rgba(255,0,0,${str * 0.2}); } }
            @keyframes shamYellowPulse { 0% { background-color: rgba(255,255,0,${str * 0.2}); } 50% { background-color: rgba(255,255,0,${str}); } 100% { background-color: rgba(255,255,0,${str * 0.2}); } }
            div[class*="panel-black"][class*="barsInner"].hp-panic-active {
                animation: shamRedPulse ${config.pulseSpeed}s infinite ease-in-out !important;
                border: ${bdr}px solid red !important;
                box-shadow: 0 0 20px rgba(255,0,0,0.9) !important;
                background-image: none !important;
            }
            div[class*="panel-black"][class*="barsInner"].cc-active-pulse {
                animation: shamYellowPulse ${config.pulseSpeed}s infinite ease-in-out !important;
                border: ${bdr}px solid yellow !important;
                box-shadow: 0 0 20px rgba(255,255,0,0.9) !important;
                background-image: none !important;
            }
            .sham-panel {
                position: fixed;
                width: 310px;
                height: 500px;
                background: rgba(8, 4, 16, 0.98);
                backdrop-filter: blur(12px);
                border: 1px solid #3c2a5c;
                border-top: 3px solid #9d4edd;
                border-radius: 8px;
                box-shadow: 0 20px 60px rgba(0,0,0,0.95);
                color: #eee;
                font-family: 'Segoe UI', Tahoma, sans-serif;
                z-index: 1000000;
                display: none;
                flex-direction: column;
                user-select: none;
                opacity: 0;
                transform: scale(0.95);
                transition: opacity 0.25s ease, transform 0.25s ease;
            }
            .sham-panel.active {
                opacity: 1;
                transform: scale(1);
            }
            .sham-header {
                flex: 0 0 auto;
                padding: 12px 18px 10px;
                border-bottom: 1px solid #2a1b3d;
                display: flex;
                justify-content: space-between;
                align-items: center;
                cursor: move;
            }
            .header-left {
                display: flex;
                align-items: center;
                gap: 10px;
            }
            .header-left img {
                width: 30px;
                height: 30px;
                border-radius: 6px;
                border: 1px solid #9d4edd;
                box-shadow: 0 0 10px rgba(157, 78, 221, 0.4);
            }
            .sham-header h3 {
                margin: 0;
                color: #e0aaff;
                font-size: 20px;
                text-shadow: 0 0 12px rgba(157, 78, 221, 0.5);
                letter-spacing: 1px;
            }
            .sham-close {
                font-size: 28px;
                font-weight: bold;
                cursor: pointer;
                color: #baa0ff;
                transition: all 0.3s ease;
            }
            .sham-close:hover {
                color: #e0aaff;
                transform: scale(1.2) rotate(90deg);
            }
            .sham-content {
                flex: 1;
                padding: 14px 18px;
                display: flex;
                flex-direction: column;
                gap: 12px;
                overflow: hidden;
            }
            .sham-desc {
                font-size: 11px;
                color: #c8b6ff;
                line-height: 1.5;
                background: rgba(157, 78, 221, 0.08);
                padding: 9px;
                border-radius: 6px;
                border-left: 2px solid #9d4edd;
            }
            .sham-warning {
                font-size: 10px;
                color: #ffaaaa;
                font-weight: bold;
                text-align: center;
                background: rgba(255, 100, 100, 0.15);
                padding: 6px;
                border-radius: 6px;
                border: 1px solid #ff6666;
                margin-bottom: 8px;
                line-height: 1.3;
            }
            .sham-list {
                flex: 1;
                overflow-y: auto;
                padding-right: 5px;
            }
            .sham-list::-webkit-scrollbar { width: 6px; }
            .sham-list::-webkit-scrollbar-track { background: #1a1226; border-radius: 3px; }
            .sham-list::-webkit-scrollbar-thumb { background: #5a189a; border-radius: 3px; }
            .sham-list::-webkit-scrollbar-thumb:hover { background: #9d4edd; }
            .toggle-row {
                display: flex;
                justify-content: space-between;
                align-items: center;
                padding: 7px 10px;
                font-size: 13px;
                color: #ddd;
                border-radius: 6px;
                transition: background 0.3s ease;
            }
            .toggle-row:hover {
                background: rgba(157, 78, 221, 0.12);
            }
            .toggle-row.enabled {
                background: rgba(157, 78, 221, 0.28);
            }
            .toggle-row.enabled .module-name {
                color: #e0aaff;
            }
            .toggle-label {
                display: flex;
                align-items: center;
                flex: 1;
            }
            .module-name {
                font-weight: 600;
            }
            .toggle-switch {
                position: relative;
                width: 38px;
                height: 20px;
            }
            .toggle-switch input { opacity: 0; width: 0; height: 0; }
            .slider {
                position: absolute;
                cursor: pointer;
                top: 0; left: 0; right: 0; bottom: 0;
                background-color: #1a1226;
                transition: .4s;
                border-radius: 20px;
                border: 1px solid #3c2a5c;
            }
            .slider:before {
                position: absolute;
                content: "";
                height: 14px;
                width: 14px;
                left: 3px;
                bottom: 3px;
                background-color: #5a189a;
                transition: .4s;
                border-radius: 50%;
            }
            input:checked + .slider { background-color: #3c096c; border-color: #9d4edd; }
            input:checked + .slider:before { transform: translateX(18px); background-color: #ffffff; box-shadow: 0 0 8px #9d4edd; }
            .sham-label {
                display: flex;
                justify-content: space-between;
                font-size: 12px;
                color: #b79ced;
                font-weight: 600;
            }
            input[type=range] {
                width: 100%;
                background: #1a1226;
                height: 5px;
                -webkit-appearance: none;
                border-radius: 8px;
                outline: none;
                margin: 7px 0;
            }
            input[type=range]::-webkit-slider-runnable-track {
                background: linear-gradient(90deg, #5a189a, #9d4edd);
                border-radius: 8px;
                height: 5px;
            }
            input[type=range]::-webkit-slider-thumb {
                -webkit-appearance: none;
                width: 15px;
                height: 15px;
                background: #ffffff;
                border: 2px solid #9d4edd;
                border-radius: 50%;
                cursor: pointer;
                margin-top: -5px;
                box-shadow: 0 0 8px rgba(157, 78, 221, 0.8);
            }
            .sham-btn {
                width: 100%;
                padding: 12px;
                font-weight: 800;
                border-radius: 6px;
                border: none;
                cursor: pointer;
                background: #9d4edd;
                color: #fff;
                margin-top: auto;
                transition: all 0.3s ease;
                text-transform: uppercase;
                letter-spacing: 1.1px;
                font-size: 13px;
            }
            .sham-btn:hover {
                background: #e0aaff;
                color: #240046;
                box-shadow: 0 0 20px rgba(157, 78, 221, 0.5);
            }
            .stagger {
                opacity: 0;
                transform: translateY(8px);
                transition: opacity 0.35s ease-out, transform 0.35s ease-out;
            }
            .stagger.visible {
                opacity: 1;
                transform: translateY(0);
            }
        `;
    }
    updateStyles();

    const generalMenu = document.createElement('div');
    generalMenu.id = 'sham-general';
    generalMenu.className = 'sham-panel';
    document.body.appendChild(generalMenu);

    const impMenu = document.createElement('div');
    impMenu.id = 'sham-imp';
    impMenu.className = 'sham-panel';
    document.body.appendChild(impMenu);

    const ccMenu = document.createElement('div');
    ccMenu.id = 'sham-cc';
    ccMenu.className = 'sham-panel';
    document.body.appendChild(ccMenu);

    function makeDraggable(panel) {
        let isDragging = false;
        let offset = { x: 0, y: 0 };
        const header = panel.querySelector('.sham-header');
        header.addEventListener('mousedown', (e) => {
            if (e.target.closest('.sham-close')) return;
            isDragging = true;
            const rect = panel.getBoundingClientRect();
            offset.x = e.clientX - rect.left;
            offset.y = e.clientY - rect.top;
        });
        document.addEventListener('mousemove', (e) => {
            if (isDragging) {
                panel.style.left = (e.clientX - offset.x) + 'px';
                panel.style.top = (e.clientY - offset.y) + 'px';
            }
        });
        document.addEventListener('mouseup', () => isDragging = false);
    }

    let impHTML = "";
    Object.keys(IMPORTANT_BUFFS).forEach(file => {
        impHTML += `<div class="toggle-row stagger">
            <div class="toggle-label">
                <span class="module-name">${IMPORTANT_BUFFS[file]}</span>
            </div>
            <label class="toggle-switch">
                <input type="checkbox" data-type="imp" data-file="${file}" ${config.activeImportants[file] ? 'checked' : ''}>
                <span class="slider"></span>
            </label>
        </div>`;
    });

    let ccHTML = "";
    Object.keys(MANAGED_BUFFS).forEach(file => {
        ccHTML += `<div class="toggle-row stagger">
            <div class="toggle-label">
                <span class="module-name">${MANAGED_BUFFS[file]}</span>
            </div>
            <label class="toggle-switch">
                <input type="checkbox" data-type="cc" data-file="${file}" ${config.activeCCs[file] ? 'checked' : ''}>
                <span class="slider"></span>
            </label>
        </div>`;
    });

    function renderPanels() {
        generalMenu.innerHTML = `
            <div class="sham-header">
                <div class="header-left">
                    <img src="https://hordes.io/data/ui/classes/3.avif">
                    <h3>SHAM UI</h3>
                </div>
                <div class="sham-close">×</div>
            </div>
            <div class="sham-content">
                <div class="sham-warning stagger">CC Indicator only works if you use 1 player per party row or else it'll cause bugs.</div>
                <div class="toggle-row stagger">
                    <div class="toggle-label">
                        <span class="module-name"><strong>Enable UI</strong></span>
                    </div>
                    <label class="toggle-switch">
                        <input type="checkbox" id="master-switch" ${config.isEnabled ? 'checked' : ''}>
                        <span class="slider"></span>
                    </label>
                </div>
                <div class="toggle-row stagger">
                    <div class="toggle-label">
                        <span class="module-name"><strong>Mouse-Over</strong></span>
                    </div>
                    <label class="toggle-switch">
                        <input type="checkbox" id="mouse-switch" ${config.mouseOverTarget ? 'checked' : ''}>
                        <span class="slider"></span>
                    </label>
                </div>

                <div class="sham-label stagger"><span>HP Threshold</span> <span id="val-hp">${config.hpThreshold}%</span></div>
                <input class="stagger" type="range" id="cfg-hp" min="10" max="100" value="${config.hpThreshold}" oninput="document.getElementById('val-hp').innerText = this.value + '%'">

                <div class="sham-label stagger"><span>Pulse Speed</span> <span id="val-speed">${config.pulseSpeed}s</span></div>
                <input class="stagger" type="range" id="cfg-speed" min="0.1" max="1.0" step="0.1" value="${config.pulseSpeed}" oninput="document.getElementById('val-speed').innerText = this.value + 's'">

                <div class="sham-label stagger"><span>Pulse Intensity</span> <span id="val-strength">${Math.round(config.pulseStrength * 100)}%</span></div>
                <input class="stagger" type="range" id="cfg-strength" min="0.1" max="1.0" step="0.05" value="${config.pulseStrength}" oninput="document.getElementById('val-strength').innerText = Math.round(this.value * 100) + '%'">

                <div class="sham-label stagger"><span>Border Size</span> <span id="val-border">${config.borderSize}px</span></div>
                <input class="stagger" type="range" id="cfg-border" min="0" max="10" step="1" value="${config.borderSize}" oninput="document.getElementById('val-border').innerText = this.value + 'px'">

                <button id="cfg-save" class="sham-btn stagger">SAVE SETTINGS</button>
            </div>
        `;

        impMenu.innerHTML = `
            <div class="sham-header">
                <div class="header-left"><h3>Important Buffs</h3></div>
                <div class="sham-close">×</div>
            </div>
            <div class="sham-content">
                <div class="sham-desc stagger">Uncheck to permanently hide these key buffs/passives from your hotbar.</div>
                <div class="sham-list">${impHTML}</div>
            </div>
        `;

        ccMenu.innerHTML = `
            <div class="sham-header">
                <div class="header-left"><h3>Icon Manager</h3></div>
                <div class="sham-close">×</div>
            </div>
            <div class="sham-content">
                <div class="sham-desc stagger">Uncheck to permanently hide these managed icons from your hotbar.</div>
                <div class="sham-list">${ccHTML}</div>
            </div>
        `;

        document.querySelectorAll('.sham-close').forEach(btn => btn.onclick = hideMenus);
        document.querySelectorAll('.toggle-switch input[type="checkbox"]').forEach(input => {
            const row = input.closest('.toggle-row');
            if (row) {
                const update = () => {
                    row.classList.toggle('enabled', input.checked);
                };
                update();
                input.addEventListener('change', update);
            }
        });

        document.getElementById('cfg-save').onclick = () => {
            config.isEnabled = document.getElementById('master-switch').checked;
            config.mouseOverTarget = document.getElementById('mouse-switch').checked;
            config.hpThreshold = parseInt(document.getElementById('cfg-hp').value);
            config.pulseSpeed = parseFloat(document.getElementById('cfg-speed').value);
            config.pulseStrength = parseFloat(document.getElementById('cfg-strength').value);
            config.borderSize = parseInt(document.getElementById('cfg-border').value);

            document.querySelectorAll('#sham-imp input[data-type="imp"]').forEach(cb => {
                config.activeImportants[cb.dataset.file] = cb.checked;
            });
            document.querySelectorAll('#sham-cc input[data-type="cc"]').forEach(cb => {
                config.activeCCs[cb.dataset.file] = cb.checked;
            });

            localStorage.setItem('shamConfig', JSON.stringify(config));
            updateStyles();
            hideMenus();
        };
    }

    renderPanels();
    makeDraggable(generalMenu);
    makeDraggable(impMenu);
    makeDraggable(ccMenu);

    function showMenus() {
        const panelWidth = 310;
        const gap = 15;
        const totalWidth = panelWidth * 3 + gap * 2;
        let startLeft = Math.max(10, (window.innerWidth - totalWidth) / 2);

        generalMenu.style.left = startLeft + 'px';
        impMenu.style.left = (startLeft + panelWidth + gap) + 'px';
        ccMenu.style.left = (startLeft + panelWidth * 2 + gap * 2) + 'px';

        const topPos = Math.max(20, (window.innerHeight - 500) / 2);
        [generalMenu, impMenu, ccMenu].forEach(menu => {
            menu.style.top = topPos + 'px';
            menu.style.display = 'flex';
            void menu.offsetWidth;
            menu.classList.add('active');
        });

        [generalMenu, impMenu, ccMenu].forEach(menu => {
            const items = menu.querySelectorAll('.stagger');
            items.forEach((item, index) => {
                setTimeout(() => item.classList.add('visible'), 120 + index * 45);
            });
        });
    }

    function hideMenus() {
        [generalMenu, impMenu, ccMenu].forEach(menu => {
            menu.querySelectorAll('.stagger').forEach(item => item.classList.remove('visible'));
            menu.classList.remove('active');
        });
        setTimeout(() => {
            [generalMenu, impMenu, ccMenu].forEach(menu => menu.style.display = 'none');
        }, 300);
    }

    window.addEventListener('keydown', (e) => {
        if (['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName)) return;

        if (e.shiftKey && e.key.toLowerCase() === 'n') {
            e.preventDefault();
            if (generalMenu.style.display === 'flex') hideMenus();
            else showMenus();
        } else if (e.key === 'Escape' && generalMenu.style.display === 'flex') {
            e.preventDefault();
            hideMenus();
        }
    });

    document.addEventListener('mouseover', (e) => {
        if (config.isEnabled && config.mouseOverTarget) {
            const t = e.target.closest('div[class*="targetable"]');
            if (t) t.click();
        }
    });

    let lastCCState = false;

    // Main pulse logic (kept exactly as in your working script, including spatial CC matching)
    setInterval(() => {
        if (!config.isEnabled) return;

        document.querySelectorAll('.hp-panic-active, .cc-active-pulse').forEach(el => {
            el.classList.remove('hp-panic-active', 'cc-active-pulse');
        });

        const allBars = Array.from(document.querySelectorAll('div[class*="panel-black"][class*="barsInner"]'));
        const ccIcons = Array.from(document.getElementsByTagName('img')).filter(img => AUTO_CC_ICONS.some(cc => img.src.includes(cc)));

        let currentCCDetected = false;

        ccIcons.forEach(icon => {
            const iconRect = icon.getBoundingClientRect();
            if (iconRect.width === 0) return;
            const iconCenter = { x: iconRect.left + iconRect.width / 2, y: iconRect.top + iconRect.height / 2 };

            let closestBar = null;
            let minDistance = Infinity;

            allBars.forEach(bar => {
                const barRect = bar.getBoundingClientRect();
                const dist = Math.hypot(iconCenter.x - (barRect.left + barRect.width / 2), iconCenter.y - (barRect.top + barRect.height / 2));
                if (dist < minDistance) {
                    minDistance = dist;
                    closestBar = bar;
                }
            });

            if (closestBar && minDistance < 250) {
                closestBar.classList.add('cc-active-pulse');
                currentCCDetected = true;
            }
        });

        allBars.forEach(bar => {
            if (bar.classList.contains('cc-active-pulse')) return;
            const bgHealth = bar.querySelector('div[class*="bghealth"]');
            if (bgHealth && bgHealth.style.width) {
                const hpWidth = parseFloat(bgHealth.style.width);
                if (hpWidth <= config.hpThreshold && hpWidth > 0) {
                    bar.classList.add('hp-panic-active');
                }
            }
        });

        if (!currentCCDetected) lastCCState = false;
    }, 150);

    // Separate fast icon deletion (as in your script for performance)
    setInterval(() => {
        if (!config.isEnabled) return;

        const allImgs = document.getElementsByTagName('img');
        for (let i = allImgs.length - 1; i >= 0; i--) {
            const img = allImgs[i];
            const srcRaw = img.src.split('/').pop() || "";
            const src = srcRaw.split('?')[0];

            const shouldHideCC = MANAGED_BUFFS[src] && config.activeCCs[src] === false;
            const shouldHideImp = IMPORTANT_BUFFS[src] && config.activeImportants[src] === false;

            if (shouldHideCC || shouldHideImp) {
                const slot = img.closest('.slot');
                if (slot) slot.remove();
                else img.remove();
            }
        }
    }, 50);
})();