[TORN] OC 2.0 Priority Helper

Adds filters to show/hide crime scenario cards and disable low-success chance join buttons

// ==UserScript==
// @name         [TORN] OC 2.0 Priority Helper
// @namespace    http://tampermonkey.net/
// @version      2.0.0
// @description  Adds filters to show/hide crime scenario cards and disable low-success chance join buttons
// @match        https://www.torn.com/factions.php*
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // Constants for localStorage keys
    const STORAGE_KEYS = {
        SHOW: 'tornCrimeFilter_show',
        LEVEL: 'tornCrimeFilter_level',
        POSITION_X: 'tornCrimeFilter_posX',
        POSITION_Y: 'tornCrimeFilter_posY',
        IS_COLLAPSED: 'tornCrimeFilter_collapsed',
        HIGHLIGHT_PRIORITY: 'tornCrimeFilter_highlightPriority',
        MIN_SUCCESS: 'tornCrimeFilter_minSuccess'
    };

    // Default settings
    const DEFAULT_SETTINGS = {
        show: true,
        level: 1,
        positionX: 0,
        positionY: 0,
        isCollapsed: false,
        highlightPriority: true,
        minSuccess: 62
    };

    // Priority colors
    const PRIORITY_COLORS = {
        waiting: 'rgba(83, 152, 237, 0.4)',      // Blue - #1 Waiting
        empty: 'rgba(76, 175, 80, 0.4)',         // Green - #2 Empty
        nobodyQueueing: 'rgba(255, 152, 0, 0.4)', // Orange - #3 Nobody queueing
        others: 'rgba(0, 0, 0, 0)'               // Transparent - #4 All others
    };

    // Load saved settings or use defaults
    function loadSettings() {
        return {
            show: localStorage.getItem(STORAGE_KEYS.SHOW) !== 'false',
            level: parseInt(localStorage.getItem(STORAGE_KEYS.LEVEL)) || DEFAULT_SETTINGS.level,
            positionX: parseInt(localStorage.getItem(STORAGE_KEYS.POSITION_X)) || DEFAULT_SETTINGS.positionX,
            positionY: parseInt(localStorage.getItem(STORAGE_KEYS.POSITION_Y)) || DEFAULT_SETTINGS.positionY,
            isCollapsed: localStorage.getItem(STORAGE_KEYS.IS_COLLAPSED) === 'true',
            highlightPriority: localStorage.getItem(STORAGE_KEYS.HIGHLIGHT_PRIORITY) !== 'false',
            minSuccess: parseInt(localStorage.getItem(STORAGE_KEYS.MIN_SUCCESS)) || DEFAULT_SETTINGS.minSuccess
        };
    }

    // Save settings to localStorage
    function saveSettings(settings) {
        Object.entries(settings).forEach(([key, value]) => {
            localStorage.setItem(STORAGE_KEYS[key.toUpperCase()], value);
        });
    }

    // Ensure position is within viewport bounds
    function keepInBounds(element) {
        const rect = element.getBoundingClientRect();
        const viewportWidth = window.innerWidth;
        const viewportHeight = window.innerHeight;

        // Extract transformed position
        const transformMatrix = window.getComputedStyle(element).transform;
        let currentX = 0;
        let currentY = 0;

        if (transformMatrix && transformMatrix !== 'none') {
            const matrixValues = transformMatrix.match(/matrix.*\((.+)\)/)[1].split(', ');
            currentX = parseInt(matrixValues[4]) || 0;
            currentY = parseInt(matrixValues[5]) || 0;
        }

        let newX = currentX;
        let newY = currentY;

        if (rect.right > viewportWidth) {
            newX = viewportWidth - rect.width;
        }
        if (rect.left < 0) {
            newX = 0;
        }
        if (rect.bottom > viewportHeight) {
            newY = viewportHeight - rect.height;
        }
        if (rect.top < 0) {
            newY = 0;
        }

        return { x: newX, y: newY };
    }

    // Get slot status - returns one of: 'completed', 'inProgress', 'notStarted', 'empty'
    function getSlotStatus(slotElement) {
        // Check if the slot is empty
        const svgElement = slotElement.querySelector('.slotIcon___VVnQy svg');
        if (svgElement) return 'empty';

        // Get the planning element
        const planningElement = slotElement.querySelector('.planning___CjB09');
        if (!planningElement) return 'unknown';

        // Get the conic-gradient style to determine progress
        const style = planningElement.getAttribute('style');
        if (!style) return 'unknown';

        // Extract the degrees value from the conic-gradient
        const match = style.match(/conic-gradient\([^,]+,\s*[^,]+\s+([0-9.]+)deg/);
        if (!match) return 'unknown';

        const degrees = parseFloat(match[1]);

        if (degrees >= 360) return 'completed';
        if (degrees > 0) return 'inProgress';
        return 'notStarted';
    }

    // Check if a slot is filled (has a participant)
    function isSlotFilled(slotElement) {
        return slotElement.querySelector('.validSlot___n6ueL') !== null;
    }

    // Get the success chance of a slot (0-100)
    function getSuccessChance(slotElement) {
        const successChanceEl = slotElement.querySelector('.successChance___ddHsR');
        if (!successChanceEl) return 0;

        const chanceText = successChanceEl.textContent.trim();
        return parseInt(chanceText, 10) || 0;
    }

    // Process join buttons - disable if success chance is too low
    function processJoinButtons(minSuccess) {
        let disabledCount = 0;
        let totalButtons = 0;

        document.querySelectorAll('.joinButton___Ikoyy').forEach(button => {
            // Find the parent slot element to check the success chance
            const slotElement = button.closest('.wrapper___Lpz_D');
            if (!slotElement) return;

            const successChance = getSuccessChance(slotElement);
            totalButtons++;

            // Apply disabled state based on success chance
            if (successChance < minSuccess) {
                button.disabled = true;
                button.title = `Success chance (${successChance}) is below minimum (${minSuccess})`;
                disabledCount++;

                // Add visual indicator
                button.style.opacity = '0.6';
                button.style.position = 'relative';
                button.style.cursor = 'not-allowed';

                // Check if we need to add the strike-through
                if (!button.querySelector('.strike-through')) {
                    const strikeThrough = document.createElement('div');
                    strikeThrough.className = 'strike-through';
                    strikeThrough.style.position = 'absolute';
                    strikeThrough.style.top = '50%';
                    strikeThrough.style.left = '0';
                    strikeThrough.style.right = '0';
                    strikeThrough.style.height = '1px';
                    strikeThrough.style.backgroundColor = 'red';
                    strikeThrough.style.transform = 'rotate(-10deg)';
                    button.appendChild(strikeThrough);
                }
            } else {
                // Ensure button is enabled and clean
                button.disabled = false;
                button.title = '';
                button.style.opacity = '';
                button.style.cursor = '';

                // Remove strike-through if exists
                const strikeThrough = button.querySelector('.strike-through');
                if (strikeThrough) {
                    strikeThrough.remove();
                }
            }
        });

        return { disabledCount, totalButtons };
    }

    // Process crime cards, add badges and apply highlighting
    function processCrimeCards(highlightPriority, minSuccess) {
        const priorityCounts = {
            waiting: 0,
            empty: 0,
            nobodyQueueing: 0,
            others: 0
        };

        document.querySelectorAll('.wrapper___U2Ap7').forEach(card => {
            // Add participant badge if not exists
            if (!card.querySelector('.participant-badge')) {
                // Get the participants container
                const participantsContainer = card.querySelector('.wrapper___g3mPt');
                if (participantsContainer) {
                    // Count filled slots and total slots
                    const allSlots = participantsContainer.querySelectorAll('.wrapper___Lpz_D');
                    const filledSlots = participantsContainer.querySelectorAll('.validSlot___n6ueL');

                    // Create the badge
                    const badge = document.createElement('div');
                    badge.className = 'participant-badge';
                    badge.style.position = 'absolute';
                    badge.style.top = '0';
                    badge.style.right = '0';
                    badge.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
                    badge.style.color = '#fff';
                    badge.style.padding = '3px 8px';
                    badge.style.borderRadius = '0 0 0 8px';
                    badge.style.fontSize = '12px';
                    badge.style.fontWeight = 'bold';
                    badge.style.zIndex = '10';
                    badge.style.textShadow = '1px 1px 1px rgba(0,0,0,0.5)';

                    // Set the badge text
                    badge.textContent = `${filledSlots.length}/${allSlots.length} slots`;

                    // Add the badge to the card
                    card.querySelector('.contentLayer___IYFdz').appendChild(badge);
                }
            }

            // Add or update priority badge
            if (highlightPriority) {
                // First, remove any existing priority badge
                const existingBadge = card.querySelector('.priority-badge');
                if (existingBadge) {
                    existingBadge.remove();
                }

                // Remove previous highlight
                card.style.backgroundColor = '';
                card.style.boxShadow = '';

                // Get the participants container and analyze slots
                const participantsContainer = card.querySelector('.wrapper___g3mPt');
                if (participantsContainer) {
                    const slots = Array.from(participantsContainer.querySelectorAll('.wrapper___Lpz_D'));
                    const filledSlots = slots.filter(slot => isSlotFilled(slot));
                    const emptySlots = slots.filter(slot => !isSlotFilled(slot));

                    // Get status of each slot
                    const slotStatuses = slots.map(slot => ({
                        element: slot,
                        filled: isSlotFilled(slot),
                        status: getSlotStatus(slot)
                    }));

                    let priorityType = '';
                    let priorityLabel = '';

                    // Priority #2: Empty - No participant slots are filled
                    if (filledSlots.length === 0) {
                        priorityType = 'empty';
                        priorityLabel = 'Empty';
                    }
                    // Priority #1: Waiting - Participant slots not fully filled, and no slots with "in progress" status
                    else if (filledSlots.length < slots.length && !slotStatuses.some(s => s.filled && s.status === 'inProgress')) {
                        priorityType = 'waiting';
                        priorityLabel = 'Waiting';
                    }
                    // Priority #3: Nobody queueing - One or more slots filled, all are completed or in progress, rest are empty
                    else if (filledSlots.length > 0 &&
                            filledSlots.every(slot => ['completed', 'inProgress'].includes(getSlotStatus(slot))) &&
                            slots.every(slot => isSlotFilled(slot) || getSlotStatus(slot) === 'empty')) {
                        priorityType = 'nobodyQueueing';
                        priorityLabel = 'Nobody Queueing';
                    }
                    // Priority #4: All others
                    else {
                        priorityType = 'others';
                        priorityLabel = '';
                    }

                    // Apply highlights based on priority
                    if (priorityType && priorityType !== 'others') {
                        card.style.backgroundColor = PRIORITY_COLORS[priorityType];
                        card.style.boxShadow = `0 0 8px ${PRIORITY_COLORS[priorityType]}`;

                        // Create priority badge if it's not "others"
                        const priorityBadge = document.createElement('div');
                        priorityBadge.className = 'priority-badge';
                        priorityBadge.style.position = 'absolute';
                        priorityBadge.style.top = '0';
                        priorityBadge.style.left = '0';
                        priorityBadge.style.backgroundColor = PRIORITY_COLORS[priorityType].replace('0.4', '0.8');
                        priorityBadge.style.color = '#fff';
                        priorityBadge.style.padding = '3px 8px';
                        priorityBadge.style.borderRadius = '0 0 8px 0';
                        priorityBadge.style.fontSize = '12px';
                        priorityBadge.style.fontWeight = 'bold';
                        priorityBadge.style.zIndex = '10';
                        priorityBadge.style.textShadow = '1px 1px 1px rgba(0,0,0,0.5)';

                        // Set badge text based on priority
                        priorityBadge.textContent = `#${priorityType === 'waiting' ? '1' : priorityType === 'empty' ? '2' : '3'} ${priorityLabel}`;

                        // Add the badge to the card
                        card.querySelector('.contentLayer___IYFdz').appendChild(priorityBadge);
                    }

                    // Count priorities for stats
                    if (priorityType) {
                        priorityCounts[priorityType]++;
                    }
                }
            }
        });

        // Process join buttons based on minimum success chance
        const joinButtonStats = processJoinButtons(minSuccess);

        return {
            priorityCounts,
            joinButtonStats
        };
    }

    function updateFilters() {
        const showCheckbox = document.getElementById('crimeFilterCheckbox');
        const levelInput = document.getElementById('minCrimeLevelInput');
        const highlightPriorityCheckbox = document.getElementById('highlightPriorityCheckbox');
        const minSuccessInput = document.getElementById('minSuccessInput');

        const shouldShow = showCheckbox.checked;
        const minLevel = parseInt(levelInput.value, 10) || 1;
        const highlightPriority = highlightPriorityCheckbox.checked;
        const minSuccess = parseInt(minSuccessInput.value, 10) || DEFAULT_SETTINGS.minSuccess;

        // Save filter settings
        saveSettings({
            show: shouldShow,
            level: minLevel,
            highlightPriority: highlightPriority,
            minSuccess: minSuccess
        });

        // Process crime cards and get statistics
        const processResult = processCrimeCards(highlightPriority, minSuccess);
        const priorityCounts = processResult.priorityCounts;
        const joinButtonStats = processResult.joinButtonStats;

        // Update status text
        const statusEl = document.getElementById('filterStatus');
        let visibleCount = 0;
        let totalCount = 0;
        let filledCount = 0;
        let openCount = 0;

        document.querySelectorAll('.wrapper___U2Ap7').forEach(card => {
            const levelEl = card.querySelector('.level___sBl49');
            totalCount++;

            let shouldDisplay = true;

            if (levelEl) {
                const text = levelEl.textContent.trim();
                const parts = text.split('/');
                const level = parseInt(parts[0], 10) || 0;

                // Apply level filter if checkbox is checked
                if (shouldShow && level < minLevel) {
                    shouldDisplay = false;
                }
            }

            // Count filled and open slots for visible cards
            if (shouldDisplay) {
                visibleCount++;

                // Get filled slots count for this card
                const participantsContainer = card.querySelector('.wrapper___g3mPt');
                if (participantsContainer) {
                    const allSlots = participantsContainer.querySelectorAll('.wrapper___Lpz_D').length;
                    const filledSlots = participantsContainer.querySelectorAll('.validSlot___n6ueL').length;

                    filledCount += filledSlots;
                    openCount += (allSlots - filledSlots);
                }
            }

            // Apply visibility
            card.style.display = shouldDisplay ? '' : 'none';
        });

        // Update status with priority counts and join button stats
        let priorityText = '';
        if (highlightPriority) {
            priorityText = ` | P1: ${priorityCounts.waiting} | P2: ${priorityCounts.empty} | P3: ${priorityCounts.nobodyQueueing}`;
        }

        let successText = '';
        if (joinButtonStats.totalButtons > 0) {
            successText = ` | Blocked: ${joinButtonStats.disabledCount}/${joinButtonStats.totalButtons} join buttons`;
        }

        statusEl.textContent = `Showing ${visibleCount} of ${totalCount} crimes | ${filledCount} filled slots | ${openCount} open slots${priorityText}${successText}`;
    }

    window.addEventListener('load', function() {
        const crimesContainer = document.getElementById('faction-crimes');
        if (!crimesContainer) return;

        // Create custom CSS for the badges
        const style = document.createElement('style');
        style.textContent = `
            .participant-badge, .priority-badge {
                transition: all 0.2s ease;
            }
            .wrapper___U2Ap7:hover .participant-badge {
                background-color: rgba(40, 167, 69, 0.8) !important;
            }
            .wrapper___U2Ap7:hover .priority-badge {
                filter: brightness(1.2);
            }

            /* Success chance tooltip */
            .join-button-tooltip {
                position: absolute;
                background-color: rgba(0, 0, 0, 0.9);
                color: white;
                padding: 5px 8px;
                border-radius: 4px;
                font-size: 12px;
                z-index: 10001;
                max-width: 250px;
                box-shadow: 0 2px 5px rgba(0,0,0,0.2);
                pointer-events: none;
                transition: opacity 0.15s;
                white-space: nowrap;
                opacity: 0;
            }

            /* Legend styles */
            .highlight-legend {
                margin-top: 8px;
                display: grid;
                grid-template-columns: 12px auto;
                grid-gap: 5px 8px;
                align-items: center;
                font-size: 12px;
            }
            .legend-color {
                width: 12px;
                height: 12px;
                border-radius: 2px;
            }
        `;
        document.head.appendChild(style);

        // Load saved settings
        const settings = loadSettings();

        // Create filter UI container
        const filterDiv = document.createElement('div');
        Object.assign(filterDiv.style, {
            position: 'fixed',
            top: '10px',
            right: '10px',
            backgroundColor: 'rgba(0, 0, 0, 0.8)',
            color: '#fff',
            padding: '12px',
            zIndex: '10000',
            borderRadius: '8px',
            fontFamily: 'Arial, sans-serif',
            fontSize: '14px',
            boxShadow: '0 2px 5px rgba(0,0,0,0.2)',
            display: 'flex',
            flexDirection: 'column',
            gap: '8px',
            transition: 'opacity 0.1s ease',
            cursor: 'default'
        });

        // Create header with title and collapse button
        const headerDiv = document.createElement('div');
        headerDiv.style.display = 'flex';
        headerDiv.style.justifyContent = 'space-between';
        headerDiv.style.alignItems = 'center';
        headerDiv.style.marginBottom = '8px';
        headerDiv.style.cursor = 'move';
        headerDiv.style.userSelect = 'none'; // Prevent text selection while dragging

        const titleSpan = document.createElement('span');
        titleSpan.textContent = 'Crime Filter';
        titleSpan.style.fontWeight = 'bold';

        const collapseButton = document.createElement('button');
        collapseButton.textContent = settings.isCollapsed ? '▼' : '▲';
        collapseButton.style.background = 'none';
        collapseButton.style.border = 'none';
        collapseButton.style.color = '#fff';
        collapseButton.style.cursor = 'pointer';
        collapseButton.style.padding = '0 5px';

        // Create content container
        const contentDiv = document.createElement('div');
        contentDiv.style.display = settings.isCollapsed ? 'none' : 'block';

        // Create controls container
        const controlsDiv = document.createElement('div');
        controlsDiv.style.display = 'flex';
        controlsDiv.style.flexDirection = 'column';
        controlsDiv.style.gap = '10px';

        // Create first row (level filter)
        const filterRow = document.createElement('div');
        filterRow.style.display = 'flex';
        filterRow.style.alignItems = 'center';
        filterRow.style.gap = '10px';

        // Create checkbox group
        const checkboxGroup = document.createElement('div');
        checkboxGroup.style.display = 'flex';
        checkboxGroup.style.alignItems = 'center';

        const checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.id = 'crimeFilterCheckbox';
        checkbox.checked = settings.show;
        checkbox.style.marginRight = '5px';

        const checkboxLabel = document.createElement('label');
        checkboxLabel.htmlFor = 'crimeFilterCheckbox';
        checkboxLabel.textContent = 'Filter by Level';
        checkboxLabel.title = 'When checked, filters crime cards by level. When unchecked, shows all cards.';
        checkboxLabel.style.cursor = 'pointer';

        // Create level input group
        const levelGroup = document.createElement('div');
        levelGroup.style.display = 'flex';
        levelGroup.style.alignItems = 'center';

        const levelLabel = document.createElement('label');
        levelLabel.htmlFor = 'minCrimeLevelInput';
        levelLabel.textContent = 'Min Level:';

        const levelInput = document.createElement('input');
        levelInput.type = 'number';
        levelInput.id = 'minCrimeLevelInput';
        levelInput.min = '1';
        levelInput.max = '10';
        levelInput.value = settings.level;
        levelInput.style.width = '45px';
        levelInput.style.marginLeft = '5px';
        levelInput.style.padding = '2px 4px';

        // Create second row (highlight checkbox and success chance input)
        const highlightRow = document.createElement('div');
        highlightRow.style.display = 'flex';
        highlightRow.style.alignItems = 'center';
        highlightRow.style.justifyContent = 'space-between';

        // Highlight checkbox group
        const highlightCheckboxGroup = document.createElement('div');
        highlightCheckboxGroup.style.display = 'flex';
        highlightCheckboxGroup.style.alignItems = 'center';

        const highlightCheckbox = document.createElement('input');
        highlightCheckbox.type = 'checkbox';
        highlightCheckbox.id = 'highlightPriorityCheckbox';
        highlightCheckbox.checked = settings.highlightPriority;
        highlightCheckbox.style.marginRight = '5px';

        const highlightLabel = document.createElement('label');
        highlightLabel.htmlFor = 'highlightPriorityCheckbox';
        highlightLabel.textContent = 'Highlight Priority';
        highlightLabel.title = 'Highlights crimes based on priority status';
        highlightLabel.style.cursor = 'pointer';

        // Min success chance group
        const successGroup = document.createElement('div');
        successGroup.style.display = 'flex';
        successGroup.style.alignItems = 'center';

        const successLabel = document.createElement('label');
        successLabel.htmlFor = 'minSuccessInput';
        successLabel.textContent = 'Min Success:';

        const successInput = document.createElement('input');
        successInput.type = 'number';
        successInput.id = 'minSuccessInput';
        successInput.min = '0';
        successInput.max = '100';
        successInput.value = settings.minSuccess;
        successInput.style.width = '45px';
        successInput.style.marginLeft = '5px';
        successInput.style.padding = '2px 4px';

        // Create legend for highlight colors
        const legendDiv = document.createElement('div');
        legendDiv.className = 'highlight-legend';
        legendDiv.style.display = settings.highlightPriority ? 'grid' : 'none';

        // Priority #1 legend
        const p1ColorDiv = document.createElement('div');
        p1ColorDiv.className = 'legend-color';
        p1ColorDiv.style.backgroundColor = PRIORITY_COLORS.waiting;
        const p1TextDiv = document.createElement('div');
        p1TextDiv.textContent = '#1 Waiting';

        // Priority #2 legend
        const p2ColorDiv = document.createElement('div');
        p2ColorDiv.className = 'legend-color';
        p2ColorDiv.style.backgroundColor = PRIORITY_COLORS.empty;
        const p2TextDiv = document.createElement('div');
        p2TextDiv.textContent = '#2 Empty';

        // Priority #3 legend
        const p3ColorDiv = document.createElement('div');
        p3ColorDiv.className = 'legend-color';
        p3ColorDiv.style.backgroundColor = PRIORITY_COLORS.nobodyQueueing;
        const p3TextDiv = document.createElement('div');
        p3TextDiv.textContent = '#3 Nobody Queueing';

        // Add legend items
        legendDiv.appendChild(p1ColorDiv);
        legendDiv.appendChild(p1TextDiv);
        legendDiv.appendChild(p2ColorDiv);
        legendDiv.appendChild(p2TextDiv);
        legendDiv.appendChild(p3ColorDiv);
        legendDiv.appendChild(p3TextDiv);

        // Create status text
        const statusEl = document.createElement('div');
        statusEl.id = 'filterStatus';
        statusEl.style.fontSize = '12px';
        statusEl.style.color = '#aaa';
        statusEl.style.marginTop = '4px';

        // Assemble the UI
        headerDiv.appendChild(titleSpan);
        headerDiv.appendChild(collapseButton);

        checkboxGroup.appendChild(checkbox);
        checkboxGroup.appendChild(checkboxLabel);
        levelGroup.appendChild(levelLabel);
        levelGroup.appendChild(levelInput);
        filterRow.appendChild(checkboxGroup);
        filterRow.appendChild(levelGroup);

        highlightCheckboxGroup.appendChild(highlightCheckbox);
        highlightCheckboxGroup.appendChild(highlightLabel);
        successGroup.appendChild(successLabel);
        successGroup.appendChild(successInput);

        highlightRow.appendChild(highlightCheckboxGroup);
        highlightRow.appendChild(successGroup);

        controlsDiv.appendChild(filterRow);
        controlsDiv.appendChild(highlightRow);
        controlsDiv.appendChild(legendDiv);

        contentDiv.appendChild(controlsDiv);
        contentDiv.appendChild(statusEl);

        filterDiv.appendChild(headerDiv);
        filterDiv.appendChild(contentDiv);
        document.body.appendChild(filterDiv);

        // Set initial position from saved settings
        filterDiv.style.transform = `translate(${settings.positionX}px, ${settings.positionY}px)`;

        // Collapse button functionality
        collapseButton.addEventListener('click', (e) => {
            e.stopPropagation();
            settings.isCollapsed = !settings.isCollapsed;
            contentDiv.style.display = settings.isCollapsed ? 'none' : 'block';
            collapseButton.textContent = settings.isCollapsed ? '▼' : '▲';
            saveSettings({ isCollapsed: settings.isCollapsed });
        });

        // Highlight checkbox functionality
        highlightCheckbox.addEventListener('change', function() {
            legendDiv.style.display = this.checked ? 'grid' : 'none';
            updateFilters();
        });

        // Add event listeners for filters
        checkbox.addEventListener('change', updateFilters);
        levelInput.addEventListener('input', updateFilters);
        levelInput.addEventListener('change', updateFilters);
        successInput.addEventListener('input', updateFilters);
        successInput.addEventListener('change', updateFilters);

        // Improved drag and drop implementation
        let isDragging = false;
        let xOffset = settings.positionX;
        let yOffset = settings.positionY;
        let startX, startY;

        function dragStart(e) {
            // Only start dragging if clicking on the header or title
            if (e.target === headerDiv || e.target === titleSpan) {
                e.preventDefault();
                isDragging = true;

                // Use clientX/Y for mouse position
                startX = e.clientX;
                startY = e.clientY;

                // Add visual feedback for dragging
                filterDiv.style.opacity = '0.8';
                filterDiv.style.transition = 'opacity 0.1s ease';
                document.body.style.cursor = 'move';

                // Disable text selection
                document.body.style.userSelect = 'none';
            }
        }

        function drag(e) {
            if (!isDragging) return;

            e.preventDefault();

            // Calculate how far we've moved
            const dx = e.clientX - startX;
            const dy = e.clientY - startY;

            // Update position based on movement
            xOffset += dx;
            yOffset += dy;

            // Update position directly with transform
            filterDiv.style.transform = `translate(${xOffset}px, ${yOffset}px)`;

            // Update starting point for next movement
            startX = e.clientX;
            startY = e.clientY;
        }

        function dragEnd(e) {
            if (!isDragging) return;

            // Clean up
            isDragging = false;
            filterDiv.style.opacity = '1';
            document.body.style.cursor = 'default';
            document.body.style.userSelect = '';

            // Check bounds and adjust if needed
            const bounds = keepInBounds(filterDiv);
            xOffset = bounds.x;
            yOffset = bounds.y;

            // Apply final position
            filterDiv.style.transform = `translate(${xOffset}px, ${yOffset}px)`;

            // Save position
            saveSettings({
                positionX: xOffset,
                positionY: yOffset
            });
        }

        // Add event listeners for dragging
        headerDiv.addEventListener('mousedown', dragStart);
        document.addEventListener('mousemove', drag);
        document.addEventListener('mouseup', dragEnd);

        // Also end drag if mouse leaves the window
        document.addEventListener('mouseleave', dragEnd);

        // Handle window resize to keep the box in bounds
        window.addEventListener('resize', () => {
            const bounds = keepInBounds(filterDiv);
            xOffset = bounds.x;
            yOffset = bounds.y;
            filterDiv.style.transform = `translate(${xOffset}px, ${yOffset}px)`;
            saveSettings({
                positionX: xOffset,
                positionY: yOffset
            });
        });

        // Initial filter application
        updateFilters();

        // Set up observer for dynamic content
        const observer = new MutationObserver(() => {
            // Delay the update slightly to ensure DOM is fully updated
            setTimeout(updateFilters, 100);
        });

        observer.observe(crimesContainer, {
            childList: true,
            subtree: true
        });
    });
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址