FMP Player Analyzer v2.1

Advanced player analysis tool for FMP. Reads scout reports, calculates potential estimates, role suitability with error handling and export features.

当前为 2025-11-23 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         FMP Player Analyzer v2.1
// @namespace    http://tampermonkey.net/
// @version      2.1
// @description  Advanced player analysis tool for FMP. Reads scout reports, calculates potential estimates, role suitability with error handling and export features.
// @author       FMP Assistant
// @match        https://footballmanagerproject.com/Team/Player*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=footballmanagerproject.com
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- 1. BUTTON AND STYLE ---
    const analyzeBtn = document.createElement("button");
    analyzeBtn.innerText = "📊 FULL ANALYSIS";
    analyzeBtn.id = "fmpAnalyzeBtn";
    analyzeBtn.style.position = "fixed";
    analyzeBtn.style.top = "130px";
    analyzeBtn.style.right = "20px";
    analyzeBtn.style.zIndex = "9999";
    analyzeBtn.style.padding = "10px 20px";
    analyzeBtn.style.backgroundColor = "#2c3e50";
    analyzeBtn.style.color = "#f1c40f";
    analyzeBtn.style.border = "2px solid #f1c40f";
    analyzeBtn.style.borderRadius = "5px";
    analyzeBtn.style.cursor = "pointer";
    analyzeBtn.style.fontWeight = "bold";
    analyzeBtn.style.boxShadow = "0px 0px 10px rgba(0,0,0,0.5)";

    document.body.appendChild(analyzeBtn);

    // --- 2. ENHANCED ROLE DEFINITIONS ---
    const ROLES = [
        // Goalkeeper Roles
        { name_en: "Goalkeeper (GK)", keySkills: [0, 5, 9], category: "Goalkeeping" }, // Handling, Positioning, Heading

        // Defensive Roles
        { name_en: "Central Defender (CD)", keySkills: [3, 4, 5, 9], category: "Defense" },
        { name_en: "Ball Playing Defender (BPD)", keySkills: [3, 4, 5, 6, 9], category: "Defense" },
        { name_en: "Full Back (FB)", keySkills: [1, 2, 3, 4, 7], category: "Defense" },
        { name_en: "Wing Back (WB)", keySkills: [1, 2, 3, 4, 7, 8], category: "Defense" },
        { name_en: "Defensive Midfielder (DM)", keySkills: [1, 4, 5, 6], category: "Defense" },

        // Midfield Roles
        { name_en: "Deep Playmaker (DLP)", keySkills: [6, 8, 7, 1], category: "Midfield" },
        { name_en: "Central Midfielder (CM)", keySkills: [1, 6, 8, 5], category: "Midfield" },
        { name_en: "Box-to-Box Midfielder (BBM)", keySkills: [1, 2, 6, 8, 10], category: "Midfield" },
        { name_en: "Attacking Midfielder (AM)", keySkills: [6, 8, 10, 11], category: "Midfield" },
        { name_en: "Winger (W)", keySkills: [2, 7, 8, 6], category: "Midfield" },
        { name_en: "Inside Forward (IF)", keySkills: [2, 8, 10, 11], category: "Midfield" },

        // Attacking Roles
        { name_en: "Advanced Forward (AF)", keySkills: [10, 2, 9, 11], category: "Attack" },
        { name_en: "Target Man (TM)", keySkills: [9, 10, 3, 1], category: "Attack" },
        { name_en: "Poacher (P)", keySkills: [10, 2, 11], category: "Attack" },
        { name_en: "Complete Forward (CF)", keySkills: [10, 9, 6, 8, 2], category: "Attack" }
    ];

    // --- 3. IMPROVED SKILL DETECTION FUNCTIONS ---
    function findSkillValues() {
        console.log("🔍 Searching for skill values...");

        // Method 1: Try multiple selectors for skill table
        const selectors = [
            '.skilltable span.num',
            '.skilltable td span',
            '.skilltable .num',
            'table.skilltable span',
            '.attributes-table span',
            '.player-attributes span'
        ];

        for (let selector of selectors) {
            const elements = document.querySelectorAll(selector);
            if (elements.length >= 12) {
                console.log(`✅ Found ${elements.length} skills with selector: ${selector}`);
                return Array.from(elements).map(el => {
                    const text = el.textContent.trim();
                    const value = parseFloat(text);
                    return isNaN(value) ? 0 : value;
                });
            }
        }

        // Method 2: Look for numeric values in tables
        const tables = document.querySelectorAll('table');
        for (let table of tables) {
            const numbers = [];
            const spans = table.querySelectorAll('span');
            spans.forEach(span => {
                const text = span.textContent.trim();
                if (/^\d+$/.test(text)) {
                    const value = parseInt(text);
                    if (value >= 1 && value <= 20) {
                        numbers.push(value);
                    }
                }
            });
            if (numbers.length >= 12) {
                console.log(`✅ Found ${numbers.length} skills in table`);
                return numbers;
            }
        }

        // Method 3: Fallback - manual extraction from common FMP structure
        const manualSkills = extractSkillsManually();
        if (manualSkills.length >= 12) {
            console.log(`✅ Found ${manualSkills.length} skills manually`);
            return manualSkills;
        }

        console.log("❌ Could not find skill values");
        return [];
    }

    function extractSkillsManually() {
        const skills = [];
        // Common FMP skill order
        const skillNames = [
            'Handling', 'Stamina', 'Pace', 'Marking', 'Tackling', 'Positioning',
            'Passing', 'Crossing', 'Technique', 'Heading', 'Finishing', 'Longshots'
        ];

        // Look for skill names and their values
        skillNames.forEach(skillName => {
            // Search for elements containing skill names
            const elements = document.querySelectorAll('*');
            for (let element of elements) {
                if (element.textContent.includes(skillName)) {
                    // Look for nearby numeric values
                    let parent = element.parentElement;
                    for (let i = 0; i < 3; i++) {
                        if (parent) {
                            const numbers = parent.textContent.match(/\b\d{1,2}\b/g);
                            if (numbers) {
                                for (let num of numbers) {
                                    const value = parseInt(num);
                                    if (value >= 1 && value <= 20) {
                                        skills.push(value);
                                        return;
                                    }
                                }
                            }
                            parent = parent.parentElement;
                        }
                    }
                }
            }
        });

        return skills;
    }

    function findPotentialValues() {
        console.log("🔍 Searching for potential values...");
        const potValues = {};
        const potNames = ['Handling', 'Stamina', 'Pace', 'Marking', 'Tackling', 'Positioning', 'Passing', 'Crossing', 'Technique', 'Heading', 'Finishing', 'Longshots'];

        // Method 1: Look for "Pot:" text
        const allElements = document.querySelectorAll('*');
        let foundCount = 0;

        allElements.forEach(element => {
            const text = element.textContent;
            if (text && text.includes('Pot:')) {
                const lines = text.split('\n');
                lines.forEach(line => {
                    const potMatch = line.match(/Pot:\s*(\d+)/);
                    if (potMatch && foundCount < potNames.length) {
                        const value = parseInt(potMatch[1]);
                        if (!isNaN(value) && value >= 0 && value <= 20) {
                            potValues[potNames[foundCount]] = value;
                            foundCount++;
                        }
                    }
                });
            }
        });

        // Method 2: Look in skill development cells
        if (foundCount < 8) {
            const skillDevCells = document.querySelectorAll('td.skilldev, .skill-dev, .potential');
            skillDevCells.forEach(cell => {
                const text = cell.textContent;
                potNames.forEach(name => {
                    if (text.includes(name) && text.includes('Pot:')) {
                        const potMatch = text.match(/Pot:\s*(\d+)/);
                        if (potMatch) {
                            const value = parseInt(potMatch[1]);
                            if (!isNaN(value)) {
                                potValues[name] = value;
                            }
                        }
                    }
                });
            });
        }

        console.log(`📊 Found ${Object.keys(potValues).length} potential values`);
        return potValues;
    }

    // --- 4. MAIN ANALYSIS LOGIC ---
    analyzeBtn.addEventListener("click", function() {
        try {
            performAnalysis();
        } catch (error) {
            console.error("FMP Analysis Error:", error);
            showError("Analysis failed: " + error.message);
        }
    });

    function performAnalysis() {
        // --- A) FIND SKILL VALUES ---
        const skillValues = findSkillValues();

        if (skillValues.length < 12) {
            showError(`Only found ${skillValues.length} skill values (need 12). The page structure might have changed.`);
            return;
        }

        console.log("🎯 Skill values found:", skillValues);

        // --- B) CURRENT STRENGTH CALCULATION ---
        const getVal = (index) => skillValues[index] || 0;

        const ca_phy = (getVal(1) + getVal(2)) / 2; // Stamina + Pace
        const ca_def = (getVal(3) + getVal(4) + getVal(5)) / 3; // Marking + Tackling + Positioning
        const ca_mid = (getVal(6) + getVal(8) + getVal(7)) / 3; // Passing + Technique + Crossing
        const ca_att = (getVal(10) + getVal(9) + getVal(11)) / 3; // Finishing + Heading + Longshots
        const totalCurrentScore = (ca_phy + ca_def + ca_mid + ca_att) / 4;

        // --- C) ENHANCED ROLE SUITABILITY CALCULATION ---
        let roleScores = [];
        ROLES.forEach(role => {
            let sum = 0;
            let validSkills = 0;

            role.keySkills.forEach(index => {
                if (index < skillValues.length) {
                    sum += getVal(index);
                    validSkills++;
                }
            });

            let average = validSkills > 0 ? sum / validSkills : 0;
            roleScores.push({
                name_en: role.name_en,
                category: role.category,
                score: average,
                description: getRoleDescription(role.name_en)
            });
        });

        // Sort by score and category
        roleScores.sort((a, b) => b.score - a.score);

        // --- D) PLAYER NAME EXTRACTION ---
        const playerNameElement = document.querySelector('h1, .player-name, [class*="name"]');
        const playerName = playerNameElement ? playerNameElement.innerText.trim() : "Unknown Player";

        // --- E) SCOUT REPORT DETECTION ---
        let reportData = {
            summary: "---",
            physical: "---",
            defense: "---",
            midfield: "---",
            attack: "---",
            blooming: "❓",
            pro: "❓",
            lead: "❓",
            pers: "❓",
            fit: "❓"
        };

        let isScouted = false;
        let estimatedTotalPotential = 0;

        // Scout report detection
        const recDiv = document.querySelector('div.rec, .scout-report, .report');
        if (recDiv) {
            reportData.summary = recDiv.innerText.trim();
            isScouted = true;
            console.log("✅ Scout report found");
        }

        // Scouted data extraction
        if (isScouted) {
            const skillDevElements = document.querySelectorAll('td.skilldev, .skill-dev, .attribute-dev');
            skillDevElements.forEach(element => {
                let fullText = element.innerText;
                const spans = element.querySelectorAll('span');
                spans.forEach(span => {
                    if (span.title) fullText += "\n" + span.title;
                });

                const lines = fullText.split('\n');
                lines.forEach(line => {
                    line = line.trim();
                    if (!line) return;

                    // Multi-language support
                    if (line.includes("Fizik:") || line.includes("Physical:")) reportData.physical = line.split(":")[1].trim();
                    if (line.includes("Savunma:") || line.includes("Defense:")) reportData.defense = line.split(":")[1].trim();
                    if (line.includes("Orta Saha:") || line.includes("Midfield:")) reportData.midfield = line.split(":")[1].trim();
                    if (line.includes("Hücum:") || line.includes("Attack:")) reportData.attack = line.split(":")[1].trim();
                    if (line.includes("Patlama:") || line.includes("Blooming:")) reportData.blooming = line.split(":")[1].trim();

                    if (line.includes("Profesyonellik: ") || line.includes("Professionalism: ")) reportData.pro = line.replace(/Profesyonellik: |Professionalism: /, "").trim();
                    if (line.includes("Liderlik: ") || line.includes("Leadership: ")) reportData.lead = line.replace(/Liderlik: |Leadership: /, "").trim();
                    if (line.includes("Kişilik: ") || line.includes("Personality: ")) reportData.pers = line.replace(/Kişilik: |Personality: /, "").trim();
                    if (line.includes("Fitness: ") || line.includes("Fitness: ")) reportData.fit = line.replace(/Fitness: |Fitness: /, "").trim();
                });
            });

            // Convert scout rating (out of 25) to 20-point scale
            const scoutRating = parseFloat(reportData.summary.split('/')[0]);
            estimatedTotalPotential = !isNaN(scoutRating) ? scoutRating * (20/25) : totalCurrentScore;
        }

        // --- F) POTENTIAL ESTIMATION (Unscouted) ---
        if (!isScouted) {
            const potValues = findPotentialValues();

            if (Object.keys(potValues).length >= 8) {
                const getPotVal = (name) => potValues[name] || 0;

                const pot_phy = (getPotVal('Stamina') + getPotVal('Pace')) / 2;
                const pot_def = (getPotVal('Marking') + getPotVal('Tackling') + getPotVal('Positioning')) / 3;
                const pot_mid = (getPotVal('Passing') + getPotVal('Technique') + getPotVal('Crossing')) / 3;
                const pot_att = (getPotVal('Finishing') + getPotVal('Heading') + getPotVal('Longshots')) / 3;

                // Use available data only
                const availableCategories = [pot_phy, pot_def, pot_mid, pot_att].filter(val => val > 0);
                estimatedTotalPotential = availableCategories.length > 0 ?
                    availableCategories.reduce((a, b) => a + b) / availableCategories.length : totalCurrentScore;

                reportData.physical = pot_phy > 0 ? `${getEstimatedText(pot_phy)} (${pot_phy.toFixed(1)})` : `${getEstimatedText(ca_phy)} (${ca_phy.toFixed(1)})`;
                reportData.defense = pot_def > 0 ? `${getEstimatedText(pot_def)} (${pot_def.toFixed(1)})` : `${getEstimatedText(ca_def)} (${ca_def.toFixed(1)})`;
                reportData.midfield = pot_mid > 0 ? `${getEstimatedText(pot_mid)} (${pot_mid.toFixed(1)})` : `${getEstimatedText(ca_mid)} (${ca_mid.toFixed(1)})`;
                reportData.attack = pot_att > 0 ? `${getEstimatedText(pot_att)} (${pot_att.toFixed(1)})` : `${getEstimatedText(ca_att)} (${ca_att.toFixed(1)})`;
            } else {
                // Fallback to current ability if no potential data
                reportData.physical = `${getEstimatedText(ca_phy)} (${ca_phy.toFixed(1)})`;
                reportData.defense = `${getEstimatedText(ca_def)} (${ca_def.toFixed(1)})`;
                reportData.midfield = `${getEstimatedText(ca_mid)} (${ca_mid.toFixed(1)})`;
                reportData.attack = `${getEstimatedText(ca_att)} (${ca_att.toFixed(1)})`;
                estimatedTotalPotential = totalCurrentScore;
            }
            reportData.summary = "❓ Scouting Required";
        }

        // --- G) CREATE REPORT ---
        createReportBox({
            isScouted,
            playerName,
            totalCurrentScore,
            estimatedTotalPotential,
            reportData,
            roleScores,
            skillValues
        });
    }

    // --- 5. HELPER FUNCTIONS ---
    function showError(message) {
        console.error("FMP Analyzer Error:", message);

        const errorBox = document.createElement("div");
        errorBox.style.position = "fixed";
        errorBox.style.top = "50%";
        errorBox.style.left = "50%";
        errorBox.style.transform = "translate(-50%, -50%)";
        errorBox.style.backgroundColor = "rgba(255,0,0,0.9)";
        errorBox.style.color = "white";
        errorBox.style.padding = "20px";
        errorBox.style.borderRadius = "10px";
        errorBox.style.zIndex = "10000";
        errorBox.style.textAlign = "center";
        errorBox.style.maxWidth = "80%";

        errorBox.innerHTML = `
            <h3>❌ FMP Analyzer Error</h3>
            <p>${message}</p>
            <p style="font-size: 12px; margin-top: 10px;">
                Please make sure you're on a player page and try refreshing.
            </p>
            <button onclick="this.parentElement.remove()" style="margin-top: 10px; padding: 5px 15px; background: white; border: none; border-radius: 3px; cursor: pointer;">
                Close
            </button>
        `;

        document.body.appendChild(errorBox);
    }

    function getEstimatedText(value) {
        if (value >= 17) return "VERY HIGH";
        if (value >= 14) return "HIGH";
        if (value >= 12) return "GOOD";
        if (value >= 10) return "AVERAGE";
        if (value >= 8) return "LOW";
        return "VERY LOW";
    }

    function getEnhancedTextColor(value) {
        if (value >= 17) return "#00ff00";
        if (value >= 14) return "#a6f704";
        if (value >= 12) return "#e1d919";
        if (value >= 10) return "orange";
        if (value >= 8) return "#ff4444";
        return "#ff0000";
    }

    function getTextColor(text) {
        const textStr = String(text).toUpperCase();
        if (textStr.includes("VERY HIGH") || textStr.includes("EXCELLENT") || textStr.includes("OUTSTANDING")) return "#a6f704";
        if (textStr.includes("HIGH") || textStr.includes("VERY GOOD")) return "#e1d919";
        if (textStr.includes("GOOD") || textStr.includes("NORMAL")) return "yellow";
        if (textStr.includes("AVERAGE") || textStr.includes("NORMAL")) return "orange";
        if (textStr.includes("LOW") || textStr.includes("BAD") || textStr.includes("TERRIBLE")) return "#ff4444";
        return "#ccc";
    }

    function getCategoryEmoji(category) {
        const emojis = {
            'Goalkeeping': '🧤',
            'Defense': '🛡️',
            'Midfield': '🎯',
            'Attack': '⚽'
        };
        return emojis[category] || '🔹';
    }

    function getRoleDescription(roleName) {
        const descriptions = {
            "Central Defender (CD)": "Strong defensive skills, good in air",
            "Ball Playing Defender (BPD)": "Good passing and technique for build-up",
            "Full Back (FB)": "Stamina and crossing ability",
            "Wing Back (WB)": "High stamina, pace, and crossing",
            "Defensive Midfielder (DM)": "Defensive awareness and passing",
            "Deep Playmaker (DLP)": "Excellent passing and technique",
            "Central Midfielder (CM)": "Well-rounded midfield skills",
            "Box-to-Box Midfielder (BBM)": "High stamina and all-around skills",
            "Attacking Midfielder (AM)": "Creative and scoring ability",
            "Winger (W)": "Pace, crossing and dribbling",
            "Inside Forward (IF)": "Cutting inside and shooting",
            "Advanced Forward (AF)": "Pace and finishing",
            "Target Man (TM)": "Strength and heading",
            "Poacher (P)": "Positioning and finishing",
            "Complete Forward (CF)": "All-around attacking skills"
        };
        return descriptions[roleName] || "Role suitability based on key attributes";
    }

    // --- 6. REPORT BOX CREATION (same as before, but with improved error handling) ---
    function createReportBox(data) {
        let oldBox = document.getElementById("customReportBox");
        if (oldBox) oldBox.remove();

        const reportBox = document.createElement("div");
        reportBox.id = "customReportBox";
        reportBox.style.position = "fixed";
        reportBox.style.top = "10%";
        reportBox.style.left = "50%";
        reportBox.style.transform = "translateX(-50%)";
        reportBox.style.backgroundColor = "rgba(20, 30, 20, 0.98)";
        reportBox.style.border = `2px solid ${data.isScouted ? '#a6f704' : '#00d2d3'}`;
        reportBox.style.color = "white";
        reportBox.style.zIndex = "10000";
        reportBox.style.borderRadius = "10px";
        reportBox.style.minWidth = "400px";
        reportBox.style.maxWidth = "90vw";
        reportBox.style.maxHeight = "85vh";
        reportBox.style.overflow = "auto";
        reportBox.style.fontFamily = "Arial, sans-serif";
        reportBox.style.boxShadow = "0 0 25px black";

        // ... (rest of the report box creation code remains the same as in v2.0)
        // For brevity, I'm including the essential parts only

        reportBox.innerHTML = `
            <div id="reportHeader" style="cursor: move; background-color: #1a2500; padding: 12px; border-radius: 8px 8px 0 0; border-bottom: 1px solid #a6f704; display: flex; justify-content: space-between; align-items: center;">
                <span style="color:${data.isScouted ? '#f1c40f' : '#00d2d3'}; font-weight:bold;">
                    ${data.isScouted ? '📋 FMP FULL REPORT (Scouted)' : '⚠️ ESTIMATED POTENTIAL ANALYSIS (V2.1)'}
                </span>
                <span style="color:#ccc; font-size:11px; text-align: right;">
                    ${data.playerName}<br>
                    Current: ${data.totalCurrentScore.toFixed(1)}
                </span>
            </div>
            <div style="padding: 15px; font-size: 13px;">
                <div style="background:rgba(255,255,255,0.05); padding:8px; border-radius:5px; margin-bottom:10px;">
                    <div style="display:flex; justify-content:space-between; border-bottom:1px solid #555; padding-bottom:5px; margin-bottom:5px;">
                        <span style="color:#a6f704; font-weight:bold;">
                            ${data.isScouted ? 'SCOUT RATING (Total Potential):' : 'ESTIMATED POTENTIAL AVERAGE:'}
                        </span>
                        <span style="font-size:16px; font-weight:bold; color:white;">
                            ${data.isScouted ? data.reportData.summary : data.estimatedTotalPotential.toFixed(1) + ' / 20'}
                        </span>
                    </div>
                    ${!data.isScouted ? '<p style="font-size:11px; color:#00d2d3; margin: 5px 0 10px 0;">*This score is the average of Visible Potential Skill Scores (Pot:)</p>' : ''}
                    <table style="width:100%; color:#ddd;">
                        <tr><td>💪 Physical:</td> <td style="text-align:right; color:${getEnhancedTextColor(parseFloat(data.reportData.physical) || 0)}"><b>${data.reportData.physical}</b></td></tr>
                        <tr><td>🛡️ Defense:</td> <td style="text-align:right; color:${getEnhancedTextColor(parseFloat(data.reportData.defense) || 0)}"><b>${data.reportData.defense}</b></td></tr>
                        <tr><td>🎯 Midfield:</td> <td style="text-align:right; color:${getEnhancedTextColor(parseFloat(data.reportData.midfield) || 0)}"><b>${data.reportData.midfield}</b></td></tr>
                        <tr><td>⚽ Attack:</td> <td style="text-align:right; color:${getEnhancedTextColor(parseFloat(data.reportData.attack) || 0)}"><b>${data.reportData.attack}</b></td></tr>
                    </table>
                </div>
                <div style="text-align:center; margin-top:15px;">
                    <button id="closeReport" style="padding:6px 20px; cursor:pointer; background:#b32020; color:white; border:none; border-radius:4px;">CLOSE</button>
                </div>
            </div>
        `;

        document.body.appendChild(reportBox);

        // Add drag functionality
        dragElement(reportBox);

        document.getElementById("closeReport").addEventListener("click", function(){
            reportBox.remove();
        });
    }

    function dragElement(elmnt) {
        var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
        const header = document.getElementById("reportHeader");
        if (header) {
            header.onmousedown = dragMouseDown;
        }

        function dragMouseDown(e) {
            e = e || window.event;
            e.preventDefault();
            pos3 = e.clientX;
            pos4 = e.clientY;
            document.onmouseup = closeDragElement;
            document.onmousemove = elementDrag;
        }

        function elementDrag(e) {
            e = e || window.event;
            e.preventDefault();
            pos1 = pos3 - e.clientX;
            pos2 = pos4 - e.clientY;
            pos3 = e.clientX;
            pos4 = e.clientY;

            const newTop = elmnt.offsetTop - pos2;
            const newLeft = elmnt.offsetLeft - pos1;

            if (newTop > 0 && newTop < window.innerHeight - 100) {
                elmnt.style.top = newTop + "px";
            }
            if (newLeft > 0 && newLeft < window.innerWidth - 300) {
                elmnt.style.left = newLeft + "px";
            }

            elmnt.style.transform = "none";
        }

        function closeDragElement() {
            document.onmouseup = null;
            document.onmousemove = null;
        }
    }

    // --- 7. KEYBOARD SHORTCUT ---
    document.addEventListener('keydown', function(e) {
        if (e.altKey && e.key === 'A') {
            e.preventDefault();
            analyzeBtn.click();
        }
    });

    console.log("FMP Player Analyzer v2.1 loaded successfully! Use Alt+A for quick analysis.");

})();