Neopets Training Helper Plus

Training Helper Plus + Global Notification System + Grabs Stones or Coins + Complete All courses

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

Advertisement:

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

Advertisement:

// ==UserScript==
// @name         Neopets Training Helper Plus
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Training Helper Plus + Global Notification System + Grabs Stones or Coins + Complete All courses
// @author       Darthmagic
// @match        https://www.neopets.com/*
// @grant        GM_addStyle
// @grant        unsafeWindow
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    const SCHOOLS = {
        island: { name: 'Mystery Island', processUrl: 'https://www.neopets.com/island/process_training.phtml', hpMult: 3 },
        academy: { name: 'Swashbuckling Academy', processUrl: 'https://www.neopets.com/pirates/process_academy.phtml', hpMult: 2 },
        ninja: { name: 'Secret Ninja', processUrl: 'https://www.neopets.com/island/process_fight_training.phtml', hpMult: 3 }
    };

    const NICE_SCHOOL = { ninja: 'Ninja Training', island: 'Beginner Training', academy: 'Deck Scrubber' };
    const BATTLE_STAT_CAP = 850;

    const LOCAL_ACTIVE = 'neoActiveTrainings';
    const LOCAL_COMPLETED = 'neoCompletedTrainings';
    const LOCAL_PENDING_WITHDRAW = 'neoPendingSDBWithdraw';
    const LOCAL_PAY_URL = 'neoAutoPayUrl';

    const loadActive = () => JSON.parse(localStorage.getItem(LOCAL_ACTIVE)) || {};
    const saveActive = d => localStorage.setItem(LOCAL_ACTIVE, JSON.stringify(d));
    const loadCompleted = () => { const arr = JSON.parse(localStorage.getItem(LOCAL_COMPLETED)) || []; return arr.filter(n => Date.now() - n.timestamp < 30*86400000); };
    const saveCompleted = arr => localStorage.setItem(LOCAL_COMPLETED, JSON.stringify(arr));

    function detectSchool() {
        const t = document.body.innerText;
        const u = location.href;
        if (t.includes('Secret Ninja') || u.includes('fight_training')) return 'ninja';
        if (t.includes('Swashbuckling Academy') || u.includes('academy')) return 'academy';
        return 'island';
    }

    function getSchoolInfo() { return SCHOOLS[detectSchool()]; }

    function getPetStats(container) {
        const text = (container.textContent || '').replace(/\s+/g, ' ');
        let level = 0;
        const levelMatch = text.match(/Lvl\s*:\s*(\d+)/i) || text.match(/Level\s*:\s*(\d+)/i) || text.match(/\(Level\s*(\d+)\)/i);
        if (levelMatch) level = parseInt(levelMatch[1]);

        const strMatch = text.match(/Str(?:ength)?\s*:\s*(\d+)/i);
        const str = strMatch ? parseInt(strMatch[1]) : 0;

        const defMatch = text.match(/Def(?:ence)?\s*:\s*(\d+)/i);
        const def = defMatch ? parseInt(defMatch[1]) : 0;

        let maxHp = 0;
        let hpMatch = text.match(/(?:Hp|HP|Health|Hit\s*Points?|Endurance)\s*:\s*(\d+)\s*\/\s*(\d+)/i);
        if (hpMatch) maxHp = parseInt(hpMatch[2]);
        else {
            hpMatch = text.match(/(?:Hp|HP|Health|Hit\s*Points?|Endurance)\s*:\s*(\d+)/i);
            if (hpMatch) maxHp = parseInt(hpMatch[1]);
        }
        return { level, str, def, maxHp };
    }

    function parseVisibleStatusPage() {
        const schoolKey = detectSchool();
        const schoolName = getSchoolInfo().name;
        let active = loadActive();

        document.querySelectorAll('td[bgcolor="#efefef"], td[bgcolor="#000000"]').forEach(header => {
            const txt = header.textContent || '';
            if (!txt.includes('is currently studying')) return;

            const petMatch = txt.match(/([A-Za-z][A-Za-z0-9_]+)\s*\(Level\s*\d+\)/);
            if (!petMatch) return;
            const pet = petMatch[1];
            const row = header.closest('tr');
            const statsTd = row?.nextElementSibling?.querySelector('td[bgcolor="white"]');
            if (!statsTd) return;

            const blockText = statsTd.textContent + ' ' + (row.nextElementSibling?.textContent || '');
            let endTime = null;
            if (blockText.includes('Course Finished!')) endTime = Date.now();
            else {
                const m = blockText.match(/(\d+)\s*hrs?,\s*(\d+)\s*minutes?,\s*(\d+)\s*seconds?/i);
                if (m) endTime = Date.now() + (parseInt(m[1])||0)*3600000 + (parseInt(m[2])||0)*60000 + (parseInt(m[3])||0)*1000;
            }
            if (!endTime) return;

            const skillMatch = txt.match(/studying\s+(.+?)(?:\s|$)/i);
            active[pet] = { school: schoolKey, skill: skillMatch ? skillMatch[1].trim() : 'Level', endTime, statusUrl: location.href, schoolName };
        });
        saveActive(active);
    }

    function addSmartQuickButtons() {
        const activePets = Object.keys(loadActive());
        const school = getSchoolInfo();

        document.querySelectorAll('td[bgcolor="white"]').forEach(cell => {
            cell.querySelectorAll('div[style*="margin-top:6px"]').forEach(el => el.remove());
            const text = cell.textContent || '';
            if (!text.match(/Lvl\s*:/i) && !text.match(/\(Level\s*\d+\)/i)) return;

            const stats = getPetStats(cell);
            let header = cell.closest('tr')?.previousElementSibling?.querySelector('td[bgcolor="#efefef"], td[bgcolor="#000000"]');
            let pet = null;
            if (header) {
                const headerTxt = header.textContent || '';
                const petMatch = headerTxt.match(/([A-Za-z][A-Za-z0-9_]+)\s*\(Level\s*\d+\)/);
                if (petMatch) pet = petMatch[1];
            }
            if (!pet) {
                const oldPetMatch = text.match(/([A-Za-z][A-Za-z0-9_]{2,})\s*(?:\(Level|:)/);
                if (oldPetMatch) pet = oldPetMatch[1];
            }
            if (!pet) return;

            if (activePets.includes(pet)) {
                const note = document.createElement('span');
                note.style = 'margin-left:8px;color:#e74c3c;font-weight:bold;';
                note.textContent = '[Training Elsewhere]';
                cell.appendChild(note);
                return;
            }

            const { level, str, def, maxHp } = stats;
            const hpCap = level * school.hpMult + (detectSchool() !== 'academy' ? 3 : 0);

            let recommended = null;
            if (level < 30) recommended = 'Level';
            else if (str < BATTLE_STAT_CAP) recommended = 'Strength';
            else if (def < BATTLE_STAT_CAP) recommended = 'Defence';
            else if (maxHp < hpCap) recommended = 'Endurance';
            else recommended = 'Level';

            const container = document.createElement('div');
            container.style.cssText = 'margin-top:6px;';

            const makeBtn = (course, letter) => {
                const isRec = course === recommended;
                const btn = document.createElement('a');
                btn.innerHTML = `[+ ${letter}]`;
                btn.style.cssText = `color:${isRec ? '#e74c3c' : '#27ae60'};font-weight:${isRec ? 'bold' : 'normal'};cursor:pointer;margin:0 4px;text-decoration:underline;`;
                btn.onclick = () => window.quickStart(pet, course);
                container.appendChild(btn);
            };

            makeBtn('Level', 'L');
            if (str < BATTLE_STAT_CAP) makeBtn('Strength', 'S');
            if (def < BATTLE_STAT_CAP) makeBtn('Defence', 'D');
            makeBtn('Endurance', 'E');

            if (str >= BATTLE_STAT_CAP && def >= BATTLE_STAT_CAP && maxHp >= hpCap) {
                const maxed = document.createElement('span');
                maxed.style = 'color:#e74c3c;font-size:12px;margin-left:6px;';
                maxed.textContent = '(maxed ✓)';
                container.appendChild(maxed);
            }
            cell.appendChild(container);
        });
    }

    function handleCompleteCourseButtons() {
        document.querySelectorAll('input[type="submit"][value="Complete Course!"]').forEach(button => {
            const form = button.closest('form');
            if (!form || button.dataset.enhanced) return;
            button.dataset.enhanced = 'true';

            button.addEventListener('click', async (e) => {
                e.preventDefault();
                await completeSingleCourse(form, button);
            });
        });
    }

    async function completeSingleCourse(form, button) {
        const originalText = button ? button.value : '';
        if (button) {
            button.value = 'Completing...';
            button.disabled = true;
        }

        try {
            const formData = new FormData(form);
            const response = await fetch(form.action, {
                method: 'POST',
                body: formData
            });

            const html = await response.text();
            const parser = new DOMParser();
            const doc = parser.parseFromString(html, 'text/html');

            let gainMessage = 'Course completed!';
            const paragraphs = doc.querySelectorAll('p');
            for (let p of paragraphs) {
                if (p.textContent.includes('now has increased') || p.textContent.includes('Congratulations')) {
                    gainMessage = p.textContent.trim();
                    break;
                }
            }

            const resultBox = document.createElement('div');
            resultBox.style.cssText = 'margin-top:8px;padding:10px 14px;background:#e8f5e9;border:1px solid #4caf50;border-radius:6px;color:#2e7d32;font-size:13px;';
            resultBox.innerHTML = `✅ <strong>${gainMessage}</strong>`;

            const container = form.closest('td') || form.parentElement;
            if (container) {
                form.style.display = 'none';
                container.appendChild(resultBox);
            }

        } catch (err) {
            console.error('[Training Helper] Failed to complete course:', err);
            if (button) {
                button.value = 'Error';
                button.disabled = false;
            }
        }
    }

    async function autoCompleteAllCourses() {
        const buttons = Array.from(document.querySelectorAll('input[type="submit"][value="Complete Course!"]'));
        if (buttons.length === 0) return;

        const petsBeingCompleted = [];
        buttons.forEach(btn => {
            const header = btn.closest('tr')?.previousElementSibling?.querySelector('td[bgcolor="#efefef"], td[bgcolor="#000000"]');
            if (header) {
                const match = header.textContent.match(/([A-Za-z][A-Za-z0-9_]+)\s*\(Level\s*\d+\)/);
                if (match) petsBeingCompleted.push(match[1]);
            }
        });

        const banner = document.createElement('div');
        banner.style.cssText = 'position:fixed;top:15%;left:50%;transform:translate(-50%,-50%);background:#1565c0;color:white;padding:14px 24px;border-radius:8px;z-index:999999;font-size:15px;';
        banner.innerHTML = `Completing ${buttons.length} course(s)...`;
        document.body.appendChild(banner);

        for (let i = 0; i < buttons.length; i++) {
            const button = buttons[i];
            const form = button.closest('form');
            if (!form) continue;

            banner.innerHTML = `Completing course ${i + 1} of ${buttons.length}...`;
            await completeSingleCourse(form, button);
            await new Promise(resolve => setTimeout(resolve, 850));
        }

        if (petsBeingCompleted.length > 0) {
            const active = loadActive();
            petsBeingCompleted.forEach(pet => delete active[pet]);
            saveActive(active);
        }

        banner.innerHTML = `✅ All courses completed!`;
        setTimeout(() => {
            banner.remove();
            if (panel && panel.style.display === 'block') {
                showNotificationPanel();
            }
        }, 1600);
    }

    function parseRequiredTrainingItems() {
        const items = [];
        document.querySelectorAll('td[width="250"]').forEach(td => {
            if (!td.textContent.includes('This course has not been paid for yet')) return;
            td.querySelectorAll('b').forEach(b => {
                const name = b.textContent.trim();
                if (!name || name.includes('click here') || name.includes('To cancel') || name.includes('Pay') || name.includes('Cancel')) return;
                if (name.includes('Codestone') || name.includes('Dubloon')) {
                    items.push({ name, qty: 1 });
                }
            });
        });

        const merged = {};
        items.forEach(item => {
            if (merged[item.name]) merged[item.name].qty += item.qty;
            else merged[item.name] = { ...item };
        });
        return Object.values(merged);
    }

    window.quickStart = function(pet, course) {
        const school = getSchoolInfo();
        const form = document.createElement('form');
        form.method = 'POST';
        form.action = school.processUrl;
        form.innerHTML = `
            <input type="hidden" name="type" value="start">
            <input type="hidden" name="course_type" value="${course}">
            <input type="hidden" name="pet_name" value="${pet}">
        `;
        document.body.appendChild(form);

        const banner = document.createElement('div');
        banner.style = 'position:fixed;top:20%;left:50%;transform:translate(-50%,-50%);background:#28a745;color:white;padding:20px 40px;border-radius:12px;box-shadow:0 10px 30px rgba(0,0,0,0.4);z-index:999999;font-size:18px;text-align:center;';
        banner.innerHTML = `✅ <b>Training started!</b><br>${pet} → ${course}`;
        document.body.appendChild(banner);

        setTimeout(() => form.submit(), 550);
    };

    function waitForStatsThenRun(fn) {
        let done = false;
        const observer = new MutationObserver(() => {
            if (!done && document.querySelector('td[bgcolor="white"]')) { done = true; observer.disconnect(); fn(); }
        });
        observer.observe(document.body, { childList: true, subtree: true });
        setTimeout(() => { if (!done) { observer.disconnect(); if (document.querySelector('td[bgcolor="white"]')) fn(); } }, 2200);
    }

    function handleStatusPage() {
        const ui = document.createElement('div');
        ui.style.cssText = 'margin:8px 0;padding:10px 20px;background:linear-gradient(#e6f0ff,#d0e0ff);border:2px solid #4a90e2;border-radius:8px;text-align:center;color:#2c5aa0;font-weight:bold;font-size:15px;';
        ui.innerHTML = `🧠 Training Helper Plus v1.0 • ${getSchoolInfo().name} • By Darthy`;
        (document.querySelector('.content, #content') || document.body).prepend(ui);

        waitForStatsThenRun(() => {
            parseVisibleStatusPage();
            addSmartQuickButtons();
            addItemGrabberButton();
            handleCompleteCourseButtons();

            const finishedCount = document.querySelectorAll('input[type="submit"][value="Complete Course!"]').length;
            if (finishedCount > 0) {
                const completeAllBtn = document.createElement('button');
                completeAllBtn.style.cssText = 'margin:10px auto;display:block;background:#1565c0;color:white;border:none;padding:10px 20px;border-radius:8px;font-weight:bold;cursor:pointer;';
                completeAllBtn.textContent = `Complete All Finished Courses (${finishedCount})`;

                completeAllBtn.onclick = async () => {
                    completeAllBtn.disabled = true;
                    completeAllBtn.textContent = 'Completing...';
                    await autoCompleteAllCourses();
                    completeAllBtn.remove();
                };

                ui.appendChild(completeAllBtn);
            }

            autoPayAfterWithdraw();
        });
    }

    function addItemGrabberButton() {
        const requiredItems = parseRequiredTrainingItems();
        if (requiredItems.length === 0) return;

        document.querySelectorAll('td[width="250"]').forEach(td => {
            if (!td.textContent.includes('This course has not been paid for yet')) return;
            if (td.querySelector('.item-grabber-btn')) return;

            const payLink = td.querySelector('a[href*="type=pay"]');
            const payUrl = payLink ? payLink.href : null;

            const btnContainer = document.createElement('div');
            btnContainer.style.cssText = 'margin: 8px 0; text-align:center;';

            const btn = document.createElement('button');
            btn.className = 'item-grabber-btn';
            btn.style.cssText = 'background:#222;color:white;border:none;padding:6px 14px;border-radius:6px;font-weight:bold;cursor:pointer;font-size:12px;';
            btn.textContent = 'Item Grabber';

            btn.onclick = () => {
                if (payUrl) localStorage.setItem(LOCAL_PAY_URL, payUrl);
                localStorage.setItem(LOCAL_PENDING_WITHDRAW, JSON.stringify(requiredItems));
                btn.textContent = 'Grabbing...';
                btn.disabled = true;
                setTimeout(() => window.location.href = '/safetydeposit.phtml', 300);
            };

            btnContainer.appendChild(btn);

            const firstP = td.querySelector('p');
            if (firstP) firstP.parentNode.insertBefore(btnContainer, firstP);
            else td.appendChild(btnContainer);
        });
    }

    function autoPayAfterWithdraw() {
        const payUrl = localStorage.getItem(LOCAL_PAY_URL);
        if (!payUrl) return;
        localStorage.removeItem(LOCAL_PAY_URL);

        const banner = document.createElement('div');
        banner.style = 'position:fixed;top:30%;left:50%;transform:translate(-50%,-50%);background:#27ae60;color:white;padding:16px 28px;border-radius:10px;z-index:999999;font-size:16px;text-align:center;';
        banner.innerHTML = `✅ Items moved!<br>Starting course...`;
        document.body.appendChild(banner);

        setTimeout(() => window.location.href = payUrl, 1400);
    }

    function handleAutoSDBWithdraw() {
        if (!location.pathname.includes('safetydeposit.phtml')) return;

        const pending = JSON.parse(localStorage.getItem(LOCAL_PENDING_WITHDRAW) || '[]');
        if (pending.length === 0) return;

        const statusDiv = document.createElement('div');
        statusDiv.style.cssText = 'position:fixed;top:20px;left:50%;transform:translateX(-50%);background:#2c5aa0;color:white;padding:12px 24px;border-radius:8px;z-index:999999;font-size:15px;';
        statusDiv.textContent = 'Withdrawing items from SDB...';
        document.body.appendChild(statusDiv);

        setTimeout(() => {
            const categorySelects = document.querySelectorAll('.sdb-select');
            if (categorySelects.length > 0) {
                const isDubloon = pending.some(i => i.name.toLowerCase().includes('dubloon'));
                categorySelects[0].value = isDubloon ? '3' : '2';
                categorySelects[0].dispatchEvent(new Event('change', { bubbles: true }));
            }

            setTimeout(() => {
                pending.forEach((req, index) => {
                    setTimeout(() => {
                        const rows = document.querySelectorAll('.sdb-table tbody tr');
                        for (let row of rows) {
                            const nameEl = row.querySelector('.sdb-item-name');
                            if (nameEl && nameEl.textContent.trim() === req.name) {
                                const input = row.querySelector('.np-stepper-input');
                                if (input) {
                                    input.value = Math.min(req.qty, parseInt(input.max) || req.qty);
                                    input.dispatchEvent(new Event('input', { bubbles: true }));
                                    input.dispatchEvent(new Event('change', { bubbles: true }));
                                }
                                const checkbox = row.querySelector('.sdb-item-checkbox');
                                if (checkbox && !checkbox.checked) {
                                    checkbox.checked = true;
                                    checkbox.dispatchEvent(new Event('change', { bubbles: true }));
                                }
                                break;
                            }
                        }
                    }, index * 300);
                });

                setTimeout(() => {
                    const actionSelect = document.querySelector('.sdb-drawer .sdb-action-select') || document.querySelector('.sdb-as-native');
                    if (actionSelect) {
                        actionSelect.value = 'inventory';
                        actionSelect.dispatchEvent(new Event('change', { bubbles: true }));
                    }

                    setTimeout(() => {
                        const firstConfirm = document.querySelector('.sdb-drawer-confirm-btn');
                        if (firstConfirm) firstConfirm.click();

                        setTimeout(() => {
                            const popupConfirm = document.querySelector('#sdb__popup .popup-footer__2020 button.np-button.button-green__2020');
                            if (popupConfirm) popupConfirm.click();

                            setTimeout(() => {
                                localStorage.removeItem(LOCAL_PENDING_WITHDRAW);
                                statusDiv.textContent = 'Done! Returning...';
                                setTimeout(() => {
                                    const returnUrl = document.referrer.includes('fight_training') ||
                                                      document.referrer.includes('training.phtml') ||
                                                      document.referrer.includes('academy.phtml')
                                        ? document.referrer
                                        : '/island/fight_training.phtml?type=status';
                                    window.location.href = returnUrl;
                                }, 1400);
                            }, 1200);
                        }, 900);
                    }, 700);
                }, 1600);
            }, 1400);
        }, 900);
    }

    // Notification system - MORE COMPACT
    let panel = null, bell = null;

    function createBellAndPanel() {
        if (document.getElementById('neo-bell') && panel) return;
        const top = (document.querySelector('#header, header, .header, table[bgcolor="#000080"]')?.getBoundingClientRect().bottom + window.scrollY + 8) || 110;

        if (!document.getElementById('neo-bell')) {
            bell = document.createElement('div');
            bell.id = 'neo-bell';
            bell.style.cssText = `position:fixed;top:${top}px;right:20px;width:54px;height:54px;background:#4a90e2;color:white;border-radius:50%;font-size:28px;display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:99999;box-shadow:0 4px 15px rgba(0,0,0,0.3);`;
            bell.textContent = '🛎️';
            document.body.appendChild(bell);
            bell.onclick = () => (panel && panel.style.display === 'block') ? panel.style.display = 'none' : showNotificationPanel();
        }

        if (!panel) {
            panel = document.createElement('div');
            // Changed width from 440px to 330px (25% narrower) + slightly less padding
            panel.style.cssText = `display:none;position:fixed;top:${top+70}px;right:20px;width:330px;max-height:75vh;overflow:auto;background:#fff;border:3px solid #4a90e2;border-radius:12px;box-shadow:0 10px 30px rgba(0,0,0,0.3);z-index:100000;padding:12px;font-family:Verdana,sans-serif;`;
            document.body.appendChild(panel);
        }
    }

    window.showNotificationPanel = function(autoOpen = false) {
        createBellAndPanel();
        if (!panel) return;

        const active = loadActive();
        const completed = loadCompleted();
        let html = `<h3 style="margin:0 0 10px;color:#4a90e2;font-size:15px;">📬 Training Notifications <button style="float:right;font-size:20px;border:none;background:none;cursor:pointer;" onclick="panel.style.display='none'">✕</button></h3>`;

        if (completed.length) {
            html += `<h4 style="color:#e74c3c;margin:6px 0 4px;font-size:13px;">✅ Completed</h4>`;
            completed.forEach(c => {
                html += `<div onclick="window.location='${c.statusUrl}'" style="cursor:pointer;padding:8px 10px;background:#fff8f0;border:1px solid #e74c3c;border-radius:6px;margin-bottom:6px;display:flex;gap:10px;font-size:13px;">
                    <img src="https://pets.neopets.com/cpn/${c.petName}/1/4.png" width="38" style="border-radius:5px;">
                    <div style="flex:1"><strong>${c.petName}</strong><br>${c.skill} • ${NICE_SCHOOL[c.school]||c.schoolName}</div>
                </div>`;
            });
        }

        if (Object.keys(active).length) {
            html += `<h4 style="color:#27ae60;margin:8px 0 4px;font-size:13px;">⏳ Currently Training</h4>`;
            Object.keys(active).forEach(p => {
                const t = active[p];
                const minLeft = Math.max(0, Math.floor((t.endTime - Date.now()) / 60000));
                html += `<div style="padding:8px 10px;background:#f0f8ff;border:1px solid #27ae60;border-radius:6px;margin-bottom:6px;font-size:13px;"><strong>${p}</strong> • ${t.skill} • ${NICE_SCHOOL[t.school]||t.schoolName}<br><span style="color:#27ae60;font-weight:bold;">${minLeft} min left</span></div>`;
            });
        } else if (!completed.length) {
            html += `<p style="text-align:center;color:#666;padding:12px 0;font-size:13px;">No pets currently training.</p>`;
        }

        html += `<button id="clear-completed-btn" style="margin-top:10px;background:#e74c3c;color:white;border:none;padding:7px 14px;border-radius:6px;font-size:13px;">🗑️ Clear All Completed</button>`;
        panel.innerHTML = html;
        panel.style.display = 'block';

        const clearBtn = panel.querySelector('#clear-completed-btn');
        if (clearBtn) {
            clearBtn.onclick = () => {
                localStorage.removeItem(LOCAL_COMPLETED);
                showNotificationPanel();
            };
        }
    };

    function checkExpiredTrainings() {
        const active = loadActive();
        let completed = loadCompleted();
        let changed = false;

        Object.keys(active).forEach(pet => {
            if (Date.now() >= active[pet].endTime) {
                completed.unshift({ petName: pet, skill: active[pet].skill || 'Course', school: active[pet].school, schoolName: active[pet].schoolName, statusUrl: active[pet].statusUrl, timestamp: Date.now() });
                delete active[pet];
                changed = true;
            }
        });

        if (changed) {
            saveActive(active);
            saveCompleted(completed);
            showNotificationPanel(true);
        }
    }

    function init() {
        setTimeout(() => {
            createBellAndPanel();
            if (loadCompleted().length > 0) showNotificationPanel(true);
            setInterval(checkExpiredTrainings, 25000);
            checkExpiredTrainings();

            handleAutoSDBWithdraw();

            if (location.search.includes('type=status') || document.body.innerText.includes('Course Status')) {
                handleStatusPage();
            } else if (!location.search && (location.pathname.includes('training.phtml') || location.pathname.includes('academy.phtml') || location.pathname.includes('fight_training.phtml'))) {
                location.replace(location.pathname + '?type=status');
            }

            console.log('%c✅ Training Helper Plus v1.0 by Darthy • More compact notification panel', 'color:#e74c3c;font-weight:bold');
        }, 800);
    }

    if (document.readyState === 'loading') window.addEventListener('load', init);
    else init();
})();