Torn War Helper (highlights and chain timers)

Enhances Torn faction wars with member status highlighting, chain timer overlay, and hospital alerts. Includes built-in settings panel for better user experience.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Torn War Helper (highlights and chain timers)
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Enhances Torn faction wars with member status highlighting, chain timer overlay, and hospital alerts. Includes built-in settings panel for better user experience.
// @author       EagWasTaken [3264609]
// @match        https://www.torn.com/factions.php*
// @grant        none
// @run-at       document-end
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const SETTINGS = {
        colors: {
            online: "rgba(134, 179, 0, 0.4)",
            idle: "rgba(255, 191, 0, 0.4)",
            offline: "rgba(220, 53, 69, 0.3)"
        },

        darkModeColors: {
            online: "rgba(87, 105, 58, 0.9)",
            idle: "rgba(209, 129, 0, 0.3)", 
            offline: "rgba(153, 27, 27, 0.3)"
        },

        highlightOnline: true,
        highlightIdle: true,
        highlightOffline: false,

        refreshRate: 3000,

        flashHospitalChange: true,
        flashDuration: 2000,

        enableChainTimerBox: true,
        chainTimerRefreshRate: 200,
        chainTimerFontSize: '20px',
        chainTimerBoxOpacity: 0.9,
        chainTimerPosition: 'top-right',
        chainTimerBoxPadding: {
            top: 60,
            right: 10,
            left: 10,
            inner: '10px 15px'
        }
    };

    const SETTINGS_UI = {
        panelTitle: "Torn War Helper Settings",
        settingsButtonId: "wh-settings-btn",
        settingsPanelId: "wh-settings-panel",
        
        panelPosition: {
            bottom: '40px',
            left: '20px', 
        },
        
        panelDimensions: {
            width: '300px',
            maxHeight: '80vh',
        },
        
        saveMessageDuration: 2000,
        
        sections: [
            {
                title: "Highlighting",
                settings: [
                    {
                        id: "highlightOnline",
                        label: "Highlight Online Members",
                        type: "checkbox",
                        default: true,
                        tooltip: "Highlight members who are online with green background"
                    },
                    {
                        id: "highlightIdle",
                        label: "Highlight Idle Members",
                        type: "checkbox",
                        default: true,
                        tooltip: "Highlight members who are idle with amber background"
                    },
                    {
                        id: "highlightOffline",
                        label: "Highlight Offline Members",
                        type: "checkbox",
                        default: false,
                        tooltip: "Highlight members who are offline with gray background"
                    }
                ]
            },
            {
                title: "Chain Timer",
                settings: [
                    {
                        id: "enableChainTimerBox",
                        label: "Enable Chain Timer Box",
                        type: "checkbox",
                        default: true,
                        tooltip: "Show the chain timer box overlay"
                    },
                    {
                        id: "chainTimerPosition",
                        label: "Chain Timer Position",
                        type: "select",
                        options: [
                            { value: "top-right", label: "Top Right" },
                            { value: "top-left", label: "Top Left" },
                            { value: "top-center", label: "Top Center" }
                        ],
                        default: "top-right",
                        tooltip: "Position of the chain timer box on screen"
                    },
                    {
                        id: "chainTimerBoxOpacity",
                        label: "Chain Timer Opacity",
                        type: "range",
                        min: 0.1,
                        max: 1.0,
                        step: 0.1,
                        default: 0.9,
                        tooltip: "Transparency of the chain timer box (0.1 = mostly transparent, 1.0 = solid)"
                    },
                    {
                        id: "chainTimerFontSize",
                        label: "Timer Font Size",
                        type: "range",
                        min: 12,
                        max: 30,
                        step: 1,
                        valueConverter: value => `${value}px`,
                        valueParser: value => parseInt(value.replace('px', '')),
                        default: 20,
                        tooltip: "Font size for the chain timer text"
                    }
                ]
            },
            {
                title: "Hospital Alert",
                settings: [
                    {
                        id: "flashHospitalChange",
                        label: "Flash Hospital Status Change",
                        type: "checkbox",
                        default: true,
                        tooltip: "Flash when a player leaves hospital"
                    },
                    {
                        id: "flashDuration",
                        label: "Flash Duration (ms)",
                        type: "number",
                        min: 500,
                        max: 5000,
                        step: 500,
                        default: 2000,
                        tooltip: "Duration of hospital status change flash (in milliseconds)"
                    }
                ]
            },
            {
                title: "Refresh Rate",
                settings: [
                    {
                        id: "refreshRate",
                        label: "Highlighting Refresh Rate (ms)",
                        type: "number",
                        min: 1000,
                        max: 10000,
                        step: 1000,
                        default: 3000,
                        tooltip: "How often to refresh member highlighting (in milliseconds)"
                    }
                ]
            }
        ]
    };

    function saveSettings() {
        localStorage.setItem('tornWarHelperSettings', JSON.stringify(SETTINGS));
    }

    function loadSettings() {
        const savedSettings = localStorage.getItem('tornWarHelperSettings');
        if (savedSettings) {
            try {
                const parsedSettings = JSON.parse(savedSettings);
                for (const key in parsedSettings) {
                    if (typeof SETTINGS[key] === 'object' && !Array.isArray(SETTINGS[key])) {
                        Object.assign(SETTINGS[key], parsedSettings[key]);
                    } else {
                        SETTINGS[key] = parsedSettings[key];
                    }
                }
            } catch (e) {
                // Error handled silently
            }
        }
    }

    const playerStatusHistory = new Map();

    function addStyles() {
        const styleElement = document.createElement('style');
        styleElement.textContent = `
            /* Status highlighting with pseudo-elements to prevent layout shifts */
            #body.tornWarHelper ul.members-list > li.wh-status--online::before,
            #body.tornWarHelper [class*="membersWrap___"] > li.wh-status--online::before,
            #body.tornWarHelper [class*="members-list"] > li.wh-status--online::before {
                content: '';
                position: absolute;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                background-color: ${SETTINGS.colors.online};
                z-index: -1;
                pointer-events: none;
            }

            #body.tornWarHelper.dark-mode ul.members-list > li.wh-status--online::before,
            #body.tornWarHelper.dark-mode [class*="membersWrap___"] > li.wh-status--online::before,
            #body.tornWarHelper.dark-mode [class*="members-list"] > li.wh-status--online::before {
                background-color: ${SETTINGS.darkModeColors.online};
            }

            /* Idle status styles */
            #body.tornWarHelper ul.members-list > li.wh-status--idle::before,
            #body.tornWarHelper [class*="membersWrap___"] > li.wh-status--idle::before,
            #body.tornWarHelper [class*="members-list"] > li.wh-status--idle::before {
                content: '';
                position: absolute;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                background-color: ${SETTINGS.colors.idle};
                z-index: -1;
                pointer-events: none;
            }

            #body.tornWarHelper.dark-mode ul.members-list > li.wh-status--idle::before,
            #body.tornWarHelper.dark-mode [class*="membersWrap___"] > li.wh-status--idle::before,
            #body.tornWarHelper.dark-mode [class*="members-list"] > li.wh-status--idle::before {
                background-color: ${SETTINGS.darkModeColors.idle};
            }

            /* Offline status styles */
            #body.tornWarHelper ul.members-list > li.wh-status--offline::before,
            #body.tornWarHelper [class*="membersWrap___"] > li.wh-status--offline::before,
            #body.tornWarHelper [class*="members-list"] > li.wh-status--offline::before {
                content: '';
                position: absolute;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                background-color: ${SETTINGS.colors.offline};
                z-index: -1;
                pointer-events: none;
            }

            /* Ensure all rows have position relative for pseudo-element positioning */
            #body.tornWarHelper ul.members-list > li,
            #body.tornWarHelper [class*="membersWrap___"] > li,
            #body.tornWarHelper [class*="members-list"] > li {
                position: relative !important;
            }

            /* Make sure no styles affect layout */
            #body.tornWarHelper li.enemy .attack,
            #body.tornWarHelper li[class*="enemy___"] .attack,
            #body.tornWarHelper li[class*="your___"] .attack {
                position: relative;
                z-index: 1;
            }

            /* Animation for hospital status change flash */
            @keyframes hospitalFlash {
                0%, 100% { background-color: transparent; }
                50% { background-color: rgba(255, 0, 0, 0.7); }
            }

            /* Apply the animation to status elements that changed from hospital */
            .wh-hospital-status-change {
                animation: hospitalFlash ${SETTINGS.flashDuration}ms ease-in-out;
            }

            /* Chain Timer Box Styles */
            #wh-chain-timer-box {
                position: fixed;
                top: ${SETTINGS.chainTimerBoxPadding.top}px;
                right: ${SETTINGS.chainTimerBoxPadding.right}px;
                left: auto;
                transform: none;
                background-color: rgba(0, 0, 0, 0.8);
                color: white;
                padding: 6px 10px;
                border-radius: 5px;
                box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
                z-index: 9999;
                font-size: ${SETTINGS.chainTimerFontSize};
                font-weight: bold;
                opacity: ${SETTINGS.chainTimerBoxOpacity};
                transition: opacity 0.3s ease, background-color 0.3s ease;
                border: 1px solid #555;
                font-family: "Arial", sans-serif;
                min-width: 140px;
                display: flex;
                align-items: center;
            }

            /* Container for the timer content */
            #wh-chain-timer-content {
                display: flex;
                align-items: center;
                justify-content: flex-start;
                width: 100%;
                position: relative;
                padding-right: 20px; /* Add padding on the right to make room for the gear icon */
            }

            /* Label text */
            #wh-chain-timer-label {
                margin-right: 6px;
                white-space: nowrap;
            }

            /* Timer value styling */
            #wh-chain-timer-value {
                min-width: 60px;
                text-align: right;
                font-family: Arial, sans-serif;
                margin-right: 0; /* Remove margin as we're using container padding instead */
            }

            /* Settings icon styling */
            #wh-chain-timer-settings {
                position: absolute;
                top: 50%;
                transform: translateY(-50%);
                right: 3px;
                font-size: 15px;
                cursor: pointer;
                opacity: 0.8;
                color: white;
                font-weight: bold;
                background-color: transparent;
                width: 16px;
                height: 16px;
                display: flex;
                justify-content: center;
                align-items: center;
                box-shadow: none;
                padding: 0;
                margin: 0;
                z-index: 10;
                text-shadow: 0 0 2px #000;
            }

            #wh-chain-timer-settings:hover {
                opacity: 1;
                color: #3f96e0;
            }

            #wh-chain-timer-box:hover {
                opacity: 1;
            }

            #wh-chain-timer-box.warning {
                background-color: rgba(255, 100, 0, 0.9);
                animation: pulse 1s infinite;
            }

            #wh-chain-timer-box.danger {
                background-color: rgba(255, 0, 0, 0.9);
                animation: pulse 0.5s infinite;
            }

            /* Chain cooldown style */
            #wh-chain-timer-box.cooldown {
                background-color: rgba(0, 0, 128, 0.9); /* Navy blue */
                animation: none; /* No pulse animation during cooldown */
                min-width: 155px; /* Wider for cooldown text */
            }

            /* Chain reset style */
            #wh-chain-timer-box.reset {
                background-color: rgba(0, 170, 0, 0.9); /* Bright green */
                animation: flash 1s ease-in-out 3; /* Flash 3 times */
            }

            @keyframes pulse {
                0%, 100% { transform: scale(1); }
                50% { transform: scale(1.05); }
            }

            @keyframes flash {
                0%, 100% { background-color: rgba(0, 170, 0, 0.9); }
                50% { background-color: rgba(0, 255, 0, 1); box-shadow: 0 0 15px rgba(0, 255, 0, 0.7); }
            }

            /* Position variants */
            #wh-chain-timer-box.top-left {
                right: auto;
                left: ${SETTINGS.chainTimerBoxPadding.left}px;
                transform: none;
            }

            #wh-chain-timer-box.top-center {
                right: auto;
                left: 50%;
                transform: translateX(-50%);
            }

            #wh-chain-timer-box.top-right {
                left: auto;
                right: ${SETTINGS.chainTimerBoxPadding.right}px;
                transform: none;
            }

            /* Settings button styles - updated for content-title placement */
            #${SETTINGS_UI.settingsButtonId} {
                color: #777;
                background: transparent;
                border: none;
                cursor: pointer;
                font-size: 14px;
                font-family: 'Lucida Grande', sans-serif;
                display: inline-flex;
                align-items: center;
                padding: 0 8px;
                line-height: 24px;
                vertical-align: middle;
                transition: color 0.2s;
                margin-right: 10px;
            }

            #${SETTINGS_UI.settingsButtonId}:hover {
                color: #069;
            }

            #${SETTINGS_UI.settingsButtonId} i {
                font-size: 16px;
                margin-right: 5px;
            }

            /* Hide text on mobile */
            @media screen and (max-width: 600px) {
                #${SETTINGS_UI.settingsButtonId} span {
                    display: none;
                }
                
                #${SETTINGS_UI.settingsButtonId} i {
                    margin-right: 0;
                    font-size: 18px;
                }
            }

            #${SETTINGS_UI.settingsPanelId} {
                position: fixed;
                bottom: ${SETTINGS_UI.panelPosition.bottom};
                left: ${SETTINGS_UI.panelPosition.left};
                width: ${SETTINGS_UI.panelDimensions.width};
                max-height: ${SETTINGS_UI.panelDimensions.maxHeight};
                background-color: #1c1c1c;
                border: 1px solid #444;
                border-radius: 5px;
                box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
                z-index: 10000;
                display: none;
                overflow-y: auto;
                color: #fff;
                font-family: Arial, sans-serif;
                transform: translateY(20px);
                opacity: 0;
                transition: transform 0.3s ease, opacity 0.3s ease;
            }

            #${SETTINGS_UI.settingsPanelId}.visible {
                display: block;
                transform: translateY(0);
                opacity: 1;
            }

            #${SETTINGS_UI.settingsPanelId} .wh-settings-header {
                background-color: #333;
                padding: 10px 15px;
                border-bottom: 1px solid #444;
                font-weight: bold;
                display: flex;
                justify-content: space-between;
                align-items: center;
            }

            #${SETTINGS_UI.settingsPanelId} .wh-settings-close {
                cursor: pointer;
                font-size: 18px;
                color: #ccc;
                transition: color 0.2s;
            }

            #${SETTINGS_UI.settingsPanelId} .wh-settings-close:hover {
                color: #fff;
            }

            #${SETTINGS_UI.settingsPanelId} .wh-settings-section {
                padding: 10px 15px;
                border-bottom: 1px solid #444;
            }

            #${SETTINGS_UI.settingsPanelId} .wh-section-title {
                font-weight: bold;
                margin-bottom: 10px;
                color: #ccc;
            }

            #${SETTINGS_UI.settingsPanelId} .wh-setting-item {
                margin-bottom: 12px;
                display: flex;
                flex-direction: column;
            }

            #${SETTINGS_UI.settingsPanelId} .wh-setting-label {
                margin-bottom: 5px;
                display: flex;
                align-items: center;
            }

            #${SETTINGS_UI.settingsPanelId} .wh-setting-tooltip {
                margin-left: 5px;
                color: #888;
                font-size: 12px;
                cursor: help;
            }

            #${SETTINGS_UI.settingsPanelId} .wh-setting-tooltip:hover {
                color: #ddd;
            }

            #${SETTINGS_UI.settingsPanelId} input[type="checkbox"] {
                margin-right: 5px;
            }

            #${SETTINGS_UI.settingsPanelId} input[type="number"],
            #${SETTINGS_UI.settingsPanelId} input[type="text"],
            #${SETTINGS_UI.settingsPanelId} select {
                background-color: #333;
                border: 1px solid #555;
                color: #fff;
                padding: 5px;
                border-radius: 3px;
                width: 100%;
            }
            

            #${SETTINGS_UI.settingsPanelId} select,
            #${SETTINGS_UI.settingsPanelId} input[type="number"] {
                height: 30px;
                box-sizing: border-box;
                appearance: none;
                -webkit-appearance: none;
                -moz-appearance: none;
                padding-right: 10px;
            }

            #${SETTINGS_UI.settingsPanelId} input[type="range"] {
                width: 100%;
                background-color: #333;
            }

            #${SETTINGS_UI.settingsPanelId} .wh-range-value {
                margin-left: 10px;
                font-size: 12px;
                color: #aaa;
            }

            #${SETTINGS_UI.settingsPanelId} .wh-settings-footer {
                padding: 10px 15px;
                display: flex;
                justify-content: center;
                align-items: center;
                gap: 15px;
            }

            #${SETTINGS_UI.settingsPanelId} .wh-save-btn {
                background-color: #5cb85c;
                color: #fff;
                border: none;
                padding: 4px 10px;
                border-radius: 3px;
                cursor: pointer;
                transition: background-color 0.2s;
                font-size: 12px;
                min-width: 60px;
            }

            #${SETTINGS_UI.settingsPanelId} .wh-save-btn:hover {
                background-color: #4cae4c;
            }

            #${SETTINGS_UI.settingsPanelId} .wh-reset-btn {
                background-color: #d9534f;
                color: #fff;
                border: none;
                padding: 4px 10px;
                border-radius: 3px;
                cursor: pointer;
                transition: background-color 0.2s;
                font-size: 12px;
                min-width: 60px;
            }

            #${SETTINGS_UI.settingsPanelId} .wh-reset-btn:hover {
                background-color: #c9302c;
            }

            #${SETTINGS_UI.settingsPanelId} .wh-save-message {
                color: #5cb85c;
                font-size: 12px;
                transition: opacity 0.5s;
                opacity: 0;
                position: absolute;
                bottom: 45px;
                left: 0;
                right: 0;
                text-align: center;
            }

            #${SETTINGS_UI.settingsPanelId} .wh-save-message.visible {
                opacity: 1;
            }
        `;

        document.head.appendChild(styleElement);
    }

    function getRows(type) {
        if (type === 'enemy') {
            return [
                ...document.querySelectorAll('li.enemy'),
                ...document.querySelectorAll('li[class*="enemy___"]')
            ];
        } else {
            return [
                ...document.querySelectorAll('li[class*="your___"]'),
                ...document.querySelectorAll('li.your')
            ];
        }
    }

    function processRows(rows) {
        if (window.location.href.includes('chain')) {
            return;
        }

        rows.forEach(row => {
            const isInChain = row.closest('[class*="chainStatus"]') !== null ||
                              row.closest('[id*="chain"]') !== null ||
                              row.closest('[class*="chain-"]') !== null;
            if (isInChain) return;

            row.classList.remove('wh-status--online', 'wh-status--idle', 'wh-status--offline');

            let playerId = extractPlayerId(row);

            processHospitalStatus(row, playerId);

            const statusValue = determinePlayerStatus(row);

            if (statusValue) {
                if (statusValue === 'online' && SETTINGS.highlightOnline) {
                    row.classList.add('wh-status--online');
                } else if (statusValue === 'idle' && SETTINGS.highlightIdle) {
                    row.classList.add('wh-status--idle');
                } else if (statusValue === 'offline' && SETTINGS.highlightOffline) {
                    row.classList.add('wh-status--offline');
                }
            }
        });
    }

    function extractPlayerId(row) {
        const playerIdElement = row.querySelector('[data-player], [data-id], [data-member]');
        if (playerIdElement) {
            return playerIdElement.getAttribute('data-player') ||
                   playerIdElement.getAttribute('data-id') ||
                   playerIdElement.getAttribute('data-member');
        }

        const nameElement = row.querySelector('[class*="name"], .playerName, .userName');
        if (nameElement) {
            return nameElement.textContent.trim();
        }

        return 'player_' + row.textContent.trim().slice(0, 20);
    }

    function processHospitalStatus(row, playerId) {
        if (!SETTINGS.flashHospitalChange || !playerId) return;

        const statusElements = row.querySelectorAll('[class*="status___"], [class*="prevColumn___"], .status, .userStatus');
        let isHospital = false;
        let isOk = false;
        let statusElement = null;

        statusElements.forEach(el => {
            const classNames = el.className;
            if (classNames.includes('status___') || classNames.includes('prevColumn___')) {
                statusElement = el;
                isHospital = classNames.includes('not-ok') ||
                            el.textContent.toLowerCase().includes('hospital');
                isOk = classNames.includes('ok') ||
                       el.textContent.toLowerCase().includes('ok');
            }
        });

        if (statusElement) {
            const previousStatus = playerStatusHistory.get(playerId);
            const currentStatus = isHospital ? 'hospital' : (isOk ? 'ok' : 'unknown');

            if (previousStatus === 'hospital' && currentStatus === 'ok') {
                statusElement.classList.remove('wh-hospital-status-change');
                void statusElement.offsetWidth;
                statusElement.classList.add('wh-hospital-status-change');

                setTimeout(() => {
                    statusElement.classList.remove('wh-hospital-status-change');
                }, SETTINGS.flashDuration);
            }

            playerStatusHistory.set(playerId, currentStatus);
        }
    }

    function determinePlayerStatus(row) {
        const statusIcon = row.querySelector('[class*="status___"], [class*="userIcon___"], .icon-status, .player-status, .memberStatusIcon');
        if (statusIcon) {
            if (statusIcon.className.includes('online')) return 'online';
            if (statusIcon.className.includes('idle')) return 'idle';
            if (statusIcon.className.includes('offline')) return 'offline';
        }

        const svgIcon = row.querySelector('svg[fill]');
        if (svgIcon) {
            const fillAttr = svgIcon.getAttribute('fill');
            if (fillAttr) {
                const match = fillAttr.match(/(online|offline|idle)/i);
                if (match) return match[0].toLowerCase();
            }
        }

        const statusText = row.querySelector('[class*="status___"], .status, .userStatus');
        if (statusText && statusText.textContent) {
            const text = statusText.textContent.toLowerCase().trim();
            if (text.includes('online')) return 'online';
            if (text.includes('idle')) return 'idle';
            return 'offline';
        }

        const statusElement = row.querySelector('[data-status]');
        if (statusElement) {
            return statusElement.getAttribute('data-status').toLowerCase();
        }

        return null;
    }

    function createChainTimerBox() {
        const existingTimerBox = document.getElementById('wh-chain-timer-box');
        if (existingTimerBox) {
            existingTimerBox.remove();
        }

        const timerBox = document.createElement('div');
        timerBox.id = 'wh-chain-timer-box';
        timerBox.className = SETTINGS.chainTimerPosition || 'top-right';
        
        const timerContent = document.createElement('div');
        timerContent.id = 'wh-chain-timer-content';
        
        const timerLabel = document.createElement('span');
        timerLabel.id = 'wh-chain-timer-label';
        timerLabel.textContent = 'Chain: ';
        
        const timerValue = document.createElement('span');
        timerValue.id = 'wh-chain-timer-value';
        timerValue.textContent = '--:--';
        
        const settingsButton = document.createElement('span');
        settingsButton.id = 'wh-chain-timer-settings';
        settingsButton.title = 'Settings';
        settingsButton.textContent = '⚙';
        settingsButton.style.lineHeight = '18px';
        settingsButton.addEventListener('click', (e) => {
            e.stopPropagation();
            toggleSettingsPanel(e);
        });
        
        timerContent.appendChild(timerLabel);
        timerContent.appendChild(timerValue);
        timerContent.appendChild(settingsButton);
        timerBox.appendChild(timerContent);
        
        if (timerBox.offsetWidth < 140) {
            timerBox.style.minWidth = '140px';
        }
        
        document.body.appendChild(timerBox);

        timerBox.addEventListener('click', (e) => {
            if (e.target.id === 'wh-chain-timer-settings') {
                return;
            }
            
            if (timerBox.classList.contains('top-right')) {
                timerBox.classList.remove('top-right');
                timerBox.classList.add('top-center');
                SETTINGS.chainTimerPosition = 'top-center';
            } else if (timerBox.classList.contains('top-center')) {
                timerBox.classList.remove('top-center');
                timerBox.classList.add('top-left');
                SETTINGS.chainTimerPosition = 'top-left';
            } else {
                timerBox.classList.remove('top-left');
                timerBox.classList.add('top-right');
                SETTINGS.chainTimerPosition = 'top-right';
            }
            
            saveSettings();
        });
        
        return timerBox;
    }

    function updateChainTimerBox() {
        if (!SETTINGS.enableChainTimerBox) return;

        const timerBox = document.getElementById('wh-chain-timer-box');
        if (!timerBox) {
            return createChainTimerBox();
        }
        
        const timerLabel = document.getElementById('wh-chain-timer-label');
        const timerValue = document.getElementById('wh-chain-timer-value');
        
        if (!timerLabel || !timerValue) return;

        const chainState = findChainState();

        if (chainState.timeText) {
            if (chainState.timeText.match(/^0*0:0*0$/) || chainState.timeText === '--:--') {
                timerBox.style.display = 'none';
                window.whPreviousChainTime = null;
            } else {
                updateTimerDisplay(chainState.timeText, chainState.isCooldown);
            }
        } else {
            timerBox.style.display = 'none';
            window.whPreviousChainTime = null;
        }

        function findChainState() {
            const result = {
                timeText: null,
                isCooldown: false
            };

            const chainBoxTitle = document.querySelector('.chain-box-title');
            if (chainBoxTitle && chainBoxTitle.textContent.toLowerCase().includes('cooldown')) {
                const timerElement = document.querySelector('.chain-box-timeleft');
                if (timerElement && timerElement.textContent) {
                    result.timeText = timerElement.textContent.trim();
                    result.isCooldown = true;
                    return result;
                }
            }

            const cooldownElement = document.querySelector('.chain-box .chain-cooldown, [class*="chainCooldown"], .cooldown-timer');
            if (cooldownElement && cooldownElement.textContent) {
                result.timeText = cooldownElement.textContent.trim();
                result.isCooldown = true;
                return result;
            }

            const chainElements = document.querySelectorAll('.chain-box, [class*="chainBox"], .chain-wrap');
            for (const el of chainElements) {
                if (el.textContent.toLowerCase().includes('cooldown')) {
                    const timerEl = el.querySelector('.chain-box-timeleft, [class*="timerValue"], .chain-time');
                    if (timerEl && timerEl.textContent) {
                        result.timeText = timerEl.textContent.trim();
                        result.isCooldown = true;
                        return result;
                    }
                    
                    const timerMatch = el.textContent.match(/(\d+:\d+)/);
                    if (timerMatch && timerMatch[1]) {
                        result.timeText = timerMatch[1];
                        result.isCooldown = true;
                        return result;
                    }
                }
            }

            const topBarChain = document.querySelector('.chain');
            if (topBarChain && topBarChain.textContent) {
                if (topBarChain.textContent.toLowerCase().includes('cooldown')) {
                    const match = topBarChain.textContent.match(/(\d+:\d+)/);
                    if (match && match[1]) {
                        result.timeText = match[1];
                        result.isCooldown = true;
                        return result;
                    }
                }
            }

            const headerChainTimer = document.querySelector('.chain-box:not(.tt-modified)');
            if (headerChainTimer) {
                const timerElement = headerChainTimer.querySelector('.chain-box-timeleft');
                if (timerElement && timerElement.textContent) {
                    result.timeText = timerElement.textContent.trim();
                    return result;
                }
            }

            const otherTimerElement = document.querySelector('.chain-box-timeleft, [class*="chainTimerContainer"] [class*="timerValue"], .chain-box .chain-time');
            if (otherTimerElement && otherTimerElement.textContent) {
                result.timeText = otherTimerElement.textContent.trim();
                return result;
            }

            const headerTimer = document.querySelector('.header-chain-box, .websiteHeaderContent .chain');
            if (headerTimer && headerTimer.textContent && headerTimer.textContent.includes('Chain:')) {
                const match = headerTimer.textContent.match(/Chain:\s*(\d+:\d+)/i);
                if (match && match[1]) {
                    result.timeText = match[1];
                    return result;
                }
            }

            const chainNumberFormat = document.querySelector('.chain');
            if (chainNumberFormat && chainNumberFormat.textContent) {
                const match = chainNumberFormat.textContent.match(/Chain:\d+:(\d+)/i);
                if (match && match[1]) {
                    const seconds = parseInt(match[1]) / 1000;
                    if (!isNaN(seconds)) {
                        const minutes = Math.floor(seconds / 60);
                        const remainingSeconds = Math.floor(seconds % 60);
                        result.timeText = `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
                        return result;
                    }
                }
            }

            return result;
        }

        function updateTimerDisplay(timeString, isCooldown) {
            timerBox.style.display = 'flex';

            let formattedTime = timeString;

            if (/^\d+:\d+$/.test(timeString)) {
                formattedTime = formattedTime.replace(/^(\d+):(\d+)$/, (match, min, sec) => {
                    return min.padStart(2, '0') + ':' + sec.padStart(2, '0');
                });
            }

            const timeComponents = formattedTime.split(':');
            const minutes = parseInt(timeComponents[0]);
            const seconds = parseInt(timeComponents[1]);
            const currentTotalSeconds = (minutes * 60) + seconds;

            if (isCooldown) {
                timerLabel.textContent = 'Cooldown: ';
                timerBox.style.minWidth = '170px';
            } else {
                timerLabel.textContent = 'Chain: ';
                timerBox.style.minWidth = '140px';
            }
            
            timerValue.textContent = formattedTime;

            timerBox.classList.remove('warning', 'danger', 'cooldown', 'reset');

            if (!isCooldown && 
                window.whPreviousChainTime !== null && 
                currentTotalSeconds >= 295 && currentTotalSeconds <= 300 && 
                window.whPreviousChainTime < 290) {
                
                timerBox.classList.add('reset');
                
                setTimeout(() => {
                    timerBox.classList.remove('reset');
                }, 3000);
            }

            if (isCooldown) {
                timerBox.classList.add('cooldown');
                timerBox.setAttribute('title', 'Chain Cooldown');
            } else {
                if (minutes === 4 && seconds >= 55 || minutes === 5 && seconds === 0) {
                    timerBox.classList.add('reset');
                    timerBox.setAttribute('title', 'Chain Timer - Fresh Chain');
                } 
                else if (minutes === 0) {
                    timerBox.classList.add('warning');
                    
                    if (seconds <= 30) {
                        timerBox.classList.remove('warning');
                        timerBox.classList.add('danger');
                    }
                }
            }
            
            window.whPreviousChainTime = currentTotalSeconds;
        }
    }

    function initializeFontAwesome() {
        if (!document.querySelector('link[href*="font-awesome"]')) {
            const link = document.createElement('link');
            link.rel = 'stylesheet';
            link.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css';
            document.head.appendChild(link);
        }
    }

    function isChatOverlaying() {
        const button = document.getElementById(SETTINGS_UI.settingsButtonId);
        const panel = document.getElementById(SETTINGS_UI.settingsPanelId);
        
        if (!button) return false;
        
        const buttonRect = button.getBoundingClientRect();
        const panelRect = panel && panel.classList.contains('visible') ? 
                         panel.getBoundingClientRect() : null;
        
        const chatElements = document.querySelectorAll('.chat-box, .chatWindow, .chat-active, .chat-box-content, [class*="chatBox"], .chat-box-input-wrap, .chatContainer, .chat-wrap, .dialogue-box, .message-box');
        
        for (const chat of chatElements) {
            const chatRect = chat.getBoundingClientRect();
            
            const buttonOverlap = !(chatRect.right < buttonRect.left || 
                                 chatRect.left > buttonRect.right || 
                                 chatRect.bottom < buttonRect.top || 
                                 chatRect.top > buttonRect.bottom);
            
            let panelOverlap = false;
            if (panelRect) {
                panelOverlap = !(chatRect.right < panelRect.left || 
                              chatRect.left > panelRect.right || 
                              chatRect.bottom < panelRect.top || 
                              chatRect.top > panelRect.bottom);
            }
            
            if (buttonOverlap || panelOverlap) {
                return true;
            }
        }
        
        return false;
    }
    
    function updateVisibility() {
        const button = document.getElementById(SETTINGS_UI.settingsButtonId);
        const panel = document.getElementById(SETTINGS_UI.settingsPanelId);
        if (!button) return;
        
        if (isChatOverlaying()) {
            button.style.opacity = '0';
            button.style.pointerEvents = 'none';
            
            if (panel && panel.classList.contains('visible')) {
                panel.classList.remove('visible');
            }
        } else {
            button.style.opacity = '1';
            button.style.pointerEvents = 'auto';
        }
    }
    
    function toggleSettingsPanel(e) {
        if (e) {
            e.stopPropagation();
        }
        
        const panel = document.getElementById(SETTINGS_UI.settingsPanelId);
        if (!panel) return;
        
        const timerBox = document.getElementById('wh-chain-timer-box');
        if (timerBox) {
            const timerRect = timerBox.getBoundingClientRect();
            panel.style.top = (timerRect.bottom + 10) + 'px';
            panel.style.left = '50%';
            panel.style.transform = 'translateX(-50%)';
            panel.style.right = 'auto';
            panel.style.bottom = 'auto';
        } else {
            panel.style.top = '100px';
            panel.style.left = '50%';
            panel.style.transform = 'translateX(-50%)';
            panel.style.right = 'auto';
            panel.style.bottom = 'auto';
        }
        
        if (!isChatOverlaying()) {
            panel.classList.toggle('visible');
            
            setTimeout(updateVisibility, 50);
        }
    }

    function addSettingsPanelStyles() {
        const styleElement = document.createElement('style');
        styleElement.textContent = `
            #${SETTINGS_UI.settingsPanelId} {
                position: fixed;
                left: auto;
                right: auto;
                bottom: auto;
                top: auto;
                width: ${SETTINGS_UI.panelDimensions.width};
                max-height: ${SETTINGS_UI.panelDimensions.maxHeight};
                background-color: #1c1c1c;
                border: 1px solid #444;
                border-radius: 5px;
                box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
                z-index: 10000;
                display: none;
                overflow-y: auto;
                color: #fff;
                font-family: Arial, sans-serif;
                transform: translateY(20px);
                opacity: 0;
                transition: transform 0.3s ease, opacity 0.3s ease;
            }

            #${SETTINGS_UI.settingsPanelId}.visible {
                display: block;
                transform: translateY(0);
                opacity: 1;
            }
            
            /* Style for settings button on chain timer */
            #${SETTINGS_UI.settingsButtonId} {
                display: inline-block;
                opacity: 0.8;
                text-shadow: 0 0 3px rgba(0,0,0,0.5);
            }
            
            #${SETTINGS_UI.settingsButtonId}:hover {
                opacity: 1;
                text-shadow: 0 0 5px rgba(255,255,255,0.8);
            }
        `;
        document.head.appendChild(styleElement);
    }

    function initialize() {
        if (!window.location.href.includes('factions.php')) return;

        loadSettings();

        initializeFontAwesome();

        document.body.classList.add('tornWarHelper');
        addStyles();
        addSettingsPanelStyles();

        setTimeout(createSettingsPanel, 1000);

        window.whPreviousChainTime = null;

        if (SETTINGS.enableChainTimerBox) {
            createChainTimerBox();
            setInterval(updateChainTimerBox, SETTINGS.chainTimerRefreshRate);
        }

        setTimeout(() => {
            highlightFactionMembers();
            if (SETTINGS.enableChainTimerBox) {
                updateChainTimerBox();
            }
        }, 1000);

        setInterval(highlightFactionMembers, SETTINGS.refreshRate);
        
        setInterval(updateVisibility, 300);

        setupMutationObservers();
    }

    function highlightFactionMembers() {
        processRows(getRows('enemy'));
        processRows(getRows('your'));
    }

    function setupMutationObservers() {
        const memberObserver = new MutationObserver(mutations => {
            for (const mutation of mutations) {
                if (mutation.type === 'childList' &&
                    mutation.target.classList &&
                    (mutation.target.classList.contains('members-list') ||
                     mutation.target.className.includes('membersWrap___') ||
                     mutation.target.className.includes('member'))) {

                    highlightFactionMembers();
                    break;
                }
            }
        });

        memberObserver.observe(document.body, {
            childList: true,
            subtree: true
        });
        
        const chatObserver = new MutationObserver(() => {
            updateVisibility();
        });
        
        chatObserver.observe(document.body, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: ['class', 'style']
        });
        
        document.addEventListener('click', (e) => {
            const panel = document.getElementById(SETTINGS_UI.settingsPanelId);
            const settingsBtn = document.getElementById(SETTINGS_UI.settingsButtonId);
            
            if (panel && panel.classList.contains('visible') && 
                !panel.contains(e.target) && 
                settingsBtn && !settingsBtn.contains(e.target)) {
                
                panel.classList.remove('visible');
            }
        });

        let lastUrl = location.href;
        const urlObserver = new MutationObserver(() => {
            if (location.href !== lastUrl) {
                lastUrl = location.href;

                if (window.location.href.includes('factions.php')) {
                    setTimeout(() => {
                        if (SETTINGS.enableChainTimerBox && !document.getElementById('wh-chain-timer-box')) {
                            createChainTimerBox();
                        }
                        
                        highlightFactionMembers();

                        if (SETTINGS.enableChainTimerBox) {
                            updateChainTimerBox();
                        }
                    }, 500);
                }
            }
        });

        urlObserver.observe(document, {
            childList: true,
            subtree: true
        });
    }

    function createSettingsPanel() {
        if (document.getElementById(SETTINGS_UI.settingsPanelId)) {
            return;
        }
        
        const panel = document.createElement('div');
        panel.id = SETTINGS_UI.settingsPanelId;
        
        panel.innerHTML = `
            <div class="wh-settings-header">
                <span>${SETTINGS_UI.panelTitle}</span>
                <span class="wh-settings-close">&times;</span>
            </div>
        `;
        
        SETTINGS_UI.sections.forEach(section => {
            const sectionEl = document.createElement('div');
            sectionEl.className = 'wh-settings-section';
            
            sectionEl.innerHTML = `<div class="wh-section-title">${section.title}</div>`;
            
            section.settings.forEach(setting => {
                const settingEl = document.createElement('div');
                settingEl.className = 'wh-setting-item';
                
                let inputHtml = '';
                
                switch (setting.type) {
                    case 'checkbox':
                        const checked = SETTINGS[setting.id] ? 'checked' : '';
                        inputHtml = `
                            <label class="wh-setting-label">
                                <input type="checkbox" id="wh-setting-${setting.id}" data-setting-id="${setting.id}" ${checked}>
                                ${setting.label}
                                <span class="wh-setting-tooltip" title="${setting.tooltip}">ⓘ</span>
                            </label>
                        `;
                        break;
                        
                    case 'select':
                        const options = setting.options.map(option => {
                            const selected = SETTINGS[setting.id] === option.value ? 'selected' : '';
                            return `<option value="${option.value}" ${selected}>${option.label}</option>`;
                        }).join('');
                        
                        inputHtml = `
                            <label class="wh-setting-label">
                                ${setting.label}
                                <span class="wh-setting-tooltip" title="${setting.tooltip}">ⓘ</span>
                            </label>
                            <select id="wh-setting-${setting.id}" data-setting-id="${setting.id}">
                                ${options}
                            </select>
                        `;
                        break;
                        
                    case 'number':
                        const value = SETTINGS[setting.id] || setting.default;
                        inputHtml = `
                            <label class="wh-setting-label">
                                ${setting.label}
                                <span class="wh-setting-tooltip" title="${setting.tooltip}">ⓘ</span>
                            </label>
                            <div style="display: flex; align-items: center;">
                                <input type="number" id="wh-setting-${setting.id}" data-setting-id="${setting.id}" 
                                    min="${setting.min}" max="${setting.max}" step="${setting.step}" value="${value}"
                                    style="flex: 1;">
                            </div>
                        `;
                        break;
                        
                    case 'range':
                        const rangeValue = setting.valueParser 
                            ? setting.valueParser(SETTINGS[setting.id]) 
                            : (SETTINGS[setting.id] || setting.default);
                            
                        inputHtml = `
                            <label class="wh-setting-label">
                                ${setting.label}
                                <span class="wh-setting-tooltip" title="${setting.tooltip}">ⓘ</span>
                            </label>
                            <div style="display: flex; align-items: center;">
                                <input type="range" id="wh-setting-${setting.id}" data-setting-id="${setting.id}" 
                                    min="${setting.min}" max="${setting.max}" step="${setting.step}" value="${rangeValue}">
                                <span class="wh-range-value" id="wh-range-value-${setting.id}">${rangeValue}</span>
                            </div>
                        `;
                        break;
                }
                
                settingEl.innerHTML = inputHtml;
                sectionEl.appendChild(settingEl);
            });
            
            panel.appendChild(sectionEl);
        });
        
        const footer = document.createElement('div');
        footer.className = 'wh-settings-footer';
        footer.innerHTML = `
            <button class="wh-reset-btn">Reset</button>
            <button class="wh-save-btn">Save</button>
            <span class="wh-save-message">Settings saved!</span>
        `;
        panel.appendChild(footer);
        
        document.body.appendChild(panel);
        
        setupSettingsPanelEvents();
    }

    function setupSettingsPanelEvents() {
        const panel = document.getElementById(SETTINGS_UI.settingsPanelId);
        if (!panel) return;
        
        panel.querySelector('.wh-settings-close').addEventListener('click', toggleSettingsPanel);
        
        panel.querySelectorAll('input[type="range"]').forEach(input => {
            const settingId = input.getAttribute('data-setting-id');
            const valueDisplay = document.getElementById(`wh-range-value-${settingId}`);
            
            if (valueDisplay) {
                input.addEventListener('input', () => {
                    valueDisplay.textContent = input.value;
                });
            }
        });
        
        panel.querySelector('.wh-save-btn').addEventListener('click', () => {
            saveSettingsFromUI();
            showSaveMessage();
        });
        
        panel.querySelector('.wh-reset-btn').addEventListener('click', resetSettings);
        
        document.addEventListener('click', (e) => {
            const settingsBtn = document.getElementById(SETTINGS_UI.settingsButtonId);
            
            if (panel.classList.contains('visible') && 
                !panel.contains(e.target) && 
                !settingsBtn.contains(e.target)) {
                toggleSettingsPanel();
            }
        });
    }

    function saveSettingsFromUI() {
        const panel = document.getElementById(SETTINGS_UI.settingsPanelId);
        if (!panel) return;
        
        panel.querySelectorAll('[data-setting-id]').forEach(input => {
            const settingId = input.getAttribute('data-setting-id');
            let value;
            
            if (input.type === 'checkbox') {
                value = input.checked;
            } else if (input.type === 'number') {
                value = parseFloat(input.value);
            } else if (input.type === 'range') {
                const section = SETTINGS_UI.sections.find(s => 
                    s.settings.some(set => set.id === settingId)
                );
                const setting = section?.settings.find(s => s.id === settingId);
                
                if (setting && setting.valueConverter) {
                    value = setting.valueConverter(parseFloat(input.value));
                } else {
                    value = parseFloat(input.value);
                }
            } else {
                value = input.value;
            }
            
            SETTINGS[settingId] = value;
        });
        
        saveSettings();
        
        refreshUIWithNewSettings();
    }

    function showSaveMessage() {
        const saveMsg = document.querySelector(`#${SETTINGS_UI.settingsPanelId} .wh-save-message`);
        if (saveMsg) {
            saveMsg.classList.add('visible');
            
            setTimeout(() => {
                saveMsg.classList.remove('visible');
            }, SETTINGS_UI.saveMessageDuration);
        }
    }

    function resetSettings() {
        if (confirm('Are you sure you want to reset all settings to defaults?')) {
            localStorage.removeItem('tornWarHelperSettings');
            
            window.location.reload();
        }
    }

    function refreshUIWithNewSettings() {
        const existingTimerBox = document.getElementById('wh-chain-timer-box');
        if (existingTimerBox) {
            existingTimerBox.remove();
        }
        
        if (SETTINGS.enableChainTimerBox) {
            createChainTimerBox();
            updateChainTimerBox();
        }
        
        document.head.querySelector('style')?.remove();
        addStyles();
        
        highlightFactionMembers();
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initialize);
    } else {
        initialize();
    }
})();