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 提交的版本,檢視 最新版本

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

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

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

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

你需要先安裝一款使用者腳本管理器擴展,比如 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.");

})();