MediaWiki:Common.js: Difference between revisions

From EQOA Sandstorm Wiki
Jump to navigationJump to search
No edit summary
No edit summary
 
(162 intermediate revisions by the same user not shown)
Line 1: Line 1:
mw.loader.load(
    mw.config.get('wgScript') +
    '?title=MediaWiki:AchievementTracker.js' +
    '&action=raw' +
    '&ctype=text/javascript' +
    '&v=20260716-1'
);
/* Any JavaScript here will be loaded for all users on every page load. */
/* Any JavaScript here will be loaded for all users on every page load. */


Line 219: Line 227:




    function initItemFilters() {
function initItemFilters() {
        var table = document.getElementById('items-found-here-table');
    var table = document.getElementById('items-found-here-table');
        var box;
    var box;
        var filters;
    var searchInput;
        var clearButton;
    var classSelect;
    var sortSelect;
    var sortButton;
    var clearButton;


        if (!table) return;
    if (!table) return;
        if (document.getElementById('items-filter-box')) return;
    if (document.getElementById('items-filter-box')) return;
 
    box = document.createElement('div');
    box.id = 'items-filter-box';
    box.className = 'items-filter-box class-item-filter-panel';
 
    box.innerHTML =
        '<div class="items-filter-left">' +
            '<input type="search" id="filter-item-search" placeholder="Search item">' +
        '</div>' +
        '<div class="items-filter-right">' +
            '<select id="items-found-here-sort-select">' +
                '<option value="name">Sort: Name</option>' +
                '<option value="level">Sort: Level</option>' +
                '<option value="class">Sort: Class</option>' +
                '<option value="dmg">Sort: DMG</option>' +
                '<option value="ac">Sort: AC</option>' +
                '<option value="str">Sort: STR</option>' +
                '<option value="sta">Sort: STA</option>' +
                '<option value="agi">Sort: AGI</option>' +
                '<option value="wis">Sort: WIS</option>' +
                '<option value="dex">Sort: DEX</option>' +
                '<option value="cha">Sort: CHA</option>' +
                '<option value="int">Sort: INT</option>' +
                '<option value="hp">Sort: HP</option>' +
                '<option value="pow">Sort: POW</option>' +
                '<option value="pr">Sort: PR</option>' +
                '<option value="dr">Sort: DR</option>' +
                '<option value="fr">Sort: FR</option>' +
                '<option value="cr">Sort: CR</option>' +
                '<option value="lr">Sort: LR</option>' +
                '<option value="ar">Sort: AR</option>' +
                '<option value="dur">Sort: DUR</option>' +
            '</select>' +
            '<button id="items-found-here-sort-button" type="button">Sort</button>' +
            '<select id="filter-class-select">' +
                '<option value="">All Classes</option>' +
                '<option value="WAR">WAR</option>' +
                '<option value="RAN">RAN</option>' +
                '<option value="PAL">PAL</option>' +
                '<option value="SK">SK</option>' +
                '<option value="MNK">MNK</option>' +
                '<option value="BRD">BRD</option>' +
                '<option value="RGE">RGE</option>' +
                '<option value="DRD">DRD</option>' +
                '<option value="SHA">SHA</option>' +
                '<option value="CL">CL</option>' +
                '<option value="MAG">MAG</option>' +
                '<option value="NEC">NEC</option>' +
                '<option value="ENC">ENC</option>' +
                '<option value="WIZ">WIZ</option>' +
                '<option value="ALC">ALC</option>' +
            '</select>' +
            '<button id="clear-item-filters" type="button">Clear</button>' +
        '</div>';


        box = document.createElement('div');
    table.parentNode.insertBefore(box, table);
        box.id = 'items-filter-box';
        box.className = 'items-filter-box';


        box.innerHTML =
    searchInput = document.getElementById('filter-item-search');
            '<b>Item Filters</b><br>' +
    classSelect = document.getElementById('filter-class-select');
            'Item: <input type="text" id="filter-item"> ' +
    sortSelect = document.getElementById('items-found-here-sort-select');
            'Dropped By: <input type="text" id="filter-mob"> ' +
    sortButton = document.getElementById('items-found-here-sort-button');
            'Class: <input type="text" id="filter-class"> ' +
    clearButton = document.getElementById('clear-item-filters');
            'Lvl >= <input type="number" id="filter-lvl-min"> ' +
            'DMG >= <input type="number" id="filter-dmg-min"> ' +
            'AC >= <input type="number" id="filter-ac-min"> ' +
            'HP >= <input type="number" id="filter-hp-min"> ' +
            'POW >= <input type="number" id="filter-pow-min"> ' +
            '<button id="clear-item-filters" type="button">Clear</button>';


        table.parentNode.insertBefore(box, table);
    box.style.setProperty('display', 'grid', 'important');
    box.style.setProperty('grid-template-columns', '1fr auto', 'important');
    box.style.setProperty('align-items', 'center', 'important');
    box.style.setProperty('gap', '12px', 'important');


        filters = {
    box.querySelector('.items-filter-left').style.setProperty('display', 'flex', 'important');
            item: document.getElementById('filter-item'),
    box.querySelector('.items-filter-left').style.setProperty('justify-content', 'flex-start', 'important');
            mob: document.getElementById('filter-mob'),
            className: document.getElementById('filter-class'),
            lvlMin: document.getElementById('filter-lvl-min'),
            dmgMin: document.getElementById('filter-dmg-min'),
            acMin: document.getElementById('filter-ac-min'),
            hpMin: document.getElementById('filter-hp-min'),
            powMin: document.getElementById('filter-pow-min')
        };


        clearButton = document.getElementById('clear-item-filters');
    box.querySelector('.items-filter-right').style.setProperty('display', 'flex', 'important');
    box.querySelector('.items-filter-right').style.setProperty('justify-content', 'flex-end', 'important');
    box.querySelector('.items-filter-right').style.setProperty('gap', '8px', 'important');


        function getCellText(row, index) {
    [searchInput, classSelect, sortSelect, sortButton, clearButton].forEach(function (control) {
            return (row.cells[index] ? row.cells[index].textContent : '').toLowerCase();
        if (!control) return;
        }


         function getCellNumber(row, index) {
         control.style.setProperty('height', '34px', 'important');
            var text = row.cells[index] ? row.cells[index].textContent.trim() : '';
        control.style.setProperty('box-sizing', 'border-box', 'important');
            var num = parseFloat(text.replace(/[^\d.-]/g, ''));
        control.style.setProperty('margin', '0', 'important');
            return isNaN(num) ? 0 : num;
        control.style.setProperty('vertical-align', 'middle', 'important');
        }
    });


        function getInputNumber(input) {
    searchInput.style.setProperty('width', '660px', 'important');
            var num;
    searchInput.style.setProperty('max-width', '100%', 'important');


            if (!input || input.value === '') return null;
    sortSelect.style.setProperty('width', '128px', 'important');
    classSelect.style.setProperty('width', '128px', 'important');


            num = parseFloat(input.value);
    sortButton.style.setProperty('width', '58px', 'important');
            return isNaN(num) ? null : num;
    clearButton.style.setProperty('width', '64px', 'important');
        }


        function filterTable() {
    function getCellText(row, index) {
            var itemText = filters.item.value.toLowerCase();
        return row.cells[index] ? row.cells[index].textContent.trim() : '';
            var mobText = filters.mob.value.toLowerCase();
    }
            var classText = filters.className.value.toLowerCase();


            var lvlMin = getInputNumber(filters.lvlMin);
    function getRowClassText(row) {
            var dmgMin = getInputNumber(filters.dmgMin);
        return (
             var acMin = getInputNumber(filters.acMin);
             row.getAttribute('data-class') ||
             var hpMin = getInputNumber(filters.hpMin);
             getCellText(row, 2) ||
             var powMin = getInputNumber(filters.powMin);
             ''
        ).toUpperCase();
    }


            var i;
    function classMatches(row, selectedClass) {
            var row;
        var classText = getRowClassText(row);
            var show;


            for (i = 1; i < table.rows.length; i++) {
        if (!selectedClass) return true;
                row = table.rows[i];
        if (classText === 'ALL') return true;
                show = true;
        if (classText.indexOf('ALL') !== -1) return true;


                if (itemText && getCellText(row, 0).indexOf(itemText) === -1) show = false;
        return classText.split(/[\s,\/]+/).indexOf(selectedClass) !== -1;
                if (mobText && getCellText(row, 1).indexOf(mobText) === -1) show = false;
    }
                if (classText && getCellText(row, 2).indexOf(classText) === -1) show = false;


                if (lvlMin !== null && getCellNumber(row, 3) < lvlMin) show = false;
    function filterTable() {
                if (dmgMin !== null && getCellNumber(row, 4) < dmgMin) show = false;
        var query = String(searchInput.value || '').toLowerCase();
                if (acMin !== null && getCellNumber(row, 5) < acMin) show = false;
        var selectedClass = classSelect.value;
                if (hpMin !== null && getCellNumber(row, 13) < hpMin) show = false;
        var rows = table.querySelectorAll('tr.items-found-here-row');
                if (powMin !== null && getCellNumber(row, 15) < powMin) show = false;


                 row.style.display = show ? '' : 'none';
        Array.prototype.forEach.call(rows, function (row) {
             }
            var rowText = [
        }
                row.textContent,
                 row.getAttribute('data-name'),
                row.getAttribute('data-level'),
                row.getAttribute('data-class'),
                row.getAttribute('data-slot'),
                row.getAttribute('data-rarity')
             ].join(' ').toLowerCase();


        Object.keys(filters).forEach(function (key) {
             var show = true;
             filters[key].addEventListener('input', filterTable);
        });


        clearButton.addEventListener('click', function () {
            if (query && rowText.indexOf(query) === -1) show = false;
            Object.keys(filters).forEach(function (key) {
             if (!classMatches(row, selectedClass)) show = false;
                filters[key].value = '';
             });


             filterTable();
             row.style.display = show ? '' : 'none';
         });
         });
     }
     }


     function initRelatedItemTooltips($content) {
     function getSortValue(row, key) {
         var root = $content && $content[0] ? $content[0] : document;
         var value = row.getAttribute('data-' + key) || '';
        var relatedLinks = root.querySelectorAll(
    '.eqoa-related-items a, ' +
    '.zone-monster-table td:nth-child(3) a, ' +
    '.zone-monster-drops a'
);
        var tooltip = document.getElementById('eqoa-related-item-tooltip');
        var cache = {};


         if (!relatedLinks.length) return;
         if (!value) {
 
            if (key === 'name') value = getCellText(row, 0);
        if (!tooltip) {
             if (key === 'level') value = getCellText(row, 1);
            tooltip = document.createElement('div');
             if (key === 'class') value = getCellText(row, 2);
             tooltip.id = 'eqoa-related-item-tooltip';
             tooltip.className = 'eqoa-related-item-tooltip';
            tooltip.style.display = 'none';
            document.body.appendChild(tooltip);
         }
         }


         function normalizeRarity(rarity) {
         return String(value || '');
            return String(rarity || 'common')
    }
                .toLowerCase()
                .replace(/\s+/g, '-');
        }


        function getPageTitleFromLink(link) {
    function sortTable() {
            var href = link.getAttribute('href') || '';
        var tbody = table.querySelector('tbody') || table;
            var articlePath = mw.config.get('wgArticlePath') || '';
        var rows = Array.prototype.slice.call(table.querySelectorAll('tr.items-found-here-row'));
            var title;
        var key = sortSelect.value;


            if (href.indexOf('/index.php/') !== -1) {
        rows.sort(function (a, b) {
                title = href.split('/index.php/')[1];
            var aValue = getSortValue(a, key);
                return decodeURIComponent(title).replace(/_/g, ' ');
            var bValue = getSortValue(b, key);
            }
            var aNumber = parseFloat(aValue.replace(/[^\d.-]/g, ''));
            var bNumber = parseFloat(bValue.replace(/[^\d.-]/g, ''));


             if (articlePath && articlePath.indexOf('$1') !== -1) {
             if (!isNaN(aNumber) || !isNaN(bNumber)) {
                title = href.substring(href.lastIndexOf('/') + 1);
                 return (bNumber || 0) - (aNumber || 0);
                 return decodeURIComponent(title).replace(/_/g, ' ');
             }
             }


             return link.textContent.trim();
             return aValue.localeCompare(bValue);
         }
         });


         function parseItemTemplate(wikitext, fallbackName) {
         rows.forEach(function (row) {
             var match = wikitext.match(/\{\{(?:ItemBox|ItemPage)([\s\S]*?)\}\}/i);
             tbody.appendChild(row);
            var item;
        });
            var lines;


            if (!match) return null;
        filterTable();
    }


            item = {
    searchInput.addEventListener('input', filterTable);
                name: fallbackName
    classSelect.addEventListener('change', filterTable);
            };
    sortButton.addEventListener('click', sortTable);


            lines = match[1].split('\n');
    clearButton.addEventListener('click', function () {
        searchInput.value = '';
        classSelect.value = '';
        sortSelect.value = 'name';
        filterTable();
    });
}


            lines.forEach(function (line) {
                var eq;
                var key;
                var value;


                line = line.trim();
                if (line.indexOf('|') !== 0) return;


                eq = line.indexOf('=');
function getItemNameFromLink(link) {
                if (eq === -1) return;
    var href = link.getAttribute('href') || '';
    var title = '';


                key = line.substring(1, eq).trim().toLowerCase();
    if (href.indexOf('title=') !== -1) {
                value = line.substring(eq + 1).trim();
        title = href.split('title=')[1].split('&')[0];
        return decodeURIComponent(title).replace(/_/g, ' ').trim();
    }


                if (key) {
    if (href.indexOf('/index.php/') !== -1) {
                    item[key] = value;
        title = href.split('/index.php/')[1].split('?')[0];
                }
        return decodeURIComponent(title).replace(/_/g, ' ').trim();
            });
    }
 
    return link.textContent.trim();
}


            if (!item.name) item.name = fallbackName;


            return item;
        }


        function statLine(item, label, key) {
            if (!item[key] || item[key] === '0') return '';
            return '<div><span>' + label + '</span> ' + item[key] + '</div>';
        }


        function buildTooltip(item) {
            var rarity = normalizeRarity(item.rarity);
            var rarityClass = 'cb-rarity-' + rarity;
            var html = '';


            html += '<div class="eqoa-related-tooltip-title ' + rarityClass + '">' +
                item.name +
                '</div>';


            if (item.slot) html += '<div><b>Equip:</b> ' + item.slot + '</div>';
            if (item.class) html += '<div><b>Class:</b> ' + item.class + '</div>';
            if (item.lvl) html += '<div><b>Level:</b> ' + item.lvl + '</div>';
            if (item.dur) html += '<div><b>DUR:</b> ' + item.dur + '</div>';


            html += '<div class="eqoa-related-tooltip-stats">';
            html += statLine(item, 'DMG:', 'dmg');
            html += statLine(item, 'AC:', 'ac');
            html += statLine(item, 'STR:', 'str');
            html += statLine(item, 'STA:', 'sta');
            html += statLine(item, 'AGI:', 'agi');
            html += statLine(item, 'DEX:', 'dex');
            html += statLine(item, 'WIS:', 'wis');
            html += statLine(item, 'INT:', 'int');
            html += statLine(item, 'CHA:', 'cha');
            html += statLine(item, 'HP:', 'hp');
            html += statLine(item, 'POW:', 'pow');
            html += statLine(item, 'PR:', 'pr');
            html += statLine(item, 'DR:', 'dr');
            html += statLine(item, 'FR:', 'fr');
            html += statLine(item, 'CR:', 'cr');
            html += statLine(item, 'LR:', 'lr');
            html += statLine(item, 'AR:', 'ar');
            html += '</div>';


            if (item.effect) html += '<div><b>Effect:</b> ' + item.effect + '</div>';
            if (item.proc) html += '<div><b>Proc:</b> ' + item.proc + '</div>';


            return html;
function initEqoaGeneratedItemLinks($content) {
         }
    var root = $content && $content[0] ? $content[0] : document;
    var links = root.querySelectorAll(
        '#items-found-here-table .items-found-here-name a:not([data-npc-item-tooltip-bound="1"]):not([data-quest-reward-tooltip-bound="1"]), ' +
        '#items-found-here-table td:first-child a:not([data-npc-item-tooltip-bound="1"]):not([data-quest-reward-tooltip-bound="1"]), ' +
        '.zone-monster-table .zone-monster-drops a:not([data-npc-item-tooltip-bound="1"]):not([data-quest-reward-tooltip-bound="1"]), ' +
        '.zone-monster-table td:nth-child(3) a:not([data-npc-item-tooltip-bound="1"]):not([data-quest-reward-tooltip-bound="1"]), ' +
         '.eqoa-related-items a:not([data-npc-item-tooltip-bound="1"]):not([data-quest-reward-tooltip-bound="1"])'
    );
    var items = window.EQOA_GENERATED_ITEMS || [];
    var lookup = {};


        function moveTooltip(event) {
    if (!links.length) return;
            var left = event.pageX + 16;
            var top = event.pageY + 16;


            if (left + 360 > window.scrollX + window.innerWidth) {
    if (!items.length) {
                left = event.pageX - 360;
        setTimeout(function () {
            }
            initEqoaGeneratedItemLinks($content);
        }, 250);
        return;
    }


             tooltip.style.left = left + 'px';
    function normalizeName(value) {
             tooltip.style.top = top + 'px';
        return String(value || '')
        }
             .replace(/\u00a0/g, ' ')
             .replace(/[’`]/g, "'")
            .replace(/\s+/g, ' ')
            .trim()
            .toLowerCase();
    }


        function showTooltip(link, event) {
    function looseName(value) {
            var title = getPageTitleFromLink(link);
        return normalizeName(value).replace(/[^a-z0-9]/g, '');
    }


             tooltip.className = 'eqoa-related-item-tooltip';
    function stripExtraText(value) {
             tooltip.innerHTML = 'Loading...';
        return String(value || '')
             tooltip.style.display = 'block';
             .replace(/\(\d+%\)/g, '')
             moveTooltip(event);
             .replace(/\([^)]*\)$/g, '')
             .replace(/\s+/g, ' ')
             .trim();
    }


            if (cache[title]) {
    function rarityRank(item) {
                tooltip.className =
        var rarity = normalizeClassItemRarity(item && item.rarity);
                    'eqoa-related-item-tooltip tooltip-rarity-' +
                    normalizeRarity(cache[title].rarity);


                tooltip.innerHTML = buildTooltip(cache[title]);
        if (rarity === 'ultra-rare') return 4;
                return;
        if (rarity === 'rare') return 3;
            }
        if (rarity === 'uncommon') return 2;


            new mw.Api().get({
        return 1;
                action: 'query',
    }
                prop: 'revisions',
                titles: title,
                rvprop: 'content',
                rvslots: 'main',
                formatversion: '2',
                format: 'json'
            }).then(function (data) {
                var page = data.query.pages[0];
                var text = '';
                var item;


                if (page && page.revisions && page.revisions[0]) {
    function storeItem(key, item) {
                    if (page.revisions[0].slots && page.revisions[0].slots.main) {
        var current;
                        text = page.revisions[0].slots.main.content || '';
                    } else {
                        text = page.revisions[0].content || '';
                    }
                }


                item = parseItemTemplate(text, title);
        if (!key) return;


                if (!item) {
        current = lookup[key];
                    tooltip.innerHTML = 'No item data found.';
                    return;
                }


                cache[title] = item;
        if (!current || rarityRank(item) > rarityRank(current)) {
            lookup[key] = item;
        }
    }


                tooltip.className =
    function getHrefTitle(link) {
                    'eqoa-related-item-tooltip tooltip-rarity-' +
        var href = link.getAttribute('href') || '';
                    normalizeRarity(item.rarity);
        var title = '';


                tooltip.innerHTML = buildTooltip(item);
        if (href.indexOf('title=') !== -1) {
             });
            title = href.split('title=')[1].split('&')[0];
             return decodeURIComponent(title).replace(/_/g, ' ').trim();
         }
         }


         Array.prototype.forEach.call(relatedLinks, function (link) {
         if (href.indexOf('/index.php/') !== -1) {
             if (link.classList.contains('eqoa-related-tooltip-ready')) return;
            title = href.split('/index.php/')[1].split('?')[0];
             return decodeURIComponent(title).replace(/_/g, ' ').trim();
        }


            link.classList.add('eqoa-related-tooltip-ready');
        return '';
 
    }
            link.addEventListener('mouseenter', function (event) {
                showTooltip(link, event);
            });


             link.addEventListener('mousemove', moveTooltip);
    function getCandidates(link) {
        var row = link.closest('tr');
        var names = [
             link.getAttribute('data-eqoa-item-name'),
            row && row.getAttribute('data-eqoa-item-name'),
            link.textContent,
            stripExtraText(link.textContent),
            getHrefTitle(link),
            stripExtraText(getHrefTitle(link))
        ];


            link.addEventListener('mouseleave', function () {
        return names.filter(function (name) {
                tooltip.style.display = 'none';
            return name && String(name).trim();
            });
         });
         });
     }
     }


function ensureMobileSidebarButton() {
    function findItem(link) {
    if (document.getElementById('eqoa-mobile-sidebar-toggle')) return;
        var candidates = getCandidates(link);
        var i;
        var name;
        var item;


    var button = document.createElement('button');
        for (i = 0; i < candidates.length; i++) {
    button.id = 'eqoa-mobile-sidebar-toggle';
            name = candidates[i];
    button.type = 'button';
            item = lookup[normalizeName(name)] || lookup[looseName(name)];
    button.textContent = '☰';
    button.setAttribute('aria-label', 'Open sidebar');


    button.addEventListener('click', function () {
            if (item) return item;
    document.documentElement.classList.toggle('eqoa-mobile-sidebar-open');
        }


    setTimeout(removeMobilePageActions, 50);
        return null;
     setTimeout(removeMobilePageActions, 250);
     }
});


     document.body.appendChild(button);
     function normalizeGeneratedItem(item) {
}
        return {
            name: item.name || '',
            rarity: item.rarity || 'common',
            slot: item.slot || '',
            level: item.level || item.lvl || '',
            race: item.race || '',
            className: Array.isArray(item.class) ? item.class.join(', ') : (item.class || item.classes || item.className || ''),
            notrade: item.notrade || item.noTrade || item.trade === 'NO TRADE',
            lore: item.lore || '',
            dmg: item.dmg || item.damage || '',
            ac: item.ac || '',
            str: item.str || '',
            sta: item.sta || '',
            agi: item.agi || '',
            dex: item.dex || '',
            wis: item.wis || '',
            int: item.int || '',
            cha: item.cha || '',
            hp: item.hp || '',
            hot: item.hot || '',
            pow: item.pow || '',
            pot: item.pot || '',
            pr: item.pr || '',
            dr: item.dr || '',
            fr: item.fr || '',
            cr: item.cr || '',
            lr: item.lr || '',
            ar: item.ar || '',
            dur: item.dur || '',
            gearhp: item.gearhp || item.gearHp || item.gear_hp || '',
            effect: item.effect || '',
            proc: item.proc || '',
            source: item.source || ''
        };
    }


function stackZoneColumnsOnMobile() {
    items.forEach(function (item) {
    if (!window.matchMedia || !window.matchMedia('(max-width: 700px)').matches) return;
        if (!item || !item.name) return;
    if (document.querySelector('.eqoa-mobile-zone-stack')) return;


    var left = document.querySelector('.zone-left');
        storeItem(normalizeName(item.name), item);
     var right = document.querySelector('.zone-right');
        storeItem(looseName(item.name), item);
    });
 
     Array.prototype.forEach.call(links, function (link) {
        var item = findItem(link);
        var tooltipItem;
        var rarity;


    if (!left || !right) return;
        if (!item) return;


    var leftTable = left.closest('table');
        tooltipItem = normalizeGeneratedItem(item);
    var rightTable = right.closest('table');
        rarity = normalizeClassItemRarity(tooltipItem.rarity);
    var layoutTable = leftTable && leftTable === rightTable ? leftTable : null;


    var stack = document.createElement('div');
        link.classList.remove(
    stack.className = 'eqoa-mobile-zone-stack';
            'class-item-rarity-common',
            'class-item-rarity-uncommon',
            'class-item-rarity-rare',
            'class-item-rarity-ultra-rare',
            'eqoa-related-tooltip-ready',
            'items-found-tooltip-ready'
        );


    if (layoutTable) {
        link.classList.add('class-item-tooltip-link');
         layoutTable.parentNode.insertBefore(stack, layoutTable);
         link.classList.add('class-item-rarity-' + rarity);
link.classList.add('cb-rarity-' + rarity);
        applyClassItemRarityStyle(link, rarity);


         if (left.tagName.toLowerCase() === 'td') {
         if (link._eqoaGeneratedTooltipBound) return;
            var leftBox = document.createElement('div');
        link._eqoaGeneratedTooltipBound = true;
            leftBox.className = left.className;
            while (left.firstChild) leftBox.appendChild(left.firstChild);
            stack.appendChild(leftBox);
        } else {
            stack.appendChild(left);
        }


         if (right.tagName.toLowerCase() === 'td') {
         link.addEventListener('mouseenter', function (event) {
             var rightBox = document.createElement('div');
             showClassItemTooltip(tooltipItem, event);
            rightBox.className = right.className;
         });
            while (right.firstChild) rightBox.appendChild(right.firstChild);
            stack.appendChild(rightBox);
         } else {
            stack.appendChild(right);
        }


         layoutTable.style.setProperty('display', 'none', 'important');
         link.addEventListener('mousemove', moveClassItemTooltip);
    } else {
        left.parentNode.insertBefore(stack, left);
        stack.appendChild(left);
        stack.appendChild(right);
    }


    stack.querySelectorAll('.zone-left, .zone-right').forEach(function (el) {
        link.addEventListener('mouseleave', function () {
        el.style.setProperty('display', 'block', 'important');
            hideClassItemTooltip();
        el.style.setProperty('float', 'none', 'important');
         });
        el.style.setProperty('clear', 'both', 'important');
        el.style.setProperty('width', '100%', 'important');
         el.style.setProperty('max-width', '100%', 'important');
        el.style.setProperty('margin', '0 0 12px 0', 'important');
     });
     });
}
}


function initItemsFoundHereRarityTooltips() {
    initEqoaGeneratedItemLinks();
}
function initRelatedItemTooltips($content) {
    initEqoaGeneratedItemLinks($content);
}


function removeMobilePageActions() {
return;


    if (!window.matchMedia || !window.matchMedia('(max-width: 700px)').matches) return;


var selectors = [
    '#p-cactions',
    '#p-views',
    '#p-namespaces',
    '#left-navigation',
    '#right-navigation',
    '#head',
    '#mw-head',
    'li[id^="ca-"]',
    'a[id^="ca-"]',
    '[id^="ca-"]'
];


    selectors.forEach(function (selector) {
        document.querySelectorAll(selector).forEach(function (el) {
            el.remove();
        });
    });


    document.querySelectorAll('#column-one .portlet, #column-one .generated-sidebar, #column-one div').forEach(function (el) {
        var label = '';
        var heading = el.querySelector('h3, h4, h5');


        if (heading) {
            label = heading.textContent.trim().toUpperCase();
        } else {
            label = el.textContent.trim().toUpperCase();
        }


        if (label.indexOf('PAGE ACTIONS') !== -1) {
            el.remove();
        }
    });


    document.querySelectorAll('#column-one a, #column-one button, #column-one input, #column-one img').forEach(function (el) {
        if (el.closest('#p-search')) return;
        if (el.id === 'eqoa-mobile-sidebar-toggle' || el.id === 'eqoa-theme-toggle') return;


        var visibleText = (el.textContent || el.value || '').trim();
        var rect = el.getBoundingClientRect();


        if (
            !visibleText &&
            rect.width > 0 &&
            rect.height > 0 &&
            rect.width <= 70 &&
            rect.height <= 70
        ) {
            var removeTarget = el.closest('li, a, button, input, div') || el;
            removeTarget.remove();
        }
    });


    document.querySelectorAll('a, button, input, li, div, img').forEach(function (el) {
        if (el.id === 'eqoa-mobile-sidebar-toggle' || el.id === 'eqoa-theme-toggle') return;
        if (el.closest('#p-search')) return;


        var rect = el.getBoundingClientRect();
        var visibleText = (el.textContent || el.value || '').trim();


        if (
function ensureMobileSidebarButton() {
            !visibleText &&
    if (document.getElementById('eqoa-mobile-sidebar-toggle')) return;
            rect.width > 0 &&
 
            rect.height > 0 &&
    var button = document.createElement('button');
            rect.width <= 70 &&
    button.id = 'eqoa-mobile-sidebar-toggle';
            rect.height <= 70 &&
    button.type = 'button';
            rect.top >= 0 &&
    button.textContent = '☰';
            rect.top <= 140
    button.setAttribute('aria-label', 'Open sidebar');
        ) {
 
            var removeTarget = el.closest('li, a, button, input, div') || el;
    button.addEventListener('click', function () {
            removeTarget.remove();
    document.documentElement.classList.toggle('eqoa-mobile-sidebar-open');
        }
 
     });
    setTimeout(removeMobilePageActions, 50);
    setTimeout(removeMobilePageActions, 250);
});
 
     document.body.appendChild(button);
}
}


function stackZoneColumnsOnMobile() {
    if (!window.matchMedia || !window.matchMedia('(max-width: 700px)').matches) return;
    if (document.querySelector('.eqoa-mobile-zone-stack')) return;
    var left = document.querySelector('.zone-left');
    var right = document.querySelector('.zone-right');


  function loadBuilderScripts() {
     if (!left || !right) return;
     loadWikiScript('MediaWiki:EQOAConfig.js')
        .always(function () {
            loadWikiScript('MediaWiki:CharacterBuilderData.js')
                .done(function () {
                    $.when(
                        loadScriptUrl('https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js'),
                        loadScriptUrl('https://cdnjs.cloudflare.com/ajax/libs/lz-string/1.5.0/lz-string.min.js')
                    ).always(function () {
                        loadWikiScript('MediaWiki:CharacterBuilder.js')
                            .fail(function (error) {
                                console.error('Character Builder failed to load:', error);
                            });
                    });
                })
                .fail(function (error) {
                    console.error('Character Builder data failed to load:', error);
                });
        });
}


    var leftTable = left.closest('table');
    var rightTable = right.closest('table');
    var layoutTable = leftTable && leftTable === rightTable ? leftTable : null;


    var stack = document.createElement('div');
    stack.className = 'eqoa-mobile-zone-stack';


    if (layoutTable) {
        layoutTable.parentNode.insertBefore(stack, layoutTable);


        if (left.tagName.toLowerCase() === 'td') {
            var leftBox = document.createElement('div');
            leftBox.className = left.className;
            while (left.firstChild) leftBox.appendChild(left.firstChild);
            stack.appendChild(leftBox);
        } else {
            stack.appendChild(left);
        }


function loadClassMasteryScripts() {
        if (right.tagName.toLowerCase() === 'td') {
    loadWikiScript('MediaWiki:ClassMasteryData.js')
            var rightBox = document.createElement('div');
        .done(function () {
            rightBox.className = right.className;
            loadWikiScript('MediaWiki:ClassMasteryRawData.js')
            while (right.firstChild) rightBox.appendChild(right.firstChild);
                .always(function () {
            stack.appendChild(rightBox);
                    loadWikiScript('MediaWiki:ClassMasteryIds.js')
         } else {
                        .always(function () {
             stack.appendChild(right);
                            loadWikiScript('MediaWiki:ClassMasteryCalculator.js')
         }
                                .fail(function (error) {
                                    console.error('Class Mastery calculator failed to load:', error);
                                });
                        });
                });
         })
        .fail(function (error) {
             console.error('Class Mastery data failed to load:', error);
         });
}
                     


    window.EQOA_APPLY_WIKI_THEME = applyWikiTheme;
        layoutTable.style.setProperty('display', 'none', 'important');
    window.EQOA_GET_WIKI_THEME = getSavedWikiTheme;
 
        if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', ensureThemeButton);
     } else {
     } else {
         ensureThemeButton();
         left.parentNode.insertBefore(stack, left);
        stack.appendChild(left);
        stack.appendChild(right);
     }
     }


     mw.hook('wikipage.content').add(function ($content) {
     stack.querySelectorAll('.zone-left, .zone-right').forEach(function (el) {
         ensureThemeButton();
         el.style.setProperty('display', 'block', 'important');
         ensureMobileSidebarButton();
         el.style.setProperty('float', 'none', 'important');
         stackZoneColumnsOnMobile();
        el.style.setProperty('clear', 'both', 'important');
         removeMobilePageActions();
         el.style.setProperty('width', '100%', 'important');
         ensureMobileSidebarButton();
         el.style.setProperty('max-width', '100%', 'important');
         el.style.setProperty('margin', '0 0 12px 0', 'important');
    });
}


        setTimeout(stackZoneColumnsOnMobile, 250);
        setTimeout(stackZoneColumnsOnMobile, 1000);


        setTimeout(function () {
function removeMobilePageActions() {
            removeMobilePageActions();
return;
            ensureMobileSidebarButton();
        }, 250);


        setTimeout(function () {
    if (!window.matchMedia || !window.matchMedia('(max-width: 700px)').matches) return;
            removeMobilePageActions();
            ensureMobileSidebarButton();
        }, 1000);


        initRaidChecks();
var selectors = [
styleRareEncounterRows();
    '#p-cactions',
setTimeout(styleRareEncounterRows, 100);
    '#p-views',
setTimeout(styleRareEncounterRows, 500);
    '#p-namespaces',
setTimeout(buildRareEncounterMobileLayout, 100);
    '#left-navigation',
setTimeout(buildRareEncounterMobileLayout, 500);
    '#right-navigation',
        initItemFilters();
    '#head',
    '#mw-head',
    'li[id^="ca-"]',
    'a[id^="ca-"]',
    '[id^="ca-"]'
];


         mw.loader.using('mediawiki.api').then(function () {
    selectors.forEach(function (selector) {
             initRelatedItemTooltips($content);
         document.querySelectorAll(selector).forEach(function (el) {
             el.remove();
         });
         });
     });
     });


     mw.loader.using('mediawiki.util').then(function () {
     document.querySelectorAll('#column-one .portlet, #column-one .generated-sidebar, #column-one div').forEach(function (el) {
         loadBuilderScripts();
         var label = '';
         loadClassMasteryScripts();
         var heading = el.querySelector('h3, h4, h5');
    });
})();


        if (heading) {
            label = heading.textContent.trim().toUpperCase();
        } else {
            label = el.textContent.trim().toUpperCase();
        }


        if (label.indexOf('PAGE ACTIONS') !== -1) {
            el.remove();
        }
    });


    document.querySelectorAll('#column-one a, #column-one button, #column-one input, #column-one img').forEach(function (el) {
        if (el.closest('#p-search')) return;
        if (el.id === 'eqoa-mobile-sidebar-toggle' || el.id === 'eqoa-theme-toggle') return;


        var visibleText = (el.textContent || el.value || '').trim();
        var rect = el.getBoundingClientRect();


function buildRareEncounterMobileLayout() {
        if (
    var isMobile = window.matchMedia('(max-width: 700px)').matches;
            !visibleText &&
     var tables = document.querySelectorAll('.raid-encounter-table table');
            rect.width > 0 &&
            rect.height > 0 &&
            rect.width <= 70 &&
            rect.height <= 70
        ) {
            var removeTarget = el.closest('li, a, button, input, div') || el;
            removeTarget.remove();
        }
     });


     function styleRareMobileHeadings(root) {
     document.querySelectorAll('a, button, input, li, div, img').forEach(function (el) {
         var isLightTheme =
         if (el.id === 'eqoa-mobile-sidebar-toggle' || el.id === 'eqoa-theme-toggle') return;
            document.documentElement.classList.contains('eqoa-theme-light') ||
        if (el.closest('#p-search')) return;
            document.body.classList.contains('eqoa-theme-light');


         Array.prototype.forEach.call(root.querySelectorAll('.rare-mobile-heading'), function (heading) {
         var rect = el.getBoundingClientRect();
            heading.style.padding = '9px 10px';
        var visibleText = (el.textContent || el.value || '').trim();
            heading.style.textAlign = 'center';
            heading.style.fontSize = '18px';
            heading.style.fontWeight = 'bold';
            heading.style.borderBottom = isLightTheme ? '1px solid #5a3a25' : '1px solid #1f1f1f';
            heading.style.background = isLightTheme ? '#3a2418' : '#3a3a3a';
            heading.style.color = isLightTheme ? '#ffffff' : '#ffd45a';
        });
    }


    Array.prototype.forEach.call(tables, function (table) {
        if (
        var wrapper = table.closest('.raid-encounter-table');
            !visibleText &&
         if (!wrapper) return;
            rect.width > 0 &&
 
            rect.height > 0 &&
        var existingMobile = wrapper.querySelector('.rare-encounter-mobile-layout');
            rect.width <= 70 &&
 
            rect.height <= 70 &&
        if (!isMobile) {
            rect.top >= 0 &&
             table.style.display = '';
            rect.top <= 140
            if (existingMobile) existingMobile.style.display = 'none';
         ) {
            return;
            var removeTarget = el.closest('li, a, button, input, div') || el;
             removeTarget.remove();
         }
         }
    });
}


        table.style.display = 'none';


         if (existingMobile) {
  function loadBuilderScripts() {
             existingMobile.style.display = 'block';
    loadWikiScript('MediaWiki:EQOAConfig.js')
            styleRareMobileHeadings(existingMobile);
         .always(function () {
            return;
             loadWikiScript('MediaWiki:CharacterBuilderData.js')
         }
                .done(function () {
                    $.when(
                        loadScriptUrl('https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js'),
                        loadScriptUrl('https://cdnjs.cloudflare.com/ajax/libs/lz-string/1.5.0/lz-string.min.js')
                    ).always(function () {
                        loadWikiScript('MediaWiki:CharacterBuilder.js')
                            .fail(function (error) {
                                console.error('Character Builder failed to load:', error);
                            });
                    });
                })
                .fail(function (error) {
                    console.error('Character Builder data failed to load:', error);
                });
         });
}


        var mobile = document.createElement('div');
        mobile.className = 'rare-encounter-mobile-layout';


        var rows = table.querySelectorAll('tr');


        function addMobileBox(title, cell) {
            if (!cell || !cell.textContent.trim()) return;


            var box = document.createElement('div');
            box.className = 'rare-mobile-box';


             var heading = document.createElement('div');
function loadClassMasteryScripts() {
             heading.className = 'rare-mobile-heading';
    loadWikiScript('MediaWiki:ClassMasteryData.js')
            heading.textContent = title;
        .done(function () {
             loadWikiScript('MediaWiki:ClassMasteryRawData.js')
                .always(function () {
                    loadWikiScript('MediaWiki:ClassMasteryIds.js')
                        .always(function () {
                            loadWikiScript('MediaWiki:ClassMasteryCalculator.js')
                                .fail(function (error) {
                                    console.error('Class Mastery calculator failed to load:', error);
                                });
                        });
                });
        })
        .fail(function (error) {
             console.error('Class Mastery data failed to load:', error);
        });
}
                     


            var body = document.createElement('div');
    window.EQOA_APPLY_WIKI_THEME = applyWikiTheme;
            body.className = 'rare-mobile-body';
    window.EQOA_GET_WIKI_THEME = getSavedWikiTheme;
            body.innerHTML = cell.innerHTML;


            box.appendChild(heading);
        if (document.readyState === 'loading') {
            box.appendChild(body);
        document.addEventListener('DOMContentLoaded', ensureThemeButton);
            mobile.appendChild(box);
    } else {
        }
        ensureThemeButton();
    }


         if (rows[1]) {
    mw.hook('wikipage.content').add(function ($content) {
            var baseCells = rows[1].querySelectorAll('td');
         ensureThemeButton();
        ensureMobileSidebarButton();
        stackZoneColumnsOnMobile();
        removeMobilePageActions();
        ensureMobileSidebarButton();


            addMobileBox('Base World 1', baseCells[0]);
        setTimeout(stackZoneColumnsOnMobile, 250);
            addMobileBox('Base World 2', baseCells[1]);
        setTimeout(stackZoneColumnsOnMobile, 1000);
            addMobileBox('Base World 3', baseCells[2]);
            addMobileBox('Base World 4', baseCells[3]);
        }


         if (rows[3]) {
         setTimeout(function () {
             var frontiersCells = rows[3].querySelectorAll('td');
             removeMobilePageActions();
            ensureMobileSidebarButton();
        }, 250);


            addMobileBox('Frontiers 1', frontiersCells[0]);
        setTimeout(function () {
             addMobileBox('Frontiers 2', frontiersCells[1]);
             removeMobilePageActions();
             addMobileBox('Frontiers 3', frontiersCells[2]);
             ensureMobileSidebarButton();
         }
         }, 1000);


         wrapper.appendChild(mobile);
         initRaidChecks();
         styleRareMobileHeadings(mobile);
styleRareEncounterRows();
    });
setTimeout(styleRareEncounterRows, 100);
}
setTimeout(styleRareEncounterRows, 500);
setTimeout(buildRareEncounterMobileLayout, 100);
setTimeout(buildRareEncounterMobileLayout, 500);
         initItemFilters();
initItemsFoundHereRarityTooltips();


        mw.loader.using('mediawiki.api').then(function () {
            initRelatedItemTooltips($content);
        });
    });


    mw.loader.using('mediawiki.util').then(function () {
        loadBuilderScripts();
        loadClassMasteryScripts();
    });
})();




window.addEventListener('resize', buildRareEncounterMobileLayout);
window.addEventListener('orientationchange', function () {
    setTimeout(buildRareEncounterMobileLayout, 150);
});


function limitDevRenodorSidebarSections() {
    var username = mw.config.get('wgUserName');
    var isDevRenodor = username === 'DevRenodor';


    ['p-navigation', 'p-tb'].forEach(function (id) {
        var section = document.getElementById(id);


        if (section) {
function buildRareEncounterMobileLayout() {
            section.style.display = isDevRenodor ? '' : 'none';
    var isMobile = window.matchMedia('(max-width: 700px)').matches;
        }
     var tables = document.querySelectorAll('.raid-encounter-table table');
     });
}


if (document.readyState === 'loading') {
    function styleRareMobileHeadings(root) {
    document.addEventListener('DOMContentLoaded', limitDevRenodorSidebarSections);
        var isLightTheme =
} else {
            document.documentElement.classList.contains('eqoa-theme-light') ||
    limitDevRenodorSidebarSections();
            document.body.classList.contains('eqoa-theme-light');
}


$(function () {
        Array.prototype.forEach.call(root.querySelectorAll('.rare-mobile-heading'), function (heading) {
    if (window.EQOA_APPLY_WIKI_THEME && window.EQOA_GET_WIKI_THEME) {
            heading.style.padding = '9px 10px';
        window.EQOA_APPLY_WIKI_THEME(window.EQOA_GET_WIKI_THEME());
            heading.style.textAlign = 'center';
            heading.style.fontSize = '18px';
            heading.style.fontWeight = 'bold';
            heading.style.borderBottom = isLightTheme ? '1px solid #5a3a25' : '1px solid #1f1f1f';
            heading.style.background = isLightTheme ? '#3a2418' : '#3a3a3a';
            heading.style.color = isLightTheme ? '#ffffff' : '#ffd45a';
        });
     }
     }
});


function limitFlaggedRevsBadgeToDevRenodor() {
    Array.prototype.forEach.call(tables, function (table) {
    var username = mw.config.get('wgUserName');
        var wrapper = table.closest('.raid-encounter-table');
    var isDevRenodor = username === 'DevRenodor';
        if (!wrapper) return;
 
        var existingMobile = wrapper.querySelector('.rare-encounter-mobile-layout');


    [
         if (!isMobile) {
         '#mw-fr-revisiontag',
            table.style.display = '';
        '.flaggedrevs_short',
            if (existingMobile) existingMobile.style.display = 'none';
        '.flaggedrevs_basic',
            return;
        '.flaggedrevs_preview',
        }
        '.flaggedrevs_notice',
        '.flaggedrevs_reviewform',
        '.mw-fr-reviewlink'
    ].forEach(function (selector) {
        document.querySelectorAll(selector).forEach(function (el) {
            el.style.setProperty('display', isDevRenodor ? 'block' : 'none', 'important');
        });
    });
}


if (document.readyState === 'loading') {
        table.style.display = 'none';
    document.addEventListener('DOMContentLoaded', limitFlaggedRevsBadgeToDevRenodor);
} else {
    limitFlaggedRevsBadgeToDevRenodor();
}


function fixPersonalNotificationButtons() {
        if (existingMobile) {
    function fixButton(id, labelText) {
            existingMobile.style.display = 'block';
        var item = document.getElementById(id);
            styleRareMobileHeadings(existingMobile);
        if (!item) return;
            return;
        }


         var link = item.querySelector('a');
         var mobile = document.createElement('div');
         if (!link) return;
         mobile.className = 'rare-encounter-mobile-layout';


         var rawText = link.textContent || '';
         var rows = table.querySelectorAll('tr');
        var countMatch = rawText.match(/\d+/);
        var count = countMatch ? countMatch[0] : '';


         link.textContent = count ? labelText + ' (' + count + ')' : labelText;
         function addMobileBox(title, cell) {
        link.setAttribute('title', labelText);
            if (!cell || !cell.textContent.trim()) return;
        link.setAttribute('aria-label', labelText);
    }


    fixButton('pt-notifications-alert', 'alerts');
            var box = document.createElement('div');
    fixButton('pt-notifications-notice', 'notices');
            box.className = 'rare-mobile-box';
}


if (document.readyState === 'loading') {
            var heading = document.createElement('div');
    document.addEventListener('DOMContentLoaded', fixPersonalNotificationButtons);
            heading.className = 'rare-mobile-heading';
} else {
            heading.textContent = title;
    fixPersonalNotificationButtons();
}


            var body = document.createElement('div');
            body.className = 'rare-mobile-body';
            body.innerHTML = cell.innerHTML;


            box.appendChild(heading);
            box.appendChild(body);
            mobile.appendChild(box);
        }


        if (rows[1]) {
            var baseCells = rows[1].querySelectorAll('td');


            addMobileBox('Base World 1', baseCells[0]);
            addMobileBox('Base World 2', baseCells[1]);
            addMobileBox('Base World 3', baseCells[2]);
            addMobileBox('Base World 4', baseCells[3]);
        }


        if (rows[3]) {
            var frontiersCells = rows[3].querySelectorAll('td');


mw.hook('wikipage.content').add(fixPersonalNotificationButtons);
            addMobileBox('Frontiers 1', frontiersCells[0]);
setTimeout(fixPersonalNotificationButtons, 500);
            addMobileBox('Frontiers 2', frontiersCells[1]);
setTimeout(fixPersonalNotificationButtons, 1500);
            addMobileBox('Frontiers 3', frontiersCells[2]);
        }


        wrapper.appendChild(mobile);
        styleRareMobileHeadings(mobile);
    });
}


mw.hook('wikipage.content').add(limitFlaggedRevsBadgeToDevRenodor);




mw.hook('wikipage.content').add(limitDevRenodorSidebarSections);


mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:CharacterBuilderGeneratedItems.js&action=raw&ctype=text/javascript');
window.addEventListener('resize', buildRareEncounterMobileLayout);
window.addEventListener('orientationchange', function () {
    setTimeout(buildRareEncounterMobileLayout, 150);
});


function limitDevRenodorSidebarSections() {
    var username = mw.config.get('wgUserName');
    var isDevRenodor = username === 'DevRenodor';


    ['p-navigation', 'p-tb'].forEach(function (id) {
        var section = document.getElementById(id);


        if (section) {
            section.style.display = isDevRenodor ? '' : 'none';
        }
    });
}


mw.loader.using( 'mediawiki.util', function () {
if (document.readyState === 'loading') {
     var topbar = document.createElement( 'div' );
     document.addEventListener('DOMContentLoaded', limitDevRenodorSidebarSections);
     topbar.id = 'eqoa-topbar';
} else {
     limitDevRenodorSidebarSections();
}


    topbar.innerHTML =
$(function () {
        '<a class="eqoa-top-title" href="' + mw.util.getUrl( 'Main Page' ) + '">EQOA: Sandstorm</a>' +
     if (window.EQOA_APPLY_WIKI_THEME && window.EQOA_GET_WIKI_THEME) {
        '<form class="eqoa-top-search" action="' + mw.util.getUrl( 'Special:Search' ) + '" method="get">' +
        window.EQOA_APPLY_WIKI_THEME(window.EQOA_GET_WIKI_THEME());
            '<input type="search" name="search" placeholder="Search EQOA: Sandstorm" />' +
            '<button type="submit">Search</button>' +
        '</form>' +
        '<a class="eqoa-discord-button" href="https://discord.gg/QK6r8Qd8Vu" target="_blank" rel="noopener">Join the Community</a>';
 
     document.body.insertBefore( topbar, document.body.firstChild );
} );
 
mw.loader.using( 'mediawiki.util', function () {
    if ( document.getElementById( 'eqoa-right-rail' ) ) {
        return;
     }
     }
});


     var rail = document.createElement( 'aside' );
function limitFlaggedRevsBadgeToDevRenodor() {
     rail.id = 'eqoa-right-rail';
     var username = mw.config.get('wgUserName');
     var isDevRenodor = username === 'DevRenodor';


     rail.innerHTML =
     [
         '<iframe ' +
         '#mw-fr-revisiontag',
            'src="https://discord.com/widget?id=1251536624467050628&theme=dark" ' +
        '.flaggedrevs_short',
            'width="300" ' +
        '.flaggedrevs_basic',
            'height="500" ' +
        '.flaggedrevs_preview',
            'allowtransparency="true" ' +
        '.flaggedrevs_notice',
            'frameborder="0" ' +
        '.flaggedrevs_reviewform',
            'sandbox="allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts">' +
        '.mw-fr-reviewlink'
         '</iframe>';
    ].forEach(function (selector) {
         document.querySelectorAll(selector).forEach(function (el) {
            el.style.setProperty('display', isDevRenodor ? 'block' : 'none', 'important');
        });
    });
}


     document.body.appendChild( rail );
if (document.readyState === 'loading') {
} );
     document.addEventListener('DOMContentLoaded', limitFlaggedRevsBadgeToDevRenodor);
} else {
    limitFlaggedRevsBadgeToDevRenodor();
}


function eqoaKeepPersonalToolsVisible() {
function fixPersonalNotificationButtons() {
     var personal = document.getElementById( 'p-personal' );
     function fixButton(id, labelText) {
        var item = document.getElementById(id);
        if (!item) return;


    if ( !personal ) {
        var link = item.querySelector('a');
        return;
        if (!link) return;
    }


    personal.style.display = 'block';
        var rawText = link.textContent || '';
    personal.style.visibility = 'visible';
        var countMatch = rawText.match(/\d+/);
    personal.style.opacity = '1';
        var count = countMatch ? countMatch[0] : '';
    personal.style.position = 'fixed';
    personal.style.top = '72px';
    personal.style.right = '18px';
    personal.style.left = 'auto';
    personal.style.zIndex = '2147483647';
    personal.style.background = 'transparent';
    personal.style.overflow = 'visible';


    var body = personal.querySelector( '.pBody' );
        link.textContent = count ? labelText + ' (' + count + ')' : labelText;
    var list = personal.querySelector( 'ul' );
        link.setAttribute('title', labelText);
        link.setAttribute('aria-label', labelText);
    }


     [ body, list ].forEach( function ( el ) {
     fixButton('pt-notifications-alert', 'alerts');
        if ( !el ) {
    fixButton('pt-notifications-notice', 'notices');
            return;
}
        }


        el.style.display = 'flex';
if (document.readyState === 'loading') {
        el.style.visibility = 'visible';
    document.addEventListener('DOMContentLoaded', fixPersonalNotificationButtons);
        el.style.opacity = '1';
} else {
        el.style.gap = '4px';
    fixPersonalNotificationButtons();
        el.style.alignItems = 'center';
}
        el.style.justifyContent = 'flex-end';
        el.style.background = 'transparent';
        el.style.border = '0';
        el.style.margin = '0';
        el.style.padding = '0';
        el.style.listStyle = 'none';
    } );


    personal.querySelectorAll( 'li, a' ).forEach( function ( el ) {
        el.style.display = 'inline-flex';
        el.style.visibility = 'visible';
        el.style.opacity = '1';
    } );
}


document.addEventListener( 'DOMContentLoaded', eqoaKeepPersonalToolsVisible );
mw.hook( 'wikipage.content' ).add( eqoaKeepPersonalToolsVisible );
setTimeout( eqoaKeepPersonalToolsVisible, 250 );
setTimeout( eqoaKeepPersonalToolsVisible, 1000 );
setTimeout( eqoaKeepPersonalToolsVisible, 2000 );








function eqoaBuildSidebarAccountButtons() {
mw.hook('wikipage.content').add(fixPersonalNotificationButtons);
    var sidebar = document.getElementById('column-one');
setTimeout(fixPersonalNotificationButtons, 500);
    var personal = document.getElementById('p-personal');
setTimeout(fixPersonalNotificationButtons, 1500);


    if (!sidebar || !personal) return;


    var oldBox = document.getElementById('eqoa-sidebar-account');
mw.hook('wikipage.content').add(limitFlaggedRevsBadgeToDevRenodor);
    if (oldBox) oldBox.remove();


    var links = personal.querySelectorAll('a');
    if (!links.length) return;


    var box = document.createElement('div');
mw.hook('wikipage.content').add(limitDevRenodorSidebarSections);
    box.id = 'eqoa-sidebar-account';


    var title = document.createElement('div');
mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:CharacterBuilderGeneratedItems.js&action=raw&ctype=text/javascript');
    title.className = 'eqoa-sidebar-account-title';
    title.textContent = 'Account';
box.appendChild(title);


var grid = document.createElement('div');
grid.className = 'eqoa-sidebar-account-grid';
box.appendChild(grid);


links.forEach(function (link) {
        var text = (link.textContent || '').trim();


        if (!text) return;


        var clone = document.createElement('a');
mw.loader.using( 'mediawiki.util', function () {
        clone.href = link.href;
    var topbar = document.createElement( 'div' );
        clone.textContent = text;
    topbar.id = 'eqoa-topbar';
        clone.title = link.title || text;
        clone.className = 'eqoa-sidebar-account-button';


         grid.appendChild(clone);
    topbar.innerHTML =
    });
         '<a class="eqoa-top-title" href="' + mw.util.getUrl( 'Main Page' ) + '">EQOA: Sandstorm</a>' +
        '<form class="eqoa-top-search" action="' + mw.util.getUrl( 'Special:Search' ) + '" method="get">' +
            '<input type="search" name="search" placeholder="Search EQOA: Sandstorm" />' +
            '<button type="submit">Search</button>' +
        '</form>' +
        '<a class="eqoa-discord-button" href="https://discord.gg/QK6r8Qd8Vu" target="_blank" rel="noopener">Join the Community</a>';


     var logo = document.getElementById('p-logo');
     document.body.insertBefore( topbar, document.body.firstChild );
} );


    if (logo && logo.parentNode === sidebar) {
mw.loader.using( 'mediawiki.util', function () {
        logo.insertAdjacentElement('afterend', box);
    if ( document.getElementById( 'eqoa-right-rail' ) ) {
    } else {
         return;
         sidebar.insertBefore(box, sidebar.firstChild);
     }
     }
}


    var rail = document.createElement( 'aside' );
    rail.id = 'eqoa-right-rail';


/* =========================================================
    rail.innerHTML =
  EQOA Interactive Maps - Load Leaflet
        '<iframe ' +
  ========================================================= */
            'src="https://discord.com/widget?id=1251536624467050628&theme=dark" ' +
            'width="300" ' +
            'height="500" ' +
            'allowtransparency="true" ' +
            'frameborder="0" ' +
            'sandbox="allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts">' +
        '</iframe>';


mw.loader.load('https://unpkg.com/leaflet@1.9.4/dist/leaflet.js');
    document.body.appendChild( rail );
mw.loader.load('https://unpkg.com/leaflet@1.9.4/dist/leaflet.css', 'text/css');
} );


/* =========================================================
function eqoaKeepPersonalToolsVisible() {
  EQOA Interactive Maps
    var personal = document.getElementById( 'p-personal' );
  ========================================================= */


function initEqoaInteractiveMaps() {
     if ( !personal ) {
     if (typeof L === 'undefined') {
        setTimeout(initEqoaInteractiveMaps, 100);
         return;
         return;
     }
     }


     document.querySelectorAll('.eqoa-interactive-map').forEach(function (mapBox) {
     personal.style.display = 'block';
        if (mapBox.classList.contains('eqoa-map-ready')) return;
    personal.style.visibility = 'visible';
    personal.style.opacity = '1';
    personal.style.position = 'fixed';
    personal.style.top = '72px';
    personal.style.right = '18px';
    personal.style.left = 'auto';
    personal.style.zIndex = '2147483647';
    personal.style.background = 'transparent';
    personal.style.overflow = 'visible';


        var imageUrl = mapBox.getAttribute('data-image');
    var body = personal.querySelector( '.pBody' );
        var width = parseInt(mapBox.getAttribute('data-width'), 10) || 2048;
    var list = personal.querySelector( 'ul' );
        var height = parseInt(mapBox.getAttribute('data-height'), 10) || 2048;
        var points = mapBox.querySelectorAll('.eqoa-map-point');
        var bounds = [[0, 0], [height, width]];
      var layersByType = {};
var markersByType = {};
        var map;


if (!imageUrl) return;
    [ body, list ].forEach( function ( el ) {
        if ( !el ) {
            return;
        }


mapBox.style.aspectRatio = width + ' / ' + height;
        el.style.display = 'flex';
mapBox.style.height = 'auto';
        el.style.visibility = 'visible';
 
        el.style.opacity = '1';
mapBox.classList.add('eqoa-map-ready');
        el.style.gap = '4px';
 
        el.style.alignItems = 'center';
map = L.map(mapBox, {
        el.style.justifyContent = 'flex-end';
    crs: L.CRS.Simple,
        el.style.background = 'transparent';
     minZoom: -2,
        el.style.border = '0';
    maxZoom: 3,
        el.style.margin = '0';
     zoomSnap: 0.25,
        el.style.padding = '0';
    attributionControl: false,
        el.style.listStyle = 'none';
    maxBounds: bounds,
     } );
    maxBoundsViscosity: 1.0
 
});
     personal.querySelectorAll( 'li, a' ).forEach( function ( el ) {
        el.style.display = 'inline-flex';
        el.style.visibility = 'visible';
        el.style.opacity = '1';
    } );
}


mapBox._leaflet_map_instance = map;
document.addEventListener( 'DOMContentLoaded', eqoaKeepPersonalToolsVisible );
mw.hook( 'wikipage.content' ).add( eqoaKeepPersonalToolsVisible );
setTimeout( eqoaKeepPersonalToolsVisible, 250 );
setTimeout( eqoaKeepPersonalToolsVisible, 1000 );
setTimeout( eqoaKeepPersonalToolsVisible, 2000 );


map.on('click', function (event) {
    console.log(
        'EQOA map coordinate:',
        'data-x="' + Math.round(event.latlng.lng) + '"',
        'data-y="' + Math.round(event.latlng.lat) + '"'
    );
});


var imageLayer = L.imageOverlay(imageUrl, bounds).addTo(map);


function getMapCenter() {
    return [height / 2, width / 2];
}


function fitEqoaMapToBox() {
function eqoaBuildSidebarAccountButtons() {
     map.invalidateSize();
     var sidebar = document.getElementById('column-one');
    var personal = document.getElementById('p-personal');


     var fitZoom = map.getBoundsZoom(bounds, false);
     if (!sidebar || !personal) return;


     map.setMinZoom(fitZoom);
     var oldBox = document.getElementById('eqoa-sidebar-account');
     map.setMaxZoom(fitZoom + 3);
     if (oldBox) oldBox.remove();
    map.setMaxBounds(bounds);


     map.fitBounds(bounds, {
     var links = personal.querySelectorAll('a');
        animate: false,
     if (!links.length) return;
        padding: [0, 0]
     });


     map.dragging.disable();
     var box = document.createElement('div');
}
    box.id = 'eqoa-sidebar-account';


function updateMapDragging() {
    var title = document.createElement('div');
     var minZoom = map.getMinZoom();
     title.className = 'eqoa-sidebar-account-title';
    title.textContent = 'Account';
box.appendChild(title);


    if (map.getZoom() <= minZoom + 0.001) {
var grid = document.createElement('div');
        map.fitBounds(bounds, {
grid.className = 'eqoa-sidebar-account-grid';
            animate: false,
box.appendChild(grid);
            padding: [0, 0]
 
         });
links.forEach(function (link) {
         var text = (link.textContent || '').trim();


         map.dragging.disable();
         if (!text) return;
    } else {
        map.dragging.enable();
    }
}


imageLayer.on('load', fitEqoaMapToBox);
        var clone = document.createElement('a');
        clone.href = link.href;
        clone.textContent = text;
        clone.title = link.title || text;
        clone.className = 'eqoa-sidebar-account-button';


setTimeout(fitEqoaMapToBox, 50);
        grid.appendChild(clone);
setTimeout(fitEqoaMapToBox, 250);
    });
setTimeout(fitEqoaMapToBox, 750);


map.on('zoomend', updateMapDragging);
    var logo = document.getElementById('p-logo');


window.addEventListener('resize', function () {
    if (logo && logo.parentNode === sidebar) {
    setTimeout(fitEqoaMapToBox, 100);
        logo.insertAdjacentElement('afterend', box);
});
    } else {
        sidebar.insertBefore(box, sidebar.firstChild);
    }
}




/* =========================================================
  EQOA Interactive Maps - Load Leaflet
  ========================================================= */


        points.forEach(function (point) {
mw.loader.load('https://unpkg.com/leaflet@1.9.4/dist/leaflet.js');
var type = point.getAttribute('data-type') || 'Marker';
mw.loader.load('https://unpkg.com/leaflet@1.9.4/dist/leaflet.css', 'text/css');
var namesRaw = point.getAttribute('data-names') || point.getAttribute('data-name') || 'Unknown';
 
var pagesRaw = point.getAttribute('data-pages') || point.getAttribute('data-page') || namesRaw;
/* =========================================================
  EQOA Interactive Maps
  ========================================================= */


var names = namesRaw.split(',').map(function (name) {
function initEqoaInteractiveMaps() {
     return name.trim();
     if (typeof L === 'undefined') {
}).filter(Boolean);
        setTimeout(initEqoaInteractiveMaps, 100);
        return;
    }


var pages = pagesRaw.split(',').map(function (page) {
document.querySelectorAll('.eqoa-interactive-map').forEach(function (mapBox) {
    return page.trim();
        if (mapBox.classList.contains('eqoa-map-ready')) return;
}).filter(Boolean);


var name = names.join(' / ');
        var imageUrl = mapBox.getAttribute('data-image');
var page = pages[0] || names[0] || 'Unknown';
        var width = parseInt(mapBox.getAttribute('data-width'), 10) || 2048;
var level = point.getAttribute('data-level') || '';
        var height = parseInt(mapBox.getAttribute('data-height'), 10) || 2048;
var x = parseFloat(point.getAttribute('data-x'));
        var points = mapBox.querySelectorAll('.eqoa-map-point');
var y = parseFloat(point.getAttribute('data-y'));
        var bounds = [[0, 0], [height, width]];
            var marker;
        var layersByType = {};
            var label;
        var markersByType = {};
        var map;


            if (isNaN(x) || isNaN(y)) return;
        if (!imageUrl) return;


if (!layersByType[type]) {
mapBox.style.aspectRatio = width + ' / ' + height;
    layersByType[type] = L.layerGroup().addTo(map);
mapBox.style.width = '100%';
    markersByType[type] = [];
mapBox.style.height = 'auto';
}


            label = '<b>' + name + '</b>';
mapBox.classList.add('eqoa-map-ready');
            if (level) label += '<br>Level: ' + level;
            label += '<br>Type: ' + type;


marker = L.circleMarker([y, x], {
map = L.map(mapBox, {
     radius: 4,
     crs: L.CRS.Simple,
     color: getEqoaMapMarkerOutlineColor(type),
     minZoom: -2,
     fillColor: getEqoaMapMarkerColor(type),
     maxZoom: 3,
     fillOpacity: 0.95,
     zoomSnap: 0.25,
     weight: 2,
     attributionControl: false,
     className: 'eqoa-map-dot'
    maxBounds: bounds,
     maxBoundsViscosity: 1.0
});
});


var typeLower = String(type || '').toLowerCase();
mapBox._leaflet_map_instance = map;
var isCamp = typeLower === 'camp' || typeLower === 'camps';
var hasMultiplePages = names.length > 1;


if (hasMultiplePages) {
map.on('click', function (event) {
     label = '<b>Shared Spawn</b>';
     console.log(
    if (level) label += '<br>Level: ' + level;
        'EQOA map coordinate:',
    label += '<br>Type: ' + type;
        'data-x="' + Math.round(event.latlng.lng) + '"',
    label += '<br>';
        'data-y="' + Math.round(event.latlng.lat) + '"'
    );
});
 
var imageLayer = L.imageOverlay(imageUrl, bounds).addTo(map);


    names.forEach(function (npcName, index) {
mapBox._leaflet_image_layer = imageLayer;
        var npcPage = pages[index] || npcName;
        var href = window.mw && mw.util && mw.util.getUrl
            ? mw.util.getUrl(npcPage)
            : '/index.php/' + encodeURIComponent(npcPage.replace(/ /g, '_'));


        label += '<br><a href="' + href + '">' + npcName + '</a>';
function sizeEqoaMobileMapBox() {
     });
    var parent;
     var boxWidth;
    var boxHeight;


marker.bindPopup(label, {
    if (!window.matchMedia || !window.matchMedia('(max-width: 700px)').matches) {
    closeButton: true,
        return;
     autoClose: false,
     }
    closeOnClick: false,
    className: 'eqoa-map-popup'
});


var sharedPopupPinned = false;
    parent = mapBox.closest('.zone-interactive-map-content');
var sharedPopupCloseTimer;


marker.on('mouseover', function () {
    if (parent) {
    clearTimeout(sharedPopupCloseTimer);
        parent.style.setProperty('padding', '0', 'important');
    marker.openPopup();
        parent.style.setProperty('height', 'auto', 'important');
});
        parent.style.setProperty('min-height', '0', 'important');
        parent.style.setProperty('background', 'transparent', 'important');
    }


marker.on('mouseout', function () {
    mapBox.style.setProperty('display', 'block', 'important');
     clearTimeout(sharedPopupCloseTimer);
    mapBox.style.setProperty('width', '100%', 'important');
    mapBox.style.setProperty('max-width', '650px', 'important');
    mapBox.style.setProperty('margin', '0 auto', 'important');
    mapBox.style.setProperty('padding', '0', 'important');
    mapBox.style.setProperty('aspect-ratio', width + ' / ' + height, 'important');
    mapBox.style.setProperty('overflow', 'hidden', 'important');
     mapBox.style.setProperty('background', 'transparent', 'important');


     sharedPopupCloseTimer = setTimeout(function () {
     boxWidth = mapBox.clientWidth || mapBox.getBoundingClientRect().width;
        if (!sharedPopupPinned) {
            marker.closePopup();
        }
    }, 350);
});


marker.on('click', function () {
    if (!boxWidth) {
    sharedPopupPinned = true;
        return;
     marker.openPopup();
     }
});


marker.on('popupclose', function () {
     boxHeight = Math.round(boxWidth * height / width);
     sharedPopupPinned = false;
});
} else if (isCamp) {
    marker.bindTooltip(label, {
        direction: 'top',
        sticky: true
    });
} else {
    marker.bindTooltip(label + '<br><span style="color:#ffd45a;">Click to open page</span>', {
        direction: 'top',
        sticky: true
    });


     marker.on('click', function () {
     mapBox.style.setProperty('height', boxHeight + 'px', 'important');
        if (window.mw && mw.util && mw.util.getUrl) {
    mapBox.style.setProperty('min-height', boxHeight + 'px', 'important');
            window.location.href = mw.util.getUrl(page);
    mapBox.style.setProperty('max-height', boxHeight + 'px', 'important');
        } else {
            window.location.href = '/index.php/' + encodeURIComponent(page.replace(/ /g, '_'));
        }
    });
}
}


/*
function repairEqoaMobileMapImage() {
marker.addTo(layersByType[type]);
    var imageElement;
    var northWest;
    var southEast;
    var imageWidth;
    var imageHeight;
 
    if (!window.matchMedia || !window.matchMedia('(max-width: 700px)').matches) {
        return;
    }
 
    imageLayer.setBounds(bounds);


*/
    imageElement = imageLayer.getElement && imageLayer.getElement();


markersByType[type].push({
    if (!imageElement) {
    marker: marker,
        return;
    name: name,
     }
     type: type
});        });


var filterControl = L.control({
    northWest = map.latLngToLayerPoint([height, 0]);
     position: 'topright'
     southEast = map.latLngToLayerPoint([0, width]);
});


filterControl.onAdd = function () {
     imageWidth = Math.ceil(Math.abs(southEast.x - northWest.x));
     var wrapper = L.DomUtil.create('div', 'eqoa-map-filter-control');
     imageHeight = Math.ceil(Math.abs(southEast.y - northWest.y));
     var button = document.createElement('button');
    var panel = document.createElement('div');


     button.type = 'button';
     imageElement.style.setProperty('display', 'block', 'important');
     button.className = 'eqoa-map-filter-icon';
     imageElement.style.setProperty('visibility', 'visible', 'important');
     button.setAttribute('aria-label', 'Map filters');
     imageElement.style.setProperty('opacity', '1', 'important');
button.innerHTML =
    imageElement.style.setProperty('width', imageWidth + 'px', 'important');
     '<span class="eqoa-map-layer-icon">' +
     imageElement.style.setProperty('height', imageHeight + 'px', 'important');
        '<span></span><span></span><span></span>' +
    imageElement.style.setProperty('max-width', 'none', 'important');
     '</span>';
     imageElement.style.setProperty('max-height', 'none', 'important');
}


    panel.className = 'eqoa-map-filter-panel';


    L.DomEvent.disableClickPropagation(wrapper);
function getMapCenter() {
     L.DomEvent.disableScrollPropagation(wrapper);
     return [height / 2, width / 2];
}


    Object.keys(markersByType).sort().forEach(function (type) {
function fitEqoaMapToBox() {
        var group = document.createElement('div');
    var fitZoom;
         group.className = 'eqoa-map-filter-group';
    var isMobileMap =
        window.matchMedia &&
         window.matchMedia('(max-width: 700px)').matches;


        var header = document.createElement('div');
    sizeEqoaMobileMapBox();
        header.className = 'eqoa-map-filter-header';


        var expand = document.createElement('button');
    if (isMobileMap) {
         expand.type = 'button';
         map.options.zoomSnap = 0;
        expand.className = 'eqoa-map-filter-expand';
    }
        expand.textContent = '+';


        var typeCheck = document.createElement('input');
    map.invalidateSize();
        typeCheck.type = 'checkbox';
        typeCheck.checked = false;


        typeCheck.className = 'eqoa-map-filter-type-check';
    fitZoom = map.getBoundsZoom(bounds, false);


        var label = document.createElement('span');
    map.setMinZoom(fitZoom);
        label.className = 'eqoa-map-filter-type-label';
    map.setMaxZoom(fitZoom + 3);
        label.textContent = type;
    map.setMaxBounds(bounds);


        var list = document.createElement('div');
    map.fitBounds(bounds, {
         list.className = 'eqoa-map-filter-list';
         animate: false,
         list.style.display = 'none';
         padding: [0, 0]
    });


        header.appendChild(expand);
    repairEqoaMobileMapImage();
        header.appendChild(typeCheck);
    updateMapDragging();
        header.appendChild(label);
}
        group.appendChild(header);
        group.appendChild(list);


        markersByType[type].forEach(function (entry) {
function updateMapDragging() {
            var row = document.createElement('label');
    var minZoom = map.getMinZoom();
            row.className = 'eqoa-map-filter-row';


            var check = document.createElement('input');
    if (map.getZoom() <= minZoom + 0.001) {
            check.type = 'checkbox';
        sizeEqoaMobileMapBox();
            check.checked = false;
        map.invalidateSize();
entry.check = check;


            var nameText = document.createElement('span');
        map.fitBounds(bounds, {
             nameText.textContent = entry.name;
            animate: false,
             padding: [0, 0]
        });


            row.appendChild(check);
        repairEqoaMobileMapImage();
            row.appendChild(nameText);
        map.dragging.disable();
            list.appendChild(row);
    } else {
        repairEqoaMobileMapImage();
        map.dragging.enable();
    }
}


check.addEventListener('change', function () {
imageLayer.on('load', function () {
     refreshTypeMarkers();
     sizeEqoaMobileMapBox();
     updateTypeCheck();
     fitEqoaMapToBox();
});
});
        });


function refreshTypeMarkers() {
setTimeout(function () {
     layersByType[type].clearLayers();
     fitEqoaMapToBox();
    repairEqoaMobileMapImage();
}, 50);


    markersByType[type].forEach(function (entry) {
setTimeout(function () {
        if (entry.check && entry.check.checked) {
    fitEqoaMapToBox();
            entry.marker.addTo(layersByType[type]);
    repairEqoaMobileMapImage();
        }
}, 250);
    });
}


function updateTypeCheck() {
setTimeout(function () {
     var checks = list.querySelectorAll('input[type="checkbox"]');
     fitEqoaMapToBox();
     var checked = list.querySelectorAll('input[type="checkbox"]:checked');
     repairEqoaMobileMapImage();
}, 750);


    typeCheck.checked = checked.length > 0;
map.on('zoomend', updateMapDragging);
    typeCheck.indeterminate = checked.length > 0 && checked.length < checks.length;
}


        expand.addEventListener('click', function (event) {
window.addEventListener('resize', function () {
            event.preventDefault();
    setTimeout(function () {
            event.stopPropagation();
        fitEqoaMapToBox();
        repairEqoaMobileMapImage();
    }, 100);


            var isOpen = list.style.display !== 'none';
    setTimeout(function () {
        fitEqoaMapToBox();
        repairEqoaMobileMapImage();
    }, 300);
});


            list.style.display = isOpen ? 'none' : 'block';
window.addEventListener('orientationchange', function () {
            expand.textContent = isOpen ? '+' : '-';
     setTimeout(fitEqoaMapToBox, 250);
        });
     setTimeout(fitEqoaMapToBox, 600);
 
typeCheck.addEventListener('change', function () {
     var checks = list.querySelectorAll('input[type="checkbox"]');
 
     Array.prototype.forEach.call(checks, function (check) {
        check.checked = typeCheck.checked;
    });
 
    typeCheck.indeterminate = false;
    refreshTypeMarkers();
});
});


        panel.appendChild(group);
function getEqoaMapTooltipDirection(x, y) {
     });
     var edgePadding = 120;


var closeTimer;
    if (y < edgePadding) return 'bottom';
    if (y > height - edgePadding) return 'top';
    if (x < edgePadding) return 'right';
    if (x > width - edgePadding) return 'left';


function openFilterPanel() {
     return 'top';
     clearTimeout(closeTimer);
    wrapper.classList.add('eqoa-map-filter-open');
}
}


function closeFilterPanelSoon() {
function getEqoaMapTooltipOffset(direction) {
     clearTimeout(closeTimer);
     if (direction === 'bottom') return [0, 18];
     closeTimer = setTimeout(function () {
     if (direction === 'top') return [0, -18];
        wrapper.classList.remove('eqoa-map-filter-open');
    if (direction === 'left') return [-18, 0];
     }, 500);
    if (direction === 'right') return [18, 0];
 
     return [0, -18];
}
}


button.addEventListener('mouseenter', openFilterPanel);
        points.forEach(function (point) {
panel.addEventListener('mouseenter', openFilterPanel);
var type = point.getAttribute('data-type') || 'Marker';
var namesRaw = point.getAttribute('data-names') || point.getAttribute('data-name') || 'Unknown';
var pagesRaw = point.getAttribute('data-pages') || point.getAttribute('data-page') || namesRaw;


wrapper.addEventListener('mouseleave', closeFilterPanelSoon);
var names = namesRaw.split(',').map(function (name) {
panel.addEventListener('mouseleave', closeFilterPanelSoon);
    return name.trim();
}).filter(Boolean);


button.addEventListener('click', function (event) {
var pages = pagesRaw.split(',').map(function (page) {
     event.preventDefault();
     return page.trim();
    event.stopPropagation();
}).filter(Boolean);
    wrapper.classList.toggle('eqoa-map-filter-open');
});


wrapper.appendChild(button);
var name = names.join(' / ');
wrapper.appendChild(panel);
var page = pages[0] || names[0] || 'Unknown';
var level = point.getAttribute('data-level') || '';
var x = parseFloat(point.getAttribute('data-x'));
var y = parseFloat(point.getAttribute('data-y'));
            var marker;
            var label;


return wrapper;
            if (isNaN(x) || isNaN(y)) return;
};


filterControl.addTo(map);
    });
}


function getEqoaMapMarkerColor(type) {
    type = String(type || '').toLowerCase();


    if (type === 'npc') return '#3fcf5f';
tooltipDirection = getEqoaMapTooltipDirection(x, y);
    if (type === 'quest') return '#ffd84d';
    if (type === 'enemy') return '#e85b5b';
    if (type === 'named') return '#b51f1f';
    if (type === 'camps') return '#22d3ee';
    if (type === 'camp') return '#22d3ee';
if (type === 'charm pet') return '#d946ef';
if (type === 'charm pets') return '#d946ef';
if (type === 'charmpet') return '#d946ef';
if (type === 'charmpets') return '#d946ef';


     return '#f2ab31';
if (!layersByType[type]) {
     layersByType[type] = L.layerGroup().addTo(map);
    markersByType[type] = [];
}
}


function getEqoaMapMarkerOutlineColor(type) {
            label = '<b>' + name + '</b>';
    type = String(type || '').toLowerCase();
            if (level) label += '<br>Level: ' + level;
            label += '<br>Type: ' + type;


    if (type === 'npc') return '#0d4f24';
marker = L.circleMarker([y, x], {
     if (type === 'quest') return '#7a5100';
     radius: 4,
     if (type === 'enemy') return '#5c1010';
     color: getEqoaMapMarkerOutlineColor(type),
     if (type === 'named') return '#3a0505';
     fillColor: getEqoaMapMarkerColor(type),
     if (type === 'camps') return '#0e7490';
     fillOpacity: 0.95,
     if (type === 'camp') return '#0e7490';
     weight: 2,
if (type === 'charm pet') return '#86198f';
    className: 'eqoa-map-dot'
if (type === 'charm pets') return '#86198f';
});
if (type === 'charmpet') return '#86198f';
if (type === 'charmpets') return '#86198f';


    return '#3a2508';
var typeLower = String(type || '').toLowerCase();
}
var isCamp = typeLower === 'camp' || typeLower === 'camps';
var hasMultiplePages = names.length > 1;


mw.hook('wikipage.content').add(initEqoaInteractiveMaps);
if (hasMultiplePages) {
document.addEventListener('DOMContentLoaded', initEqoaInteractiveMaps);
    label = '<b>Shared Spawn</b>';
setTimeout(initEqoaInteractiveMaps, 500);
    if (level) label += '<br>Level: ' + level;
setTimeout(initEqoaInteractiveMaps, 1500);
    label += '<br>Type: ' + type;
    label += '<br>';


    names.forEach(function (npcName, index) {
        var npcPage = pages[index] || npcName;
        var href = window.mw && mw.util && mw.util.getUrl
            ? mw.util.getUrl(npcPage)
            : '/index.php/' + encodeURIComponent(npcPage.replace(/ /g, '_'));


        label += '<br><a href="' + href + '">' + npcName + '</a>';
    });


marker.bindPopup(label, {
    closeButton: true,
    autoClose: false,
    closeOnClick: false,
    autoPan: true,
    keepInView: true,
    autoPanPaddingTopLeft: [24, 80],
    autoPanPaddingBottomRight: [24, 24],
    offset: tooltipDirection === 'bottom' ? [0, 22] : [0, -8],
    className: 'eqoa-map-popup'
});


var sharedPopupPinned = false;
var sharedPopupCloseTimer;


marker.on('mouseover', function () {
    clearTimeout(sharedPopupCloseTimer);
    marker.openPopup();
});


marker.on('mouseout', function () {
    clearTimeout(sharedPopupCloseTimer);


document.addEventListener('DOMContentLoaded', eqoaBuildSidebarAccountButtons);
    sharedPopupCloseTimer = setTimeout(function () {
mw.hook('wikipage.content').add(eqoaBuildSidebarAccountButtons);
        if (!sharedPopupPinned) {
setTimeout(eqoaBuildSidebarAccountButtons, 250);
            marker.closePopup();
setTimeout(eqoaBuildSidebarAccountButtons, 1000);
        }
setTimeout(eqoaBuildSidebarAccountButtons, 2000);
    }, 350);
});


/* =========================================================
marker.on('click', function () {
  FLAGGEDREVS ACCEPT BUTTON FIX
    sharedPopupPinned = true;
  Fixes broken review.js Accept revision button for DevRenodor.
    marker.openPopup();
  Does not grant permissions. It only uses the normal review API.
});
  ========================================================= */


function eqoaFixFlaggedRevsAcceptButton() {
marker.on('popupclose', function () {
     if (!window.mw || !mw.Api || mw.config.get('wgUserName') !== 'DevRenodor') {
     sharedPopupPinned = false;
        return;
});
     }
} else if (isCamp) {
marker.bindTooltip(label, {
    direction: tooltipDirection,
    sticky: false,
    offset: getEqoaMapTooltipOffset(tooltipDirection),
    opacity: 1
});
} else {
marker.bindTooltip(label + '<br><span style="color:#ffd45a;">Click to open page</span>', {
    direction: tooltipDirection,
    sticky: false,
    offset: getEqoaMapTooltipOffset(tooltipDirection),
     opacity: 1
});


     var acceptButton = document.getElementById('mw-fr-submit-accept');
     marker.on('click', function () {
        if (window.mw && mw.util && mw.util.getUrl) {
            window.location.href = mw.util.getUrl(page);
        } else {
            window.location.href = '/index.php/' + encodeURIComponent(page.replace(/ /g, '_'));
        }
    });
}


    if (!acceptButton || acceptButton.getAttribute('data-eqoa-fixed') === '1') {
/*
        return;
marker.addTo(layersByType[type]);
    }


    acceptButton.setAttribute('data-eqoa-fixed', '1');
*/


    acceptButton.addEventListener('click', function (event) {
markersByType[type].push({
        var api;
    marker: marker,
        var form;
    name: name,
        var oldidInput;
    type: type
        var commentInput;
});       });
        var revid;
        var comment;


        event.preventDefault();
var filterControl = L.control({
        event.stopPropagation();
    position: 'topright'
        event.stopImmediatePropagation();
});


        form = acceptButton.closest('form');
filterControl.onAdd = function () {
        oldidInput = document.getElementById('mw-fr-input-oldid');
    var wrapper = L.DomUtil.create('div', 'eqoa-map-filter-control');
        commentInput = document.getElementById('mw-fr-commentbox');
    var button = document.createElement('button');
    var panel = document.createElement('div');


        revid = oldidInput && oldidInput.value
    button.type = 'button';
            ? oldidInput.value
    button.className = 'eqoa-map-filter-icon';
            : mw.config.get('wgRevisionId');
    button.setAttribute('aria-label', 'Map filters');
button.innerHTML =
    '<span class="eqoa-map-layer-icon">' +
        '<span></span><span></span><span></span>' +
    '</span>';


        comment = commentInput && commentInput.value
    panel.className = 'eqoa-map-filter-panel';
            ? commentInput.value
            : 'Approved by DevRenodor';


        if (!revid) {
    L.DomEvent.disableClickPropagation(wrapper);
            alert('Could not find revision ID to approve.');
    L.DomEvent.disableScrollPropagation(wrapper);
            return;
        }


         acceptButton.disabled = true;
    Object.keys(markersByType).sort().forEach(function (type) {
         acceptButton.value = 'Approving...';
         var group = document.createElement('div');
         group.className = 'eqoa-map-filter-group';


         api = new mw.Api();
         var header = document.createElement('div');
        header.className = 'eqoa-map-filter-header';


         api.get({
         var expand = document.createElement('button');
            action: 'query',
         expand.type = 'button';
            meta: 'tokens',
         expand.className = 'eqoa-map-filter-expand';
            type: 'csrf',
         expand.textContent = '+';
            format: 'json'
         }).then(function (tokenData) {
            return api.post({
                action: 'review',
                revid: revid,
                token: tokenData.query.tokens.csrftoken,
                flag_accuracy: 1,
                comment: comment,
                format: 'json'
            });
         }).then(function () {
            acceptButton.value = 'Approved';
            window.location.reload();
         }).catch(function (error) {
            console.error('EQOA FlaggedRevs approve failed:', error);


            acceptButton.disabled = false;
        var typeCheck = document.createElement('input');
            acceptButton.value = 'Accept revision';
        typeCheck.type = 'checkbox';
        typeCheck.checked = false;


            alert('Review failed. Check console for EQOA FlaggedRevs approve failed.');
        typeCheck.className = 'eqoa-map-filter-type-check';
        });
    }, true);
}


if (document.readyState === 'loading') {
        var label = document.createElement('span');
    document.addEventListener('DOMContentLoaded', eqoaFixFlaggedRevsAcceptButton);
        label.className = 'eqoa-map-filter-type-label';
} else {
        label.textContent = type;
    eqoaFixFlaggedRevsAcceptButton();
}


mw.hook('wikipage.content').add(eqoaFixFlaggedRevsAcceptButton);
        var list = document.createElement('div');
        list.className = 'eqoa-map-filter-list';
        list.style.display = 'none';


setTimeout(eqoaFixFlaggedRevsAcceptButton, 250);
        header.appendChild(expand);
setTimeout(eqoaFixFlaggedRevsAcceptButton, 1000);
        header.appendChild(typeCheck);
setTimeout(eqoaFixFlaggedRevsAcceptButton, 2000);
        header.appendChild(label);
        group.appendChild(header);
        group.appendChild(list);


        markersByType[type].forEach(function (entry) {
            var row = document.createElement('label');
            row.className = 'eqoa-map-filter-row';


/* =========================================================
            var check = document.createElement('input');
  EQOA ZONE RELATED TABS
            check.type = 'checkbox';
  Turns zone page sections into Wowhead-style tabs.
            check.checked = false;
  No Screenshots tab.
entry.check = check;
  ========================================================= */


function initEqoaZoneTabs() {
            var nameText = document.createElement('span');
    var tabSections = [
            nameText.textContent = entry.name;
        { id: 'quests', label: 'Quests' },
        { id: 'npcs', label: 'NPCs Found Here' },
        { id: 'rare', label: 'Rare Monsters' },
        { id: 'regular', label: 'Regular Monsters' },
        { id: 'items', label: 'Items Found Here' }
    ];


    var headings = [];
            row.appendChild(check);
            row.appendChild(nameText);
            list.appendChild(row);


    function getHeadingById(id) {
check.addEventListener('change', function () {
         var el = document.getElementById(id);
    refreshTypeMarkers();
    updateTypeCheck();
});
         });


        if (!el) {
function refreshTypeMarkers() {
            return null;
    layersByType[type].clearLayers();
        }


         if (/^h2$/i.test(el.tagName)) {
    markersByType[type].forEach(function (entry) {
             return el;
         if (entry.check && entry.check.checked) {
             entry.marker.addTo(layersByType[type]);
         }
         }
    });
}


        return el.closest('h2');
function updateTypeCheck() {
     }
    var checks = list.querySelectorAll('input[type="checkbox"]');
     var checked = list.querySelectorAll('input[type="checkbox"]:checked');


     tabSections.forEach(function (section) {
     typeCheck.checked = checked.length > 0;
        var heading = getHeadingById(section.id);
    typeCheck.indeterminate = checked.length > 0 && checked.length < checks.length;
}


         if (heading) {
         expand.addEventListener('click', function (event) {
             headings.push({
             event.preventDefault();
                id: section.id,
            event.stopPropagation();
                label: section.label,
                heading: heading
            });
        }
    });


    if (!headings.length) {
            var isOpen = list.style.display !== 'none';
        return;
    }


    if (document.querySelector('.eqoa-zone-tabs')) {
            list.style.display = isOpen ? 'none' : 'block';
         return;
            expand.textContent = isOpen ? '+' : '-';
    }
         });


    var wrapper = document.createElement('div');
typeCheck.addEventListener('change', function () {
     wrapper.className = 'eqoa-zone-tabs';
     var checks = list.querySelectorAll('input[type="checkbox"]');


     var title = document.createElement('div');
     Array.prototype.forEach.call(checks, function (check) {
    title.className = 'eqoa-zone-tabs-title';
        check.checked = typeCheck.checked;
     title.textContent = 'Related';
     });


     var nav = document.createElement('div');
     typeCheck.indeterminate = false;
    nav.className = 'eqoa-zone-tabs-nav';
    refreshTypeMarkers();
});


    var body = document.createElement('div');
        panel.appendChild(group);
     body.className = 'eqoa-zone-tabs-body';
     });


    wrapper.appendChild(title);
var closeTimer;
    wrapper.appendChild(nav);
    wrapper.appendChild(body);


     headings[0].heading.parentNode.insertBefore(wrapper, headings[0].heading);
function openFilterPanel() {
    clearTimeout(closeTimer);
     wrapper.classList.add('eqoa-map-filter-open');
}


    headings.forEach(function (section, index) {
function closeFilterPanelSoon() {
        var button = document.createElement('button');
    clearTimeout(closeTimer);
         var panel = document.createElement('div');
    closeTimer = setTimeout(function () {
        var current = section.heading.nextSibling;
         wrapper.classList.remove('eqoa-map-filter-open');
        var next;
    }, 500);
}


        button.type = 'button';
button.addEventListener('mouseenter', openFilterPanel);
        button.className = 'eqoa-zone-tab-button';
panel.addEventListener('mouseenter', openFilterPanel);
        button.textContent = section.label;
 
        button.setAttribute('data-tab', section.id);
wrapper.addEventListener('mouseleave', closeFilterPanelSoon);
panel.addEventListener('mouseleave', closeFilterPanelSoon);


        panel.className = 'eqoa-zone-tab-panel';
button.addEventListener('click', function (event) {
        panel.setAttribute('data-tab-panel', section.id);
    event.preventDefault();
    event.stopPropagation();
    wrapper.classList.toggle('eqoa-map-filter-open');
});


        if (index === 0) {
wrapper.appendChild(button);
            button.classList.add('is-active');
wrapper.appendChild(panel);
            panel.classList.add('is-active');
        }


        section.heading.parentNode.removeChild(section.heading);
return wrapper;
};


        while (current) {
filterControl.addTo(map);
            next = current.nextSibling;
    });
}


            if (
function getEqoaMapMarkerColor(type) {
                current.nodeType === 1 &&
    type = String(type || '').toLowerCase();
                /^h2$/i.test(current.tagName) &&
                headings.some(function (other) {
                    return other.heading === current;
                })
            ) {
                break;
            }


            panel.appendChild(current);
    if (type === 'npc') return '#3fcf5f';
            current = next;
    if (type === 'quest') return '#ffd84d';
        }
    if (type === 'enemy') return '#e85b5b';
    if (type === 'named') return '#b51f1f';
    if (type === 'camps') return '#22d3ee';
    if (type === 'camp') return '#22d3ee';
if (type === 'charm pet') return '#d946ef';
if (type === 'charm pets') return '#d946ef';
if (type === 'charmpet') return '#d946ef';
if (type === 'charmpets') return '#d946ef';


        function activateTab() {
    return '#f2ab31';
            wrapper.querySelectorAll('.eqoa-zone-tab-button').forEach(function (btn) {
}
                btn.classList.remove('is-active');
            });


            wrapper.querySelectorAll('.eqoa-zone-tab-panel').forEach(function (tabPanel) {
function getEqoaMapMarkerOutlineColor(type) {
                tabPanel.classList.remove('is-active');
    type = String(type || '').toLowerCase();
            });


            button.classList.add('is-active');
    if (type === 'npc') return '#0d4f24';
            panel.classList.add('is-active');
    if (type === 'quest') return '#7a5100';
 
    if (type === 'enemy') return '#5c1010';
            wrapper.scrollIntoView({
    if (type === 'named') return '#3a0505';
                behavior: 'smooth',
    if (type === 'camps') return '#0e7490';
                block: 'start'
    if (type === 'camp') return '#0e7490';
            });
if (type === 'charm pet') return '#86198f';
        }
if (type === 'charm pets') return '#86198f';
if (type === 'charmpet') return '#86198f';
if (type === 'charmpets') return '#86198f';
 
    return '#3a2508';
}


        button.addEventListener('click', function () {
mw.hook('wikipage.content').add(initEqoaInteractiveMaps);
            activateTab();
document.addEventListener('DOMContentLoaded', initEqoaInteractiveMaps);
        });
setTimeout(initEqoaInteractiveMaps, 500);
setTimeout(initEqoaInteractiveMaps, 1500);


        document.querySelectorAll('a[href="#' + section.id + '"]').forEach(function (link) {
            link.addEventListener('click', function (event) {
                event.preventDefault();
                activateTab();


                if (window.history && window.history.pushState) {
                    window.history.pushState(null, '', '#' + section.id);
                }
            });
        });


        panel.id = section.id;


        nav.appendChild(button);
        body.appendChild(panel);
    });
}


mw.hook('wikipage.content').add(initEqoaZoneTabs);
if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initEqoaZoneTabs);
} else {
    initEqoaZoneTabs();
    if (window.location.hash) {
        var hashId = window.location.hash.replace('#', '');
        var hashButton = wrapper.querySelector('.eqoa-zone-tab-button[data-tab="' + hashId + '"]');
        if (hashButton) {
            hashButton.click();
        }
    }
}
setTimeout(initEqoaZoneTabs, 500);
setTimeout(initEqoaZoneTabs, 1500);




document.addEventListener('DOMContentLoaded', eqoaBuildSidebarAccountButtons);
mw.hook('wikipage.content').add(eqoaBuildSidebarAccountButtons);
setTimeout(eqoaBuildSidebarAccountButtons, 250);
setTimeout(eqoaBuildSidebarAccountButtons, 1000);
setTimeout(eqoaBuildSidebarAccountButtons, 2000);


/* =========================================================
/* =========================================================
   EQOA CLASS PAGE TABS
   FLAGGEDREVS ACCEPT BUTTON FIX
   Durable delegated handler for class tabs + URL hashes
   Fixes broken review.js Accept revision button for DevRenodor.
  Does not grant permissions. It only uses the normal review API.
   ========================================================= */
   ========================================================= */


function eqoaActivateClassTab(tabs, tabName, updateUrl) {
function eqoaFixFlaggedRevsAcceptButton() {
     var button;
     if (!window.mw || !mw.Api || mw.config.get('wgUserName') !== 'DevRenodor') {
     var panel;
        return;
     }


     if (!tabs || !tabName) return;
     var acceptButton = document.getElementById('mw-fr-submit-accept');


     button = tabs.querySelector('.eqoa-class-tab-button[data-class-tab="' + tabName + '"]');
     if (!acceptButton || acceptButton.getAttribute('data-eqoa-fixed') === '1') {
    panel = tabs.querySelector('.eqoa-class-tab-panel[data-class-tab-panel="' + tabName + '"]');
        return;
    }


     if (!button || !panel) return;
     acceptButton.setAttribute('data-eqoa-fixed', '1');


     tabs.querySelectorAll('.eqoa-class-tab-button').forEach(function (btn) {
     acceptButton.addEventListener('click', function (event) {
         btn.classList.remove('is-active');
         var api;
    });
        var form;
        var oldidInput;
        var commentInput;
        var revid;
        var comment;


    tabs.querySelectorAll('.eqoa-class-tab-panel').forEach(function (tabPanel) {
        event.preventDefault();
         tabPanel.classList.remove('is-active');
        event.stopPropagation();
    });
         event.stopImmediatePropagation();


    button.classList.add('is-active');
        form = acceptButton.closest('form');
    panel.classList.add('is-active');
        oldidInput = document.getElementById('mw-fr-input-oldid');
        commentInput = document.getElementById('mw-fr-commentbox');


    if (updateUrl && window.history && window.history.pushState) {
        revid = oldidInput && oldidInput.value
        window.history.pushState(null, '', '#' + tabName);
            ? oldidInput.value
    }
            : mw.config.get('wgRevisionId');
}


function initEqoaClassTabs() {
        comment = commentInput && commentInput.value
    document.querySelectorAll('.eqoa-class-tabs').forEach(function (tabs) {
            ? commentInput.value
        var initialTab;
            : 'Approved by DevRenodor';


         if (tabs.getAttribute('data-eqoa-class-tabs-ready') === '1') {
         if (!revid) {
            alert('Could not find revision ID to approve.');
             return;
             return;
         }
         }


         tabs.setAttribute('data-eqoa-class-tabs-ready', '1');
         acceptButton.disabled = true;
        acceptButton.value = 'Approving...';


         initialTab = window.location.hash
         api = new mw.Api();
            ? window.location.hash.replace('#', '')
            : 'races';


         if (!tabs.querySelector('.eqoa-class-tab-button[data-class-tab="' + initialTab + '"]')) {
         api.get({
             initialTab = 'races';
            action: 'query',
        }
            meta: 'tokens',
 
            type: 'csrf',
        eqoaActivateClassTab(tabs, initialTab, false);
            format: 'json'
    });
        }).then(function (tokenData) {
}
             return api.post({
 
                action: 'review',
document.addEventListener('click', function (event) {
                revid: revid,
    var button = event.target.closest('.eqoa-class-tab-button');
                token: tokenData.query.tokens.csrftoken,
    var tabs;
                flag_accuracy: 1,
                comment: comment,
                format: 'json'
            });
        }).then(function () {
            acceptButton.value = 'Approved';
            window.location.reload();
        }).catch(function (error) {
            console.error('EQOA FlaggedRevs approve failed:', error);
 
            acceptButton.disabled = false;
            acceptButton.value = 'Accept revision';


     if (!button) return;
            alert('Review failed. Check console for EQOA FlaggedRevs approve failed.');
        });
     }, true);
}


     tabs = button.closest('.eqoa-class-tabs');
if (document.readyState === 'loading') {
     document.addEventListener('DOMContentLoaded', eqoaFixFlaggedRevsAcceptButton);
} else {
    eqoaFixFlaggedRevsAcceptButton();
}


    if (!tabs) return;
mw.hook('wikipage.content').add(eqoaFixFlaggedRevsAcceptButton);


    event.preventDefault();
setTimeout(eqoaFixFlaggedRevsAcceptButton, 250);
    event.stopPropagation();
setTimeout(eqoaFixFlaggedRevsAcceptButton, 1000);
setTimeout(eqoaFixFlaggedRevsAcceptButton, 2000);


    eqoaActivateClassTab(tabs, button.getAttribute('data-class-tab'), true);
});


document.addEventListener('click', function (event) {
/* =========================================================
     var link = event.target.closest('a[href^="#"]');
  EQOA ZONE RELATED TABS
     var tabs;
  Turns zone page sections into Wowhead-style tabs.
     var tabName;
  No Screenshots tab.
  ========================================================= */
 
function initEqoaZoneTabs() {
    var tabSections = [
        { id: 'quests', label: 'Quests' },
        { id: 'npcs', label: 'NPCs' },
        { id: 'items', label: 'Drops' },
        { id: 'achievements', label: 'Achievements' }
    ];
 
     var headings = [];
     var activators = {};
     var initialHash = window.location.hash ? window.location.hash.replace('#', '') : '';


     if (!link) return;
     function getHeadingById(id) {
        var el = document.getElementById(id);


    tabName = link.getAttribute('href').replace('#', '');
        if (!el) {
    tabs = document.querySelector('.eqoa-class-tabs');
            return null;
        }


    if (!tabs) return;
        if (/^h2$/i.test(el.tagName)) {
            return el;
        }


    if (!tabs.querySelector('.eqoa-class-tab-button[data-class-tab="' + tabName + '"]')) {
        return el.closest('h2');
        return;
     }
     }


     event.preventDefault();
     tabSections.forEach(function (section) {
 
        var heading = getHeadingById(section.id);
    eqoaActivateClassTab(tabs, tabName, true);


    tabs.scrollIntoView({
        if (heading) {
        behavior: 'smooth',
            headings.push({
         block: 'start'
                id: section.id,
                label: section.label,
                heading: heading
            });
         }
     });
     });
});


window.addEventListener('hashchange', function () {
    if (!headings.length) {
     var tabs = document.querySelector('.eqoa-class-tabs');
        return;
     var tabName = window.location.hash.replace('#', '');
    }
 
     if (document.querySelector('.eqoa-zone-tabs')) {
        return;
     }


     if (!tabs || !tabName) return;
     var wrapper = document.createElement('div');
    wrapper.className = 'eqoa-zone-tabs';


     eqoaActivateClassTab(tabs, tabName, false);
     var title = document.createElement('div');
});
    title.className = 'eqoa-zone-tabs-title';
    title.textContent = 'Related';


mw.hook('wikipage.content').add(initEqoaClassTabs);
    var nav = document.createElement('div');
    nav.className = 'eqoa-zone-tabs-nav';


if (document.readyState === 'loading') {
    var body = document.createElement('div');
     document.addEventListener('DOMContentLoaded', initEqoaClassTabs);
     body.className = 'eqoa-zone-tabs-body';
} else {
    initEqoaClassTabs();
}


setTimeout(initEqoaClassTabs, 250);
    wrapper.appendChild(title);
setTimeout(initEqoaClassTabs, 1000);
    wrapper.appendChild(nav);
setTimeout(initEqoaClassTabs, 2000);
    wrapper.appendChild(body);


mw.hook('wikipage.content').add(initEqoaClassTabs);
    headings[0].heading.parentNode.insertBefore(wrapper, headings[0].heading);


if (document.readyState === 'loading') {
    headings.forEach(function (section, index) {
    document.addEventListener('DOMContentLoaded', initEqoaClassTabs);
        var button = document.createElement('button');
} else {
        var panel = document.createElement('div');
    initEqoaClassTabs();
        var current = section.heading.nextSibling;
}
        var next;


setTimeout(initEqoaClassTabs, 250);
        button.type = 'button';
setTimeout(initEqoaClassTabs, 1000);
        button.className = 'eqoa-zone-tab-button';
setTimeout(initEqoaClassTabs, 2000);
        button.textContent = section.label;
        button.setAttribute('data-tab', section.id);


        panel.className = 'eqoa-zone-tab-panel';
        panel.setAttribute('data-tab-panel', section.id);
        panel.id = section.id;


/* =========================================================
        if (index === 0) {
  EQOA CLASS ITEM LISTS
            button.classList.add('is-active');
  Loads class item tabs from Category:MAG Items, etc.
            panel.classList.add('is-active');
  ========================================================= */
        }


function initClassItemLists() {
        section.heading.parentNode.removeChild(section.heading);
    if (!window.mw || !mw.Api) return;


    document.querySelectorAll('.class-item-list').forEach(function (box) {
        while (current) {
        var classCode = (box.getAttribute('data-class') || '').trim().toUpperCase();
            next = current.nextSibling;
        var loading = box.querySelector('.class-item-list-loading');
        var table = box.querySelector('.class-item-table');


        if (!classCode || box.classList.contains('class-item-list-ready')) return;
            if (
                current.nodeType === 1 &&
                /^h2$/i.test(current.tagName) &&
                headings.some(function (other) {
                    return other.heading === current;
                })
            ) {
                break;
            }


        box.classList.add('class-item-list-ready');
            panel.appendChild(current);
            current = next;
        }


         loadClassCategoryItems(classCode).then(function (items) {
         function activateTab(pushHash) {
             renderClassItemList(box, table, loading, items);
             wrapper.querySelectorAll('.eqoa-zone-tab-button').forEach(function (btn) {
        }).catch(function (error) {
                btn.classList.remove('is-active');
            console.error('Class item list failed:', error);
             });
             if (loading) loading.textContent = 'Failed to load class items.';
 
        });
            wrapper.querySelectorAll('.eqoa-zone-tab-panel').forEach(function (tabPanel) {
    });
                tabPanel.classList.remove('is-active');
}
            });


function loadClassCategoryItems(classCode) {
            button.classList.add('is-active');
    var api = new mw.Api();
            panel.classList.add('is-active');
    var category = 'Category:' + classCode + ' Items';
    var pages = [];


    function getBatch(cmcontinue) {
            if (pushHash && window.history && window.history.pushState) {
        var request = {
                window.history.pushState(null, '', '#' + section.id);
            action: 'query',
             }
            list: 'categorymembers',
            cmtitle: category,
            cmnamespace: 0,
            cmlimit: 'max',
             format: 'json'
        };


        if (cmcontinue) {
            wrapper.scrollIntoView({
             request.cmcontinue = cmcontinue;
                behavior: 'smooth',
                block: 'start'
             });
         }
         }


         return api.get(request).then(function (data) {
         activators[section.id] = activateTab;
            pages = pages.concat(data.query.categorymembers || []);


            if (data.continue && data.continue.cmcontinue) {
        button.addEventListener('click', function () {
                return getBatch(data.continue.cmcontinue);
            activateTab(true);
            }
        });


             return pages;
        document.querySelectorAll('a[href="#' + section.id + '"]').forEach(function (link) {
             link.addEventListener('click', function (event) {
                event.preventDefault();
                activateTab(true);
            });
         });
         });
    }


    return getBatch().then(function (categoryPages) {
        nav.appendChild(button);
         return loadClassItemPageData(categoryPages.map(function (page) {
         body.appendChild(panel);
            return page.title;
        }));
     });
     });
    if (initialHash && activators[initialHash]) {
        activators[initialHash](false);
    }
}
}


function loadClassItemPageData(titles) {
mw.hook('wikipage.content').add(initEqoaZoneTabs);
    var api = new mw.Api();
    var chunks = [];
    var i;


    for (i = 0; i < titles.length; i += 50) {
if (document.readyState === 'loading') {
        chunks.push(titles.slice(i, i + 50));
    document.addEventListener('DOMContentLoaded', initEqoaZoneTabs);
    }
} else {
    initEqoaZoneTabs();
}


    return Promise.all(chunks.map(function (chunk) {
setTimeout(initEqoaZoneTabs, 500);
        return api.get({
setTimeout(initEqoaZoneTabs, 1500);
            action: 'query',
            prop: 'revisions',
            titles: chunk.join('|'),
            rvprop: 'content',
            rvslots: 'main',
            formatversion: '2',
            format: 'json'
        });
    })).then(function (responses) {
        var items = [];


        responses.forEach(function (data) {
            (data.query.pages || []).forEach(function (page) {
                var text = '';


                if (page.revisions && page.revisions[0]) {
/* =========================================================
                    if (page.revisions[0].slots && page.revisions[0].slots.main) {
  EQOA CLASS PAGE TABS
                        text = page.revisions[0].slots.main.content || '';
  Durable delegated handler for class tabs + URL hashes
                    } else {
  ========================================================= */
                        text = page.revisions[0].content || '';
                    }
                }


                items.push(parseClassItemPage(page.title, text));
function eqoaActivateClassTab(tabs, tabName, updateUrl) {
            });
    var button;
        });
    var panel;


        items.sort(function (a, b) {
    if (!tabs || !tabName) return;
            if (a.level !== b.level) return a.level - b.level;
 
            if (a.slot !== b.slot) return a.slot.localeCompare(b.slot);
    button = tabs.querySelector('.eqoa-class-tab-button[data-class-tab="' + tabName + '"]');
            return a.name.localeCompare(b.name);
    panel = tabs.querySelector('.eqoa-class-tab-panel[data-class-tab-panel="' + tabName + '"]');
        });
 
    if (!button || !panel) return;
 
    tabs.querySelectorAll('.eqoa-class-tab-button').forEach(function (btn) {
        btn.classList.remove('is-active');
    });


         return items;
    tabs.querySelectorAll('.eqoa-class-tab-panel').forEach(function (tabPanel) {
         tabPanel.classList.remove('is-active');
     });
     });
}


function parseClassItemPage(title, text) {
    button.classList.add('is-active');
     function field(name) {
    panel.classList.add('is-active');
         var match = text.match(new RegExp('\\|' + name + '\\s*=\\s*([^\\n]*)', 'i'));
 
        return match ? match[1].trim() : '';
     if (updateUrl && window.history && window.history.pushState) {
         window.history.pushState(null, '', '#' + tabName);
     }
     }
}


    var name = field('name') || title;
function initEqoaClassTabs() {
    var rawSlot = field('slot') || '';
     document.querySelectorAll('.eqoa-class-tabs').forEach(function (tabs) {
    var slot = normalizeClassItemSlot(rawSlot);
        var initialTab;
    var level = parseInt(field('lvl'), 10) || 0;
    var droppedby = cleanClassItemSource(field('droppedby'));
     var zone = cleanClassItemSource(field('zone'));
    var quests = cleanClassItemSource(field('quests'));
    var notes = cleanClassItemSource(field('notes'));


    return {
         if (tabs.getAttribute('data-eqoa-class-tabs-ready') === '1') {
         name: name,
            return;
        slot: slot,
        }
        level: level,
        droppedby: droppedby,
        zone: zone,
        quests: quests,
        notes: notes,
        type: getClassItemType(name, slot, droppedby, zone, quests, notes),
        source: getClassItemSource(droppedby, zone, quests, notes)
    };
}


function normalizeClassItemSlot(slot) {
        tabs.setAttribute('data-eqoa-class-tabs-ready', '1');
    slot = String(slot || '').trim();


    if (!slot) return '';
        initialTab = window.location.hash
            ? window.location.hash.replace('#', '')
            : 'races';


    if (/^\d+$/.test(slot)) return '';
        if (!tabs.querySelector('.eqoa-class-tab-button[data-class-tab="' + initialTab + '"]')) {
            initialTab = 'races';
        }


    if (slot === 'Helm') return 'Head';
        eqoaActivateClassTab(tabs, initialTab, false);
    if (slot === 'Helmet') return 'Head';
     });
    if (slot === 'Necklace') return 'Neck';
     if (slot === 'Earring') return 'Ear';
    if (slot === 'Leggings') return 'Legs';
    if (slot === 'Bow') return 'Range';
    if (slot === 'Thrown') return 'Range';
 
    return slot;
}
}


function cleanClassItemSource(value) {
document.addEventListener('click', function (event) {
     value = String(value || '').trim();
     var button = event.target.closest('.eqoa-class-tab-button');
    var tabs;


     if (!value) return '';
     if (!button) return;
    if (value.toLowerCase() === 'unknown') return '';
    if (value.toLowerCase() === 'none') return '';
    if (value.toLowerCase() === 'no screenshots uploaded.') return '';
    if (value.toLowerCase() === 'no additional lore available.') return '';


     return value;
     tabs = button.closest('.eqoa-class-tabs');
}


function getClassItemSource(droppedby, zone, quests, notes) {
    if (!tabs) return;
    var parts = [];


     if (droppedby) parts.push(droppedby);
     event.preventDefault();
     if (zone) parts.push(zone);
     event.stopPropagation();


     if (quests && quests !== 'None') {
     eqoaActivateClassTab(tabs, button.getAttribute('data-class-tab'), true);
        parts.push('Quest: ' + quests);
});
    }


    if (!parts.length && notes && /quest reward/i.test(notes)) {
document.addEventListener('click', function (event) {
        parts.push('Quest Reward');
    var link = event.target.closest('a[href^="#"]');
     }
     var tabs;
    var tabName;


     return parts.join('<br>');
     if (!link) return;
}


function getClassItemType(name, slot, droppedby, zone, quests, notes) {
    tabName = link.getAttribute('href').replace('#', '');
     var text = [
     tabs = document.querySelector('.eqoa-class-tabs');
        name,
        slot,
        droppedby,
        zone,
        quests,
        notes
    ].join(' ').toLowerCase();


     if (text.indexOf('recipe') !== -1) return 'Recipe';
     if (!tabs) return;
    if (text.indexOf('quest') !== -1 || text.indexOf('reward') !== -1) return 'Quest';
    if (text.indexOf('clicky') !== -1 || text.indexOf('clickies') !== -1 || text.indexOf('effect:') !== -1) return 'Clickies';


     if (slot) return slot;
     if (!tabs.querySelector('.eqoa-class-tab-button[data-class-tab="' + tabName + '"]')) {
        return;
    }


     return 'Miscellaneous';
     event.preventDefault();
}


function renderClassItemList(box, table, loading, items) {
    eqoaActivateClassTab(tabs, tabName, true);
    var tbody;
    var controls;
    var searchInput;
    var filterBox;
    var activeFilter = 'All';


     if (!table) return;
     tabs.scrollIntoView({
        behavior: 'smooth',
        block: 'start'
    });
});


    tbody = table.querySelector('tbody') || table;
window.addEventListener('hashchange', function () {
     controls = box.querySelector('.class-item-controls');
     var tabs = document.querySelector('.eqoa-class-tabs');
     var searchHolder = box.querySelector('.class-item-search-holder');
     var tabName = window.location.hash.replace('#', '');


var searchHolder = box.querySelector('.class-item-search-holder');
    if (!tabs || !tabName) return;


searchInput = box.querySelector('.class-item-search');
    eqoaActivateClassTab(tabs, tabName, false);
});


if (!searchInput && searchHolder) {
mw.hook('wikipage.content').add(initEqoaClassTabs);
    searchInput = document.createElement('input');
    searchInput.className = 'class-item-search';
    searchInput.type = 'search';
    searchInput.placeholder = 'Search items';
    searchHolder.appendChild(searchInput);
}
    filterBox = box.querySelector('.class-item-filter-buttons');


    if (loading) {
if (document.readyState === 'loading') {
        loading.style.display = 'none';
    document.addEventListener('DOMContentLoaded', initEqoaClassTabs);
     }
} else {
     initEqoaClassTabs();
}


    if (controls) {
setTimeout(initEqoaClassTabs, 250);
        controls.style.display = '';
setTimeout(initEqoaClassTabs, 1000);
    }
setTimeout(initEqoaClassTabs, 2000);


    table.style.display = '';
mw.hook('wikipage.content').add(initEqoaClassTabs);


    buildClassItemFilters(filterBox, items, activeFilter, function (filterName) {
if (document.readyState === 'loading') {
        activeFilter = filterName;
    document.addEventListener('DOMContentLoaded', initEqoaClassTabs);
        drawClassItemRows(tbody, items, activeFilter, searchInput ? searchInput.value : '');
} else {
     });
     initEqoaClassTabs();
}


    if (searchInput) {
setTimeout(initEqoaClassTabs, 250);
        searchInput.addEventListener('input', function () {
setTimeout(initEqoaClassTabs, 1000);
            drawClassItemRows(tbody, items, activeFilter, searchInput.value);
setTimeout(initEqoaClassTabs, 2000);
        });
    }


    drawClassItemRows(tbody, items, activeFilter, '');
}


function buildClassItemFilters(filterBox, items, activeFilter, onFilterClick) {
/* =========================================================
    var orderedFilters = [
  EQOA CLASS ITEM LISTS
        'All',
  Loads class item tabs from Category:MAG Items, etc.
        'Head',
  ========================================================= */
        'Chest',
        'Robe',
        'Hands',
        'Legs',
        'Feet',
        'Forearm',
        'Bracelet',
        'Waist',
        'Neck',
        'Ear',
        'Ring',
        'Primary',
        'Secondary',
        'Shield',
        'Held',
        'Range',
        'Recipe',
        'Quest',
        'Clickies',
        'Miscellaneous'
    ];


     var counts = {};
function initClassItemLists() {
     if (!window.mw || !mw.Api) return;


     if (!filterBox) return;
     document.querySelectorAll('.class-item-list').forEach(function (box) {
 
         var classCode = (box.getAttribute('data-class') || '').trim().toUpperCase();
    items.forEach(function (item) {
         var loading = box.querySelector('.class-item-list-loading');
         counts.All = (counts.All || 0) + 1;
        var table = box.querySelector('.class-item-table');
         counts[item.type] = (counts[item.type] || 0) + 1;
    });


    filterBox.innerHTML = '';
        if (!classCode || box.classList.contains('class-item-list-ready')) return;


    orderedFilters.forEach(function (filterName) {
        box.classList.add('class-item-list-ready');
        var button;


         if (filterName !== 'All' && !counts[filterName]) return;
         loadClassCategoryItems(classCode).then(function (items) {
            renderClassItemList(box, table, loading, items);
        }).catch(function (error) {
            console.error('Class item list failed:', error);
            if (loading) loading.textContent = 'Failed to load class items.';
        });
    });
}


        button = document.createElement('button');
function loadClassCategoryItems(classCode) {
        button.type = 'button';
    var api = new mw.Api();
        button.className = 'class-item-filter-button';
    var category = 'Category:' + classCode + ' Items';
    var pages = [];


         if (filterName === activeFilter) {
    function getBatch(cmcontinue) {
             button.className += ' is-active';
         var request = {
         }
             action: 'query',
            list: 'categorymembers',
            cmtitle: category,
            cmnamespace: 0,
            cmlimit: 'max',
            format: 'json'
         };


         button.textContent = filterName + ' (' + (counts[filterName] || 0) + ')';
         if (cmcontinue) {
            request.cmcontinue = cmcontinue;
        }
 
        return api.get(request).then(function (data) {
            pages = pages.concat(data.query.categorymembers || []);


        button.addEventListener('click', function () {
            if (data.continue && data.continue.cmcontinue) {
            filterBox.querySelectorAll('.class-item-filter-button').forEach(function (btn) {
                 return getBatch(data.continue.cmcontinue);
                 btn.classList.remove('is-active');
             }
             });


             button.classList.add('is-active');
             return pages;
            onFilterClick(filterName);
         });
         });
    }


         filterBox.appendChild(button);
    return getBatch().then(function (categoryPages) {
         return loadClassItemPageData(categoryPages.map(function (page) {
            return page.title;
        }));
     });
     });
}
}


function drawClassItemRows(tbody, items, activeFilter, searchText) {
function loadClassItemPageData(titles) {
     var filtered;
     var api = new mw.Api();
     var query = String(searchText || '').toLowerCase();
     var chunks = [];
    var i;


     tbody.innerHTML = '';
     for (i = 0; i < titles.length; i += 50) {
        chunks.push(titles.slice(i, i + 50));
    }


     filtered = items.filter(function (item) {
     return Promise.all(chunks.map(function (chunk) {
         var haystack;
        return api.get({
            action: 'query',
            prop: 'revisions',
            titles: chunk.join('|'),
            rvprop: 'content',
            rvslots: 'main',
            formatversion: '2',
            format: 'json'
        });
    })).then(function (responses) {
         var items = [];


         if (activeFilter && activeFilter !== 'All' && item.type !== activeFilter) {
         responses.forEach(function (data) {
            return false;
            (data.query.pages || []).forEach(function (page) {
        }
                var text = '';


        if (!query) return true;
                if (page.revisions && page.revisions[0]) {
                    if (page.revisions[0].slots && page.revisions[0].slots.main) {
                        text = page.revisions[0].slots.main.content || '';
                    } else {
                        text = page.revisions[0].content || '';
                    }
                }


        haystack = [
                items.push(parseClassItemPage(page.title, text));
            item.name,
             });
             item.slot,
        });
             item.level,
 
             item.source,
        items.sort(function (a, b) {
             item.type
             if (a.level !== b.level) return a.level - b.level;
        ].join(' ').toLowerCase();
             if (a.slot !== b.slot) return a.slot.localeCompare(b.slot);
             return a.name.localeCompare(b.name);
        });


         return haystack.indexOf(query) !== -1;
         return items;
     });
     });
}


     filtered.forEach(function (item) {
function parseClassItemPage(title, text) {
         var row = document.createElement('tr');
     function field(name) {
         var match = text.match(new RegExp('\\|' + name + '\\s*=\\s*([^\\n]*)', 'i'));
        return match ? match[1].trim() : '';
    }


row.innerHTML =
     function numberField(name) {
     '<td><a class="class-item-tooltip-link" data-class-item-name="' + encodeURIComponent(item.name) + '" href="' + mw.util.getUrl(item.name) + '">' + escapeClassItemHtml(item.name) + '</a></td>' +
        var value = field(name);
    '<td>' + (item.level || '') + '</td>' +
        var number = parseFloat(value);
    '<td>' + escapeClassItemHtml(item.slot || '') + '</td>' +
    '<td>' + item.source + '</td>';


         tbody.appendChild(row);
         return isNaN(number) ? 0 : number;
bindClassItemTooltip(row, item);
     }
     });


     if (!filtered.length) {
     var name = field('name') || title;
        var emptyRow = document.createElement('tr');
    var rawSlot = field('slot') || '';
        emptyRow.innerHTML = '<td colspan="4">No class items found.</td>';
    var slot = normalizeClassItemSlot(rawSlot);
        tbody.appendChild(emptyRow);
    var level = parseInt(field('lvl'), 10) || 0;
     }
    var droppedby = cleanClassItemSource(field('droppedby'));
}
    var zone = cleanClassItemSource(field('zone'));
    var quests = cleanClassItemSource(field('quests'));
    var notes = cleanClassItemSource(field('notes'));
 
     return {
        name: name,
        rarity: field('rarity'),
        slot: slot,
        level: level,
        race: field('race') || 'ALL',
        className: field('class') || '',
        notrade: field('notrade'),
        lore: field('lore'),


function bindClassItemTooltip(row, item) {
        dmg: numberField('dmg'),
    var link = row.querySelector('.class-item-tooltip-link');
        ac: numberField('ac'),
        str: numberField('str'),
        sta: numberField('sta'),
        agi: numberField('agi'),
        dex: numberField('dex'),
        wis: numberField('wis'),
        int: numberField('int'),
        cha: numberField('cha'),
        hp: numberField('hp'),
        hot: numberField('hot'),
        pow: numberField('pow'),
        pot: numberField('pot'),
        pr: numberField('pr'),
        dr: numberField('dr'),
        fr: numberField('fr'),
        cr: numberField('cr'),
        lr: numberField('lr'),
        ar: numberField('ar'),
        dur: numberField('dur'),
        gearhp: numberField('gearhp'),


    if (!link || !item) return;
        effect: field('effect'),
        proc: field('proc'),
        desc: field('desc'),


    link.addEventListener('mouseenter', function (event) {
        droppedby: droppedby,
         showClassItemTooltip(item, event);
        zone: zone,
     });
        quests: quests,
        notes: notes,
        type: getClassItemType(name, slot, droppedby, zone, quests, notes),
         source: getClassItemSource(droppedby, zone, quests, notes)
     };
}


    link.addEventListener('mousemove', moveClassItemTooltip);


     link.addEventListener('mouseleave', function () {
function normalizeClassItemSlot(slot) {
        hideClassItemTooltip();
     slot = String(slot || '').trim();
     });
 
}
     if (!slot) return '';


function getClassItemTooltipBox() {
    if (/^\d+$/.test(slot)) return '';
    var tooltip = document.getElementById('class-item-tooltip');


     if (!tooltip) {
     if (slot === 'Helm') return 'Head';
        tooltip = document.createElement('div');
    if (slot === 'Helmet') return 'Head';
        tooltip.id = 'class-item-tooltip';
    if (slot === 'Necklace') return 'Neck';
        tooltip.className = 'class-item-tooltip';
    if (slot === 'Earring') return 'Ear';
        tooltip.style.display = 'none';
    if (slot === 'Leggings') return 'Legs';
        document.body.appendChild(tooltip);
    if (slot === 'Bow') return 'Range';
     }
     if (slot === 'Thrown') return 'Range';


     return tooltip;
     return slot;
}
}


function showClassItemTooltip(item, event) {
function cleanClassItemSource(value) {
     var tooltip = getClassItemTooltipBox();
     value = String(value || '').trim();
    var html = '';


     html += '<div class="class-item-tooltip-title">' + escapeClassItemHtml(item.name) + '</div>';
     if (!value) return '';
 
    if (value.toLowerCase() === 'unknown') return '';
     if (item.level) html += '<div><b>Level:</b> ' + item.level + '</div>';
     if (value.toLowerCase() === 'none') return '';
     if (item.slot) html += '<div><b>Slot:</b> ' + escapeClassItemHtml(item.slot) + '</div>';
     if (value.toLowerCase() === 'no screenshots uploaded.') return '';
     if (item.source) html += '<div><b>Source:</b> ' + item.source + '</div>';
     if (value.toLowerCase() === 'no additional lore available.') return '';
 
    return value;
}


    tooltip.innerHTML = html;
function getClassItemSource(droppedby, zone, quests, notes) {
     tooltip.style.display = 'block';
     var parts = [];


     moveClassItemTooltip(event);
     if (droppedby) parts.push(droppedby);
}
    if (zone) parts.push(zone);


function moveClassItemTooltip(event) {
    if (quests && quests !== 'None') {
    var tooltip = getClassItemTooltipBox();
        parts.push('Quest: ' + quests);
     var left = event.pageX + 16;
     }
    var top = event.pageY + 16;


     if (left + 360 > window.scrollX + window.innerWidth) {
     if (!parts.length && notes && /quest reward/i.test(notes)) {
         left = event.pageX - 360;
         parts.push('Quest Reward');
     }
     }


     tooltip.style.left = left + 'px';
     return parts.join('<br>');
    tooltip.style.top = top + 'px';
}
}


function hideClassItemTooltip() {
function getClassItemType(name, slot, droppedby, zone, quests, notes) {
     var tooltip = document.getElementById('class-item-tooltip');
     var text = [
        name,
        slot,
        droppedby,
        zone,
        quests,
        notes
    ].join(' ').toLowerCase();


     if (tooltip) {
     if (text.indexOf('recipe') !== -1) return 'Recipe';
        tooltip.style.display = 'none';
    if (text.indexOf('quest') !== -1 || text.indexOf('reward') !== -1) return 'Quest';
     }
     if (text.indexOf('clicky') !== -1 || text.indexOf('clickies') !== -1 || text.indexOf('effect:') !== -1) return 'Clickies';
}
 
    if (slot) return slot;


function escapeClassItemHtml(value) {
     return 'Miscellaneous';
     return String(value == null ? '' : value)
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#039;');
}
}


mw.hook('wikipage.content').add(initClassItemLists);
function renderClassItemList(box, table, loading, items) {
    var tbody;
    var controls;
    var searchInput;
    var filterBox;
    var activeFilter = 'All';


if (document.readyState === 'loading') {
    if (!table) return;
     document.addEventListener('DOMContentLoaded', initClassItemLists);
 
} else {
    tbody = table.querySelector('tbody') || table;
     initClassItemLists();
    controls = box.querySelector('.class-item-controls');
    var searchHolder = box.querySelector('.class-item-search-holder');
 
var searchHolder = box.querySelector('.class-item-search-holder');
 
searchInput = box.querySelector('.class-item-search');
 
if (!searchInput && searchHolder) {
     searchInput = document.createElement('input');
    searchInput.className = 'class-item-search';
    searchInput.type = 'search';
    searchInput.placeholder = 'Search items';
     searchHolder.appendChild(searchInput);
}
}
    filterBox = box.querySelector('.class-item-filter-buttons');


setTimeout(initClassItemLists, 500);
    if (loading) {
        loading.style.display = 'none';
    }
 
    if (controls) {
        controls.style.display = '';
    }
 
    table.style.display = '';
 
    buildClassItemFilters(filterBox, items, activeFilter, function (filterName) {
        activeFilter = filterName;
        drawClassItemRows(tbody, items, activeFilter, searchInput ? searchInput.value : '');
    });
 
    if (searchInput) {
        searchInput.addEventListener('input', function () {
            drawClassItemRows(tbody, items, activeFilter, searchInput.value);
        });
    }
 
    drawClassItemRows(tbody, items, activeFilter, '');
}
 
function buildClassItemFilters(filterBox, items, activeFilter, onFilterClick) {
    var orderedFilters = [
        'All',
        'Head',
        'Chest',
        'Robe',
        'Hands',
        'Legs',
        'Feet',
        'Forearm',
        'Bracelet',
        'Waist',
        'Neck',
        'Ear',
        'Ring',
        'Primary',
        'Secondary',
        'Shield',
        'Held',
        'Range',
        'Recipe',
        'Quest',
        'Clickies',
        'Miscellaneous'
    ];
 
    var counts = {};
 
    if (!filterBox) return;
 
    items.forEach(function (item) {
        counts.All = (counts.All || 0) + 1;
        counts[item.type] = (counts[item.type] || 0) + 1;
    });
 
    filterBox.innerHTML = '';
 
    orderedFilters.forEach(function (filterName) {
        var button;
 
        if (filterName !== 'All' && !counts[filterName]) return;
 
        button = document.createElement('button');
        button.type = 'button';
        button.className = 'class-item-filter-button';
 
        if (filterName === activeFilter) {
            button.className += ' is-active';
        }
 
        button.textContent = filterName + ' (' + (counts[filterName] || 0) + ')';
 
        button.addEventListener('click', function () {
            filterBox.querySelectorAll('.class-item-filter-button').forEach(function (btn) {
                btn.classList.remove('is-active');
            });
 
            button.classList.add('is-active');
            onFilterClick(filterName);
        });
 
        filterBox.appendChild(button);
    });
}
 
function drawClassItemRows(tbody, items, activeFilter, searchText) {
    var filtered;
    var query = String(searchText || '').toLowerCase();
 
    tbody.innerHTML = '';
 
    filtered = items.filter(function (item) {
        var haystack;
 
        if (activeFilter && activeFilter !== 'All' && item.type !== activeFilter) {
            return false;
        }
 
        if (!query) return true;
 
        haystack = [
            item.name,
            item.slot,
            item.level,
            item.source,
            item.type
        ].join(' ').toLowerCase();
 
        return haystack.indexOf(query) !== -1;
    });
 
    filtered.forEach(function (item) {
        var row = document.createElement('tr');
 
row.innerHTML =
'<td><a class="class-item-tooltip-link class-item-rarity-' + normalizeClassItemRarity(item.rarity) + '" data-class-item-name="' + encodeURIComponent(item.name) + '" href="' + mw.util.getUrl(item.name) + '">' + escapeClassItemHtml(item.name) + '</a></td>' +
    '<td>' + (item.level || '') + '</td>' +
    '<td>' + escapeClassItemHtml(item.slot || '') + '</td>' +
    '<td>' + item.source + '</td>';
 
        tbody.appendChild(row);
bindClassItemTooltip(row, item);
    });
 
    if (!filtered.length) {
        var emptyRow = document.createElement('tr');
        emptyRow.innerHTML = '<td colspan="4">No class items found.</td>';
        tbody.appendChild(emptyRow);
    }
}
 
function bindClassItemTooltip(row, item) {
    var link = row.querySelector('.class-item-tooltip-link');
 
    if (!link || !item) return;
 
    link.addEventListener('mouseenter', function (event) {
        showClassItemTooltip(item, event);
    });
 
    link.addEventListener('mousemove', moveClassItemTooltip);
 
    link.addEventListener('mouseleave', function () {
        hideClassItemTooltip();
    });
}
 
function getClassItemTooltipBox() {
    var tooltip = document.getElementById('class-item-tooltip');
 
    if (!tooltip) {
        tooltip = document.createElement('div');
        tooltip.id = 'class-item-tooltip';
        tooltip.className = 'class-item-tooltip';
        tooltip.style.display = 'none';
        document.body.appendChild(tooltip);
    }
 
    return tooltip;
}
 
function showClassItemTooltip(item, event) {
    var tooltip = getClassItemTooltipBox();
    var rarity = normalizeClassItemRarity(item.rarity);
    var html = '';
 
    function statLine(label, value) {
        if (!value || parseFloat(value) === 0) return '';
        return '<div><span>' + label + ':</span> ' + escapeClassItemHtml(value) + '</div>';
    }
 
    tooltip.className = 'class-item-tooltip class-item-tooltip-rarity-' + rarity;
 
    html += '<div class="class-item-tooltip-title class-item-rarity-' + rarity + ' cb-rarity-' + rarity + '" style="' + getClassItemRarityInlineStyle(rarity) + '">' +
        escapeClassItemHtml(item.name) +
        '</div>';
 
    if (item.notrade || item.lore) {
        html += '<div class="class-item-tooltip-flags">';
        if (item.notrade) html += '<span>NO TRADE</span>';
        if (item.lore) html += '<span>LORE</span>';
        html += '</div>';
    }
 
    if (item.level) html += '<div><b>Level:</b> ' + item.level + '</div>';
    if (item.slot) html += '<div><b>Slot:</b> ' + escapeClassItemHtml(item.slot) + '</div>';
    if (item.dur) html += '<div><b>DUR:</b> ' + item.dur + '</div>';
    if (item.gearhp) html += '<div><b>Gear HP:</b> ' + item.gearhp + '</div>';
    if (item.race) html += '<div><b>Race:</b> ' + escapeClassItemHtml(item.race) + '</div>';
    if (item.className) html += '<div><b>Class:</b> ' + escapeClassItemHtml(item.className) + '</div>';
 
    html += '<div class="class-item-tooltip-section">Item Stats</div>';
    html += '<div class="class-item-tooltip-stats">';
    html += statLine('DMG', item.dmg);
    html += statLine('AC', item.ac);
    html += statLine('STR', item.str);
    html += statLine('STA', item.sta);
    html += statLine('AGI', item.agi);
    html += statLine('DEX', item.dex);
    html += statLine('WIS', item.wis);
    html += statLine('INT', item.int);
    html += statLine('CHA', item.cha);
    html += statLine('HP', item.hp);
    html += statLine('HoT', item.hot);
    html += statLine('POW', item.pow);
    html += statLine('PoT', item.pot);
    html += statLine('PR', item.pr);
    html += statLine('DR', item.dr);
    html += statLine('FR', item.fr);
    html += statLine('CR', item.cr);
    html += statLine('LR', item.lr);
    html += statLine('AR', item.ar);
    html += '</div>';
 
    if (item.effect) html += '<div><b>Effect:</b> ' + escapeClassItemHtml(item.effect) + '</div>';
    if (item.proc) html += '<div><b>Proc:</b> ' + escapeClassItemHtml(item.proc) + '</div>';
 
    if (item.source) {
        html += '<div class="class-item-tooltip-section">Source</div>';
        html += '<div>' + item.source + '</div>';
    }
 
    tooltip.innerHTML = html;
    tooltip.style.display = 'block';
 
    moveClassItemTooltip(event);
}
 
function normalizeClassItemRarity(rarity) {
    rarity = String(rarity || 'common')
        .toLowerCase()
        .replace(/\s+/g, '-')
        .trim();
 
    if (rarity === 'ultrarare') return 'ultra-rare';
    if (rarity === 'ultra-rare') return 'ultra-rare';
    if (rarity === 'legendary') return 'legendary';
    if (rarity === 'unique') return 'unique';
    if (rarity === 'rare') return 'rare';
    if (rarity === 'uncommon') return 'uncommon';
    if (rarity === 'common') return 'common';
 
    return 'common';
}
 
 
function getClassItemRarityInlineStyle(rarity) {
    rarity = normalizeClassItemRarity(rarity);
 
    if (rarity === 'unique') {
        return 'color:#ffd700!important;text-shadow:0 0 8px rgba(255,215,0,0.85)!important;';
    }
 
    if (rarity === 'legendary') {
        return 'color:#ff8000!important;text-shadow:0 0 8px rgba(255,128,0,0.85)!important;';
    }
 
    if (rarity === 'ultra-rare') {
        return 'color:#ff33ff!important;text-shadow:0 0 8px rgba(255,51,255,0.85)!important;';
    }
 
    if (rarity === 'rare') {
        return 'color:#1e90ff!important;text-shadow:0 0 8px rgba(30,144,255,0.85)!important;';
    }
 
    if (rarity === 'uncommon') {
        return 'color:#00ff55!important;text-shadow:0 0 8px rgba(0,255,85,0.85)!important;';
    }
 
    return 'color:#ffffff!important;text-shadow:0 0 6px rgba(255,255,255,0.75)!important;';
}
 
function applyClassItemRarityStyle(element, rarity) {
    rarity = normalizeClassItemRarity(rarity);
 
    if (!element) return;
 
    if (rarity === 'unique') {
        element.style.setProperty('color', '#ffd700', 'important');
        element.style.setProperty('text-shadow', '0 0 8px rgba(255,215,0,0.85)', 'important');
        return;
    }
 
    if (rarity === 'legendary') {
        element.style.setProperty('color', '#ff8000', 'important');
        element.style.setProperty('text-shadow', '0 0 8px rgba(255,128,0,0.85)', 'important');
        return;
    }
 
    if (rarity === 'ultra-rare') {
        element.style.setProperty('color', '#ff33ff', 'important');
        element.style.setProperty('text-shadow', '0 0 8px rgba(255,51,255,0.85)', 'important');
        return;
    }
 
    if (rarity === 'rare') {
        element.style.setProperty('color', '#1e90ff', 'important');
        element.style.setProperty('text-shadow', '0 0 8px rgba(30,144,255,0.85)', 'important');
        return;
    }
 
    if (rarity === 'uncommon') {
        element.style.setProperty('color', '#00ff55', 'important');
        element.style.setProperty('text-shadow', '0 0 8px rgba(0,255,85,0.85)', 'important');
        return;
    }
 
    element.style.setProperty('color', '#ffffff', 'important');
    element.style.setProperty('text-shadow', '0 0 6px rgba(255,255,255,0.75)', 'important');
}
 
function moveClassItemTooltip(event) {
    var tooltip = getClassItemTooltipBox();
    var left = event.pageX + 16;
    var top = event.pageY + 16;
 
    if (left + 360 > window.scrollX + window.innerWidth) {
        left = event.pageX - 360;
    }
 
    tooltip.style.left = left + 'px';
    tooltip.style.top = top + 'px';
}
 
function hideClassItemTooltip() {
    var tooltip = document.getElementById('class-item-tooltip');
 
    if (tooltip) {
        tooltip.style.display = 'none';
    }
}
 
function escapeClassItemHtml(value) {
    return String(value == null ? '' : value)
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#039;');
}
 
function initGeneratedItemTooltipsForWiki($content) {
    var root = $content && $content[0] ? $content[0] : document;
    var items = window.EQOA_GENERATED_ITEMS || [];
    var lookup = {};
    var links;
 
    if (!items.length) {
        setTimeout(function () {
            initGeneratedItemTooltipsForWiki($content);
        }, 250);
        return;
    }
 
    function normalizeItemName(value) {
        return String(value || '')
            .replace(/\u00a0/g, ' ')
            .replace(/[’`]/g, "'")
            .replace(/\s+/g, ' ')
            .trim()
            .toLowerCase();
    }
 
    function looseItemName(value) {
        return normalizeItemName(value).replace(/[^a-z0-9]/g, '');
    }
 
    function stripDropText(value) {
        return String(value || '')
            .replace(/\(\d+%\)/g, '')
            .replace(/\([^)]*\)$/g, '')
            .replace(/\s+/g, ' ')
            .trim();
    }
 
    function rarityRank(item) {
        var rarity = normalizeClassItemRarity(item && item.rarity);
 
        if (rarity === 'ultra-rare') return 4;
        if (rarity === 'rare') return 3;
        if (rarity === 'uncommon') return 2;
 
        return 1;
    }
 
    function storeItem(key, item) {
        var current;
 
        if (!key) return;
 
        current = lookup[key];
 
        if (!current || rarityRank(item) > rarityRank(current)) {
            lookup[key] = item;
        }
    }
 
    function getItemFromLink(link) {
        var text = stripDropText(link.textContent);
        var href = link.getAttribute('href') || '';
        var title = '';
 
        if (href.indexOf('title=') !== -1) {
            title = decodeURIComponent(href.split('title=')[1].split('&')[0]).replace(/_/g, ' ');
        } else if (href.indexOf('/index.php?title=') !== -1) {
            title = decodeURIComponent(href.split('/index.php?title=')[1].split('&')[0]).replace(/_/g, ' ');
        }
 
        return (
            lookup[normalizeItemName(text)] ||
            lookup[looseItemName(text)] ||
            lookup[normalizeItemName(title)] ||
            lookup[looseItemName(title)] ||
            null
        );
    }
 
    function normalizeGeneratedItem(item) {
        return {
            name: item.name || '',
            rarity: item.rarity || 'common',
            slot: item.slot || '',
            level: item.level || item.lvl || '',
            race: item.race || '',
            className: Array.isArray(item.class) ? item.class.join(', ') : (item.class || item.classes || item.className || ''),
            notrade: item.notrade || item.noTrade || item.trade === 'NO TRADE',
            lore: item.lore || '',
            dmg: item.dmg || item.damage || '',
            ac: item.ac || '',
            str: item.str || '',
            sta: item.sta || '',
            agi: item.agi || '',
            dex: item.dex || '',
            wis: item.wis || '',
            int: item.int || '',
            cha: item.cha || '',
            hp: item.hp || '',
            hot: item.hot || '',
            pow: item.pow || '',
            pot: item.pot || '',
            pr: item.pr || '',
            dr: item.dr || '',
            fr: item.fr || '',
            cr: item.cr || '',
            lr: item.lr || '',
            ar: item.ar || '',
            dur: item.dur || '',
            gearhp: item.gearhp || item.gearHp || item.gear_hp || '',
            effect: item.effect || '',
            proc: item.proc || '',
            source: item.source || ''
        };
    }
 
    items.forEach(function (item) {
        if (!item || !item.name) return;
 
        storeItem(normalizeItemName(item.name), item);
        storeItem(looseItemName(item.name), item);
    });
 
    links = root.querySelectorAll(
        '#items-found-here-table td:first-child a, ' +
        '#items-found-here-table .items-found-here-name a, ' +
        '.zone-monster-table .zone-monster-drops a, ' +
        '.zone-monster-table td:nth-child(3) a, ' +
        '.eqoa-related-items a'
    );
 
    Array.prototype.forEach.call(links, function (link) {
        var item = getItemFromLink(link);
        var tooltipItem;
        var rarity;
 
        if (!item) return;
 
        tooltipItem = normalizeGeneratedItem(item);
        rarity = normalizeClassItemRarity(tooltipItem.rarity);
 
        link.classList.remove(
            'class-item-rarity-common',
            'class-item-rarity-uncommon',
            'class-item-rarity-rare',
            'class-item-rarity-ultra-rare',
            'class-item-rarity-legendary',
            'class-item-rarity-unique',
            'cb-rarity-common',
            'cb-rarity-uncommon',
            'cb-rarity-rare',
            'cb-rarity-ultra-rare',
            'cb-rarity-legendary',
            'cb-rarity-unique',
            'eqoa-related-tooltip-ready',
            'items-found-tooltip-ready'
        );
 
        link.classList.add('class-item-tooltip-link');
        link.classList.add('class-item-rarity-' + rarity);
        link.classList.add('cb-rarity-' + rarity);
        applyClassItemRarityStyle(link, rarity);
 
        link.onmouseenter = function (event) {
            showClassItemTooltip(tooltipItem, event);
        };
 
        link.onmousemove = moveClassItemTooltip;
 
        link.onmouseleave = function () {
            hideClassItemTooltip();
        };
    });
}
 
mw.hook('wikipage.content').add(initGeneratedItemTooltipsForWiki);
document.addEventListener('DOMContentLoaded', initGeneratedItemTooltipsForWiki);
setTimeout(initGeneratedItemTooltipsForWiki, 500);
setTimeout(initGeneratedItemTooltipsForWiki, 1500);
 
mw.hook('wikipage.content').add(initClassItemLists);
 
if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initClassItemLists);
} else {
    initClassItemLists();
}
 
setTimeout(initClassItemLists, 500);
setTimeout(initClassItemLists, 1500);
setTimeout(initClassItemLists, 1500);
function initItemsFoundHereSortControls(root) {
    root = root && root.jquery ? root[0] : (root || document);
    var table = root.querySelector('#items-found-here-table');
    if (!table) return;
    var wrapper = table.closest('.zone-drops-auto-wrapper') || table.parentElement;
    if (!wrapper) return;
    if (wrapper.querySelector('.zone-drops-controls')) return;
    var controls = document.createElement('div');
    controls.className = 'zone-drops-controls';
    controls.innerHTML =
        '<div class="zone-drops-controls-left">' +
            '<input class="zone-drops-search" type="search" placeholder="Search item">' +
        '</div>' +
        '<div class="zone-drops-controls-right">' +
            '<select class="zone-drops-sort-select">' +
                '<option value="name">Sort: Name</option>' +
                '<option value="level">Sort: Level</option>' +
                '<option value="class">Sort: Class</option>' +
                '<option value="dmg">Sort: DMG</option>' +
                '<option value="ac">Sort: AC</option>' +
                '<option value="slot">Sort: Slot</option>' +
                '<option value="rarity">Sort: Rarity</option>' +
            '</select>' +
            '<button type="button" class="zone-drops-sort-button">Sort</button>' +
            '<select class="zone-drops-class-select">' +
                '<option value="">All Classes</option>' +
                '<option value="WAR">WAR</option>' +
                '<option value="RAN">RAN</option>' +
                '<option value="PAL">PAL</option>' +
                '<option value="SK">SK</option>' +
                '<option value="MNK">MNK</option>' +
                '<option value="BRD">BRD</option>' +
                '<option value="RGE">RGE</option>' +
                '<option value="DRD">DRD</option>' +
                '<option value="SHA">SHA</option>' +
                '<option value="CL">CL</option>' +
                '<option value="MAG">MAG</option>' +
                '<option value="NEC">NEC</option>' +
                '<option value="ENC">ENC</option>' +
                '<option value="WIZ">WIZ</option>' +
                '<option value="ALC">ALC</option>' +
            '</select>' +
            '<button type="button" class="zone-drops-clear">Clear</button>' +
        '</div>';
    wrapper.insertBefore(controls, table);
    var searchInput = controls.querySelector('.zone-drops-search');
    var sortSelect = controls.querySelector('.zone-drops-sort-select');
    var sortButton = controls.querySelector('.zone-drops-sort-button');
    var classSelect = controls.querySelector('.zone-drops-class-select');
    var clearButton = controls.querySelector('.zone-drops-clear');
    function getRows() {
        return Array.from(table.querySelectorAll('tbody tr'));
    }
    function getCellText(row, index) {
        var cell = row.children[index];
        return cell ? cell.textContent.trim() : '';
    }
    function getSortValue(row, key) {
        if (key === 'name') return row.getAttribute('data-name') || getCellText(row, 0);
        if (key === 'level') return row.getAttribute('data-level') || getCellText(row, 1);
        if (key === 'class') return row.getAttribute('data-class') || getCellText(row, 2);
        return row.getAttribute('data-' + key) || '';
    }
    function numberOrText(value) {
        var number = parseFloat(String(value).replace(/[^\d.-]/g, ''));
        return isNaN(number) ? String(value).toLowerCase() : number;
    }
    function applyFilters() {
        var query = searchInput.value.trim().toLowerCase();
        var classFilter = classSelect.value.trim().toUpperCase();
        getRows().forEach(function (row) {
            var rowText = row.textContent.toLowerCase();
            var rowClass = String(row.getAttribute('data-class') || getCellText(row, 2)).toUpperCase();
            var matchesSearch = !query || rowText.indexOf(query) !== -1;
            var matchesClass = !classFilter || rowClass.indexOf(classFilter) !== -1;
            row.style.display = matchesSearch && matchesClass ? '' : 'none';
        });
    }
    function sortRows() {
        var tbody = table.querySelector('tbody');
        var key = sortSelect.value;
        var rows = getRows();
        rows.sort(function (a, b) {
            var aValue = numberOrText(getSortValue(a, key));
            var bValue = numberOrText(getSortValue(b, key));
            if (typeof aValue === 'number' && typeof bValue === 'number') {
                return aValue - bValue;
            }
            return String(aValue).localeCompare(String(bValue));
        });
        rows.forEach(function (row) {
            tbody.appendChild(row);
        });
        applyFilters();
    }
    searchInput.addEventListener('input', applyFilters);
    classSelect.addEventListener('change', applyFilters);
    sortButton.addEventListener('click', sortRows);
    clearButton.addEventListener('click', function () {
        searchInput.value = '';
        classSelect.value = '';
        sortSelect.value = 'name';
        applyFilters();
    });
}
function initEqoaCategoryLists() {
    document.querySelectorAll('.eqoa-category-list').forEach(function (box) {
        var category = box.getAttribute('data-category');
        if (!category || box.classList.contains('eqoa-category-list-ready')) return;
        box.classList.add('eqoa-category-list-ready');
        box.innerHTML = 'Loading...';
        new mw.Api().get({
            action: 'query',
            list: 'categorymembers',
            cmtitle: 'Category:' + category,
            cmlimit: 500,
            format: 'json'
        }).then(function (data) {
            var pages = data.query.categorymembers || [];
            if (!pages.length) {
                box.innerHTML = 'No entries found.';
                return;
            }
            pages.sort(function (a, b) {
                return a.title.localeCompare(b.title);
            });
box.innerHTML =
    '<table class="zone-data-table zone-quest-table">' +
        '<tr>' +
            '<th class="zone-quest-name">Quest</th>' +
            '<th class="zone-quest-type">Type</th>' +
            '<th class="zone-quest-starter">Starter</th>' +
            '<th class="zone-quest-location">Location</th>' +
        '</tr>' +
        pages.map(function (page) {
            return '<tr>' +
                '<td class="zone-quest-name"><a href="' + mw.util.getUrl(page.title) + '">' + page.title + '</a></td>' +
                '<td class="zone-quest-type"></td>' +
                '<td class="zone-quest-starter"></td>' +
                '<td class="zone-quest-location"></td>' +
            '</tr>';
        }).join('') +
    '</table>';
        });
    });
}
mw.hook('wikipage.content').add(initEqoaCategoryLists);
document.addEventListener('DOMContentLoaded', initEqoaCategoryLists);
function initEqoaClassIndex() {
    document.querySelectorAll('.eqoa-class-index').forEach(function (root) {
        if (root.classList.contains('eqoa-class-index-ready')) return;
        var grid = root.querySelector('.eqoa-class-index-grid');
        var cards = root.querySelectorAll('.eqoa-class-card');
        var toolbar = document.createElement('div');
        var filters = document.createElement('div');
        var roles;
        var activeRole = 'all';
        if (!grid) return;
        root.classList.add('eqoa-class-index-ready');
        toolbar.className = 'eqoa-class-index-toolbar';
        filters.className = 'eqoa-class-index-filters';
        if (root.classList.contains('eqoa-race-index')) {
            roles = [
                ['all', 'All'],
                ['good', 'Good'],
                ['evil', 'Evil'],
                ['neutral', 'Neutral']
            ];
        } else {
            roles = [
                ['all', 'All'],
                ['tank', 'Tank'],
                ['melee', 'Melee'],
                ['healer', 'Healer'],
                ['support', 'Support'],
                ['caster', 'Caster']
            ];
        }
        roles.forEach(function (role) {
            var button = document.createElement('button');
            button.type = 'button';
            button.textContent = role[1];
            button.setAttribute('data-role-filter', role[0]);
            if (role[0] === 'all') {
                button.classList.add('is-active');
            }
            button.addEventListener('click', function () {
                activeRole = role[0];
                filters.querySelectorAll('button').forEach(function (btn) {
                    btn.classList.remove('is-active');
                });
                button.classList.add('is-active');
                applyFilters();
            });
            filters.appendChild(button);
        });
        toolbar.appendChild(filters);
        root.insertBefore(toolbar, grid);
        function cardHasRole(card, role) {
            if (role === 'all') return true;
            return card.classList.contains('eqoa-role-' + role);
        }
        function applyFilters() {
            cards.forEach(function (card) {
                card.style.display = cardHasRole(card, activeRole) ? '' : 'none';
            });
        }
        applyFilters();
    });
}
   
mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:ClassSpellGeneratedSpells1.js&action=raw&ctype=text/javascript');
mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:ClassSpellGeneratedSpells2.js&action=raw&ctype=text/javascript');
mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:ClassSpellGeneratedSpells3.js&action=raw&ctype=text/javascript');
mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:ClassSpellGeneratedSpells4.js&action=raw&ctype=text/javascript');
mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:ClassSpellGeneratedSpells5.js&action=raw&ctype=text/javascript');
mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:ClassSpellGroupOverrides.js&action=raw&ctype=text/javascript&v=20260604');
mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:ClassSpellGroupOverrides.js&action=raw&ctype=text/javascript');
mw.hook('wikipage.content').add(initEqoaClassIndex);
document.addEventListener('DOMContentLoaded', initEqoaClassIndex);
setTimeout(initEqoaClassIndex, 500);
function initEqoaSpellLists() {
    var lists = document.querySelectorAll('[data-eqoa-spell-list]');
    var spells = window.EQOA_GENERATED_SPELLS || [];
    if (!lists.length) return;
    if (!spells.length) {
        setTimeout(initEqoaSpellLists, 250);
        return;
    }
    function escapeHtml(value) {
        return String(value == null ? '' : value)
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;')
            .replace(/'/g, '&#039;');
    }
    function fileUrl(fileName) {
        fileName = String(fileName || '').trim();
        if (!fileName) return '';
        if (/^https?:\/\//i.test(fileName)) return fileName;
        return mw.config.get('wgScript') + '?title=Special:Redirect/file/' + encodeURIComponent(fileName);
    }
    function pageUrl(title) {
        if (window.mw && mw.util && mw.util.getUrl) {
            return mw.util.getUrl(title);
        }
        return '/index.php?title=' + encodeURIComponent(String(title || '').replace(/ /g, '_'));
    }
    function iconHtml(fileName, className, altText) {
        var url = fileUrl(fileName);
        if (!url) return '';
        return '<img class="' + className + '" src="' + url + '" alt="' + escapeHtml(altText || '') + '">';
    }
    function classIconFile(classCode) {
        return 'ClassIcon' + classCode + '.png';
    }
    function classIconsHtml(spell) {
        return (spell.classes || []).map(function (classCode) {
            return iconHtml(classIconFile(classCode), 'eqoa-spell-class-icon', classCode);
        }).join('');
    }
function spellIconFile(spell) {
    var iconName = String(spell.icon || '').trim();
    if (!iconName) return '';
    iconName = iconName
        .replace(/\.[a-z0-9]+$/i, '')
        .replace(/[^a-z0-9]+/gi, '');
    return 'SpellIcon' + iconName + '.png';
}
    function classText(spell) {
        return (spell.classNames || spell.classes || []).join(', ');
    }
    function sortSpells(rows, sortKey) {
        rows.sort(function (a, b) {
            if (sortKey === 'name') {
                return String(a.name || '').localeCompare(String(b.name || ''));
            }
            if (sortKey === 'level') {
                return (parseFloat(a.level) || 0) - (parseFloat(b.level) || 0);
            }
            return String(classText(a)).localeCompare(String(classText(b)));
        });
    }
    function getSpellTooltipBox() {
        var tooltip = document.getElementById('eqoa-spell-tooltip');
        if (!tooltip) {
            tooltip = document.createElement('div');
            tooltip.id = 'eqoa-spell-tooltip';
            tooltip.className = 'eqoa-spell-tooltip';
            tooltip.style.display = 'none';
            document.body.appendChild(tooltip);
        }
        return tooltip;
    }
    function formatSpellValue(value) {
        if (value === undefined || value === null || value === '') {
            return '0';
        }
        return escapeHtml(value);
    }
    function showSpellTooltip(spell, event) {
        var tooltip = getSpellTooltipBox();
        var name = escapeHtml(spell.name || '');
        var scope = escapeHtml(spell.scope || '');
        var cast = formatSpellValue(spell.castTime);
        var recast = formatSpellValue(spell.recast);
        var power = formatSpellValue(spell.powerCost);
        var equipReq = escapeHtml((spell.classNames || spell.classes || ['ANY']).join(', ') || 'ANY');
        var level = formatSpellValue(spell.level);
        var range = formatSpellValue(spell.range);
        var description = escapeHtml(spell.description || '');
        tooltip.innerHTML =
            '<div class="eqoa-spell-tooltip-main">' +
                '<div class="eqoa-spell-tooltip-name">' + name + '</div>' +
                '<div>Scope: ' + scope + ' <span>Cast: ' + cast + '</span></div>' +
                '<div>Recast: ' + recast + ' <span>Power: ' + power + '</span></div>' +
                '<div>EquipReq: ' + equipReq + '</div>' +
                '<div>Level: ' + level + ' <span>Range: ' + range + '</span></div>' +
            '</div>' +
            '<div class="eqoa-spell-tooltip-desc">' +
                description +
            '</div>';
        tooltip.style.display = 'block';
        moveSpellTooltip(event);
    }
    function moveSpellTooltip(event) {
        var tooltip = getSpellTooltipBox();
        var left = event.pageX + 18;
        var top = event.pageY + 18;
        if (left + 430 > window.scrollX + window.innerWidth) {
            left = event.pageX - 430;
        }
        tooltip.style.left = left + 'px';
        tooltip.style.top = top + 'px';
    }
    function hideSpellTooltip() {
        var tooltip = document.getElementById('eqoa-spell-tooltip');
        if (tooltip) {
            tooltip.style.display = 'none';
        }
    }
    function bindSpellTooltip(row, spell) {
        row.addEventListener('mouseenter', function (event) {
            showSpellTooltip(spell, event);
        });
        row.addEventListener('mousemove', moveSpellTooltip);
        row.addEventListener('mouseleave', hideSpellTooltip);
    }
    function renderRows(tbody, rows) {
        tbody.innerHTML = '';
        rows.forEach(function (spell) {
            var row = document.createElement('tr');
            var name = escapeHtml(spell.name || '');
            var level = escapeHtml(spell.level || '');
            var classes = classIconsHtml(spell);
            var skill = escapeHtml(classText(spell));
            var icon = iconHtml(spellIconFile(spell), 'eqoa-spell-icon', spell.name);
            row.className = 'eqoa-spell-row';
row.innerHTML =
    '<td class="eqoa-spell-name-cell">' + icon + '<a href="' + pageUrl(spell.name) + '">' + name + '</a></td>' +
    '<td class="eqoa-spell-level-cell">' + level + '</td>' +
    '<td class="eqoa-spell-classes-cell">' + classes + '</td>';
            bindSpellTooltip(row, spell);
            tbody.appendChild(row);
        });
    }
    lists.forEach(function (box) {
        var classCode = String(box.getAttribute('data-class') || '').toUpperCase().trim();
        var rows;
        var table;
        var tbody;
        var sortSelect;
        var loading;
        if (box.classList.contains('eqoa-spell-list-ready')) return;
        box.classList.add('eqoa-spell-list-ready');
        rows = spells.filter(function (spell) {
            if (String(spell.spellGroup || 'Base') !== 'Base') return false;
            if (!classCode) return true;
            return (spell.classes || []).indexOf(classCode) !== -1;
        });
        sortSpells(rows, 'level');
        if (!rows.length) {
            box.innerHTML += '<div class="eqoa-spell-empty">No spells found.</div>';
            return;
        }
        loading = box.querySelector('.eqoa-spell-list__loading');
        if (loading) loading.remove();
        box.insertAdjacentHTML('beforeend',
            '<div class="eqoa-spell-toolbar">' +
                '<div class="eqoa-spell-count">' + rows.length + ' spells</div>' +
                '<div class="eqoa-spell-sort-wrap">' +
                    '<select class="eqoa-spell-sort">' +
                        '<option value="level">Sort: Level</option>' +
                        '<option value="name">Sort: Name</option>' +
                        '<option value="class">Sort: Class</option>' +
                    '</select>' +
                '</div>' +
            '</div>' +
            '<table class="eqoa-spell-list-table">' +
                '<tr>' +
                    '<th>Name</th>' +
                    '<th>Level</th>' +
                    '<th>Classes</th>' +
                '</tr>' +
            '</table>'
        );
        table = box.querySelector('.eqoa-spell-list-table');
        tbody = document.createElement('tbody');
        table.appendChild(tbody);
        renderRows(tbody, rows);
        sortSelect = box.querySelector('.eqoa-spell-sort');
        sortSelect.addEventListener('change', function () {
            sortSpells(rows, sortSelect.value);
            renderRows(tbody, rows);
        });
    });
}
mw.hook('wikipage.content').add(initEqoaSpellLists);
document.addEventListener('DOMContentLoaded', initEqoaSpellLists);
setTimeout(initEqoaSpellLists, 500);
setTimeout(initEqoaSpellLists, 1500);
function initEqoaSpellPathLists() {
    var spellData = window.EQOA_GENERATED_SPELLS || [];
    document.querySelectorAll('[data-eqoa-spell-path-list]').forEach(function (box) {
        var classCode;
        var spellGroup;
        var title;
        var rows;
        var path1;
        var path2;
        var loading;
        classCode = String(box.getAttribute('data-class') || '').trim().toUpperCase();
        spellGroup = normalizeEqoaSpellPathGroup(box.getAttribute('data-spell-group'));
        title = box.querySelector('.eqoa-spell-list__title')
            ? box.querySelector('.eqoa-spell-list__title').textContent.trim()
            : 'Spell Path';
        if (!spellData.length) {
            setTimeout(initEqoaSpellPathLists, 250);
            return;
        }
        loading = box.querySelector('.eqoa-spell-list__loading');
        if (loading) loading.remove();
        rows = spellData.filter(function (spell) {
            var currentGroup = normalizeEqoaSpellPathGroup(spell.spellGroup);
            if (currentGroup !== spellGroup) return false;
            if (!classCode) return true;
            return (spell.classes || []).map(function (code) {
                return String(code || '').toUpperCase();
            }).indexOf(classCode) !== -1;
        });
        path1 = rows.filter(function (spell) {
            var spellPath = String(spell.spellPath || '');
            return spellPath === '1' || spellPath === 'both';
        });
        path2 = rows.filter(function (spell) {
            var spellPath = String(spell.spellPath || '');
            return spellPath === '2' || spellPath === 'both';
        });
        box.innerHTML =
            '<div class="eqoa-spell-list__title">' + escapeEqoaSpellPathHtml(title) + '</div>' +
            '<div class="eqoa-spell-path-grid">' +
                buildEqoaSpellPathSection('Path 1', path1) +
                buildEqoaSpellPathSection('Path 2', path2) +
            '</div>';
        bindEqoaSpellPathTooltipEvents(box, rows);
    });
}
function normalizeEqoaSpellPathGroup(value) {
    value = String(value || '').toLowerCase().replace(/\s+/g, '');
    if (value === 'classpath' || value === 'path') return 'ClassPath';
    if (value === 'exemplarpath' || value === 'exemplar') return 'ExemplarPath';
    return value;
}
function buildEqoaSpellPathSection(title, spells) {
    var html = '';
    html += '<div class="eqoa-spell-path-section">';
    html += '<div class="eqoa-spell-path-title">' + escapeEqoaSpellPathHtml(title) + '</div>';
    html += '<table class="eqoa-spell-list-table">';
html += '<tr>';
html += '<th>Name</th>';
html += '<th>Level</th>';
html += '<th>Classes</th>';
html += '</tr>';
    if (!spells.length) {
        html += '<tr class="eqoa-spell-row"><td colspan="4">No spells found.</td></tr>';
    } else {
        spells.forEach(function (spell) {
            html += '<tr class="eqoa-spell-row" data-eqoa-spell-name="' + escapeEqoaSpellPathHtml(spell.name) + '">';
            html += '<td class="eqoa-spell-name-cell">';
            html += buildEqoaSpellPathIcon(spell);
            html += '<a class="eqoa-spell-link" href="' + eqoaSpellPathPageUrl(spell.name) + '" data-eqoa-spell-name="' + escapeEqoaSpellPathHtml(spell.name) + '">';
            html += escapeEqoaSpellPathHtml(spell.name);
            html += '</a>';
            html += '</td>';
            html += '<td class="eqoa-spell-level-cell">' + escapeEqoaSpellPathHtml(spell.level || '') + '</td>';
            html += '<td class="eqoa-spell-classes-cell">' + buildEqoaSpellPathClassIcons(spell) + '</td>';
            html += '</tr>';
        });
    }
    html += '</table>';
    html += '</div>';
    return html;
}
function buildEqoaSpellPathIcon(spell) {
    var fileName = spellIconNameToFile(spell.icon);
    if (!fileName) return '';
    return '<img class="eqoa-spell-icon" src="' + eqoaSpellPathFileUrl(fileName) + '" alt="' + escapeEqoaSpellPathHtml(spell.name || '') + '">';
}
function spellIconNameToFile(iconName) {
    iconName = String(iconName || '').trim();
    if (!iconName) return '';
    iconName = iconName
        .replace(/\.[a-z0-9]+$/i, '')
        .replace(/[^a-z0-9]+/gi, '');
    return 'SpellIcon' + iconName + '.png';
}
function buildEqoaSpellPathClassIcons(spell) {
    var classes = spell.classes || [];
    if (!classes.length) return '';
    return classes.map(function (classCode) {
        return '<img class="eqoa-spell-class-icon" src="' + eqoaSpellPathFileUrl('ClassIcon' + classCode + '.png') + '" alt="' + escapeEqoaSpellPathHtml(classCode) + '" title="' + escapeEqoaSpellPathHtml(classCode) + '">';
    }).join('');
}
function bindEqoaSpellPathTooltipEvents(root, spells) {
    var lookup = {};
    spells.forEach(function (spell) {
        lookup[String(spell.name || '').toLowerCase()] = spell;
    });
    root.querySelectorAll('.eqoa-spell-row, .eqoa-spell-link').forEach(function (el) {
        var spellName = String(el.getAttribute('data-eqoa-spell-name') || el.textContent || '').trim();
        var spell = lookup[spellName.toLowerCase()];
        if (!spell) return;
        el.onmouseenter = function (event) {
            showEqoaSpellPathTooltip(spell, event);
        };
        el.onmousemove = function (event) {
            moveEqoaSpellPathTooltip(event);
        };
        el.onmouseleave = function () {
            hideEqoaSpellPathTooltip();
        };
    });
}
function getEqoaSpellPathTooltipBox() {
    var tooltip = document.getElementById('eqoa-spell-tooltip');
    if (!tooltip) {
        tooltip = document.createElement('div');
        tooltip.id = 'eqoa-spell-tooltip';
        tooltip.className = 'eqoa-spell-tooltip';
        tooltip.style.display = 'none';
        document.body.appendChild(tooltip);
    }
    return tooltip;
}
function showEqoaSpellPathTooltip(spell, event) {
    var tooltip = getEqoaSpellPathTooltipBox();
    var name = escapeEqoaSpellPathHtml(spell.name || '');
    var scope = escapeEqoaSpellPathHtml(spell.scope || '');
    var cast = formatEqoaSpellPathValue(spell.castTime);
    var recast = formatEqoaSpellPathValue(spell.recast);
    var power = formatEqoaSpellPathValue(spell.powerCost);
    var equipReq = escapeEqoaSpellPathHtml(spell.reqEquipment || 'ANY');
    var level = formatEqoaSpellPathValue(spell.level);
    var range = formatEqoaSpellPathValue(spell.range);
    var description = escapeEqoaSpellPathHtml(spell.description || '');
    tooltip.innerHTML =
        '<div class="eqoa-spell-tooltip-main">' +
            '<div class="eqoa-spell-tooltip-name">' + name + '</div>' +
            '<div>Scope: ' + scope + ' <span>Cast: ' + cast + '</span></div>' +
            '<div>Recast: ' + recast + ' <span>Power: ' + power + '</span></div>' +
            '<div>EquipReq: ' + equipReq + '</div>' +
            '<div>Level: ' + level + ' <span>Range: ' + range + '</span></div>' +
        '</div>' +
        '<div class="eqoa-spell-tooltip-desc">' +
            description +
        '</div>';
    tooltip.style.display = 'block';
    moveEqoaSpellPathTooltip(event);
}
function moveEqoaSpellPathTooltip(event) {
    var tooltip = getEqoaSpellPathTooltipBox();
    var left = event.pageX + 18;
    var top = event.pageY + 18;
    if (left + 430 > window.scrollX + window.innerWidth) {
        left = event.pageX - 430;
    }
    tooltip.style.left = left + 'px';
    tooltip.style.top = top + 'px';
}
function hideEqoaSpellPathTooltip() {
    var tooltip = document.getElementById('eqoa-spell-tooltip');
    if (tooltip) {
        tooltip.style.display = 'none';
    }
}
function formatEqoaSpellPathValue(value) {
    if (value === undefined || value === null || value === '') {
        return '0';
    }
    return escapeEqoaSpellPathHtml(value);
}
function eqoaSpellPathFileUrl(fileName) {
    fileName = String(fileName || '').trim();
    if (!fileName) return '';
    if (/^https?:\/\//i.test(fileName)) return fileName;
    return mw.config.get('wgScript') + '?title=Special:Redirect/file/' + encodeURIComponent(fileName);
}
function eqoaSpellPathPageUrl(title) {
    if (window.mw && mw.util && mw.util.getUrl) {
        return mw.util.getUrl(title);
    }
    return '/index.php?title=' + encodeURIComponent(String(title || '').replace(/ /g, '_'));
}
function escapeEqoaSpellPathHtml(value) {
    return String(value || '')
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#039;');
}
function initZoneNpcListFilters() {
    document.querySelectorAll('.zone-npc-list-wrapper').forEach(function (wrapper) {
        var table = wrapper.querySelector('.zone-monster-table');
        var bar;
        var input;
        var clear;
        if (!table) return;
        if (wrapper.classList.contains('zone-npc-filter-ready')) return;
        wrapper.classList.add('zone-npc-filter-ready');
        bar = document.createElement('div');
        bar.className = 'zone-npc-filter-bar';
        input = document.createElement('input');
        input.className = 'zone-npc-search-input';
        input.type = 'search';
        input.placeholder = 'Search NPCs';
        clear = document.createElement('button');
        clear.className = 'zone-npc-search-clear';
        clear.type = 'button';
        clear.textContent = 'Clear';
        bar.appendChild(input);
        bar.appendChild(clear);
        wrapper.insertBefore(bar, table);
        function filterRows() {
            var query = String(input.value || '').toLowerCase();
            var rows = table.querySelectorAll('tr');
            Array.prototype.forEach.call(rows, function (row, index) {
                var text;
                if (index === 0) return;
                text = String(row.textContent || '').toLowerCase();
                row.style.display = !query || text.indexOf(query) !== -1 ? '' : 'none';
            });
        }
        input.addEventListener('input', filterRows);
        clear.addEventListener('click', function () {
            input.value = '';
            filterRows();
            input.focus();
        });
    });
}
function initEqoaCategoryPageLists() {
    var boxes = document.querySelectorAll('.eqoa-category-page-list');
    if (!boxes.length) return;
    boxes.forEach(function (box) {
        var category = box.getAttribute('data-category');
        var api;
        if (!category) return;
        if (box.classList.contains('eqoa-category-page-list-ready')) return;
        box.classList.add('eqoa-category-page-list-ready');
        box.textContent = 'Loading...';
        api = new mw.Api();
        api.get({
            action: 'query',
            list: 'categorymembers',
            cmtitle: 'Category:' + category,
            cmnamespace: 0,
            cmlimit: 500,
            cmsort: 'sortkey',
            cmdir: 'asc',
            format: 'json'
        }).then(function (data) {
            var pages = data.query && data.query.categorymembers
                ? data.query.categorymembers
                : [];
            var list;
            box.innerHTML = '';
            if (!pages.length) {
                box.textContent = 'No pages found.';
                return;
            }
            list = document.createElement('ul');
            list.className = 'eqoa-category-page-list-items';
            pages.forEach(function (page) {
                var item = document.createElement('li');
                var link = document.createElement('a');
                link.href = mw.util.getUrl(page.title);
                link.textContent = page.title;
                item.appendChild(link);
                list.appendChild(item);
            });
            box.appendChild(list);
        }).catch(function () {
            box.textContent = 'Unable to load category.';
        });
    });
}
function initQuestRewardItemTooltips() {
    var links = document.querySelectorAll('.quest-reward-items a');
    var items = window.EQOA_GENERATED_ITEMS || [];
    if (!links.length) return;
    if (!items.length) {
        setTimeout(initQuestRewardItemTooltips, 500);
        return;
    }
    function normalizeQuestRewardRarity(rarity) {
        return String(rarity || 'common')
            .toLowerCase()
            .replace(/\s+/g, '-');
    }
    function getQuestRewardRarityStyle(rarity) {
        rarity = normalizeQuestRewardRarity(rarity);
        if (rarity === 'uncommon') {
            return {
                color: '#20ff45',
                shadow: '0 0 6px rgba(32, 255, 69, 0.55)'
            };
        }
        if (rarity === 'rare') {
            return {
                color: '#1e88ff',
                shadow: '0 0 6px rgba(30, 136, 255, 0.65)'
            };
        }
        if (rarity === 'ultra-rare') {
            return {
                color: '#ff3cff',
                shadow: '0 0 8px rgba(255, 60, 255, 0.75)'
            };
        }
        if (rarity === 'legendary') {
            return {
                color: '#ff8000',
                shadow: '0 0 8px rgba(255, 128, 0, 0.85)'
            };
        }
        if (rarity === 'unique') {
            return {
                color: '#ffd700',
                shadow: '0 0 8px rgba(255, 215, 0, 0.85)'
            };
        }
        return {
            color: '#ffffff',
            shadow: '0 0 6px rgba(255, 255, 255, 0.45)'
        };
    }
    function applyQuestRewardRarityStyle(link, item) {
        var rarity = normalizeQuestRewardRarity(item.rarity);
        var style = getQuestRewardRarityStyle(rarity);
        [
            'class-item-rarity-common',
            'class-item-rarity-uncommon',
            'class-item-rarity-rare',
            'class-item-rarity-ultra-rare',
            'class-item-rarity-legendary',
            'class-item-rarity-unique'
        ].forEach(function (className) {
            link.classList.remove(className);
        });
        link.classList.add('class-item-rarity-' + rarity);
        link.style.setProperty('color', style.color, 'important');
        link.style.setProperty('font-weight', '800', 'important');
        link.style.setProperty('text-shadow', style.shadow, 'important');
        link.style.setProperty('text-decoration-color', style.color, 'important');
    }
    links.forEach(function (link) {
        var itemName;
        var item;
        itemName = (link.getAttribute('data-class-item-name') || link.textContent || '')
            .replace(/_/g, ' ')
            .trim();
        try {
            itemName = decodeURIComponent(itemName);
        } catch (e) {}
        if (!itemName) return;
        item = items.find(function (entry) {
            return String(entry.name || '').toLowerCase() === itemName.toLowerCase();
        });
        if (!item) return;
        link.classList.add('quest-reward-tooltip-ready');
        link.classList.add('class-item-tooltip-link');
        link.setAttribute('data-class-item-name', encodeURIComponent(item.name));
        applyQuestRewardRarityStyle(link, item);
        if (link.getAttribute('data-quest-reward-tooltip-bound') === '1') return;
        link.setAttribute('data-quest-reward-tooltip-bound', '1');
        link.addEventListener('mouseenter', function (event) {
            applyQuestRewardRarityStyle(link, item);
            if (typeof showClassItemTooltip === 'function') {
                showClassItemTooltip(item, event);
            }
        });
        link.addEventListener('mousemove', function (event) {
            if (typeof moveClassItemTooltip === 'function') {
                moveClassItemTooltip(event);
            }
        });
        link.addEventListener('mouseleave', function () {
            applyQuestRewardRarityStyle(link, item);
            if (typeof hideClassItemTooltip === 'function') {
                hideClassItemTooltip();
            }
        });
    });
}
function initEqoaQuestIndexFilters() {
    document.querySelectorAll('.eqoa-quest-index-section').forEach(function (section) {
        var filterRow = section.querySelector('.eqoa-quest-filter-row');
        var filters = section.querySelectorAll('.eqoa-quest-filter');
        var panels = section.querySelectorAll('.eqoa-quest-panel');
        var search;
        if (!filterRow || !filters.length || !panels.length) return;
        search = section.querySelector('.eqoa-quest-search');
        if (!search) {
            search = document.createElement('input');
            search.type = 'search';
            search.className = 'eqoa-quest-search';
            search.placeholder = 'Search quests';
            filterRow.insertAdjacentElement('afterend', search);
        }
        function setActive(target) {
            filters.forEach(function (filter) {
                filter.classList.toggle(
                    'is-active',
                    filter.getAttribute('data-quest-filter') === target
                );
            });
            panels.forEach(function (panel) {
                var active = panel.getAttribute('data-quest-panel') === target;
                panel.classList.toggle('is-active', active);
                panel.style.display = active ? 'block' : 'none';
            });
            filterQuestList();
        }
        function filterQuestList() {
            var activePanel = section.querySelector('.eqoa-quest-panel.is-active');
            var query = String(search.value || '').toLowerCase();
            if (!activePanel) return;
            activePanel.querySelectorAll('li').forEach(function (li) {
                li.style.display = li.textContent.toLowerCase().indexOf(query) !== -1
                    ? ''
                    : 'none';
            });
        }
        if (!section.classList.contains('eqoa-quest-index-ready')) {
            section.classList.add('eqoa-quest-index-ready');
            filters.forEach(function (filter) {
                filter.addEventListener('click', function () {
                    setActive(filter.getAttribute('data-quest-filter'));
                });
            });
            search.addEventListener('input', filterQuestList);
        }
        var activeFilter = section.querySelector('.eqoa-quest-filter.is-active') || filters[0];
        setActive(activeFilter.getAttribute('data-quest-filter'));
    });
}
mw.hook('wikipage.content').add(initEqoaQuestIndexFilters);
document.addEventListener('DOMContentLoaded', initEqoaQuestIndexFilters);
setTimeout(initEqoaQuestIndexFilters, 500);
setTimeout(initEqoaQuestIndexFilters, 1500);
       
function initEqoaQuestIndexExpandableSections() {
    document.querySelectorAll('.eqoa-quest-index-section').forEach(function (section) {
        var title = section.querySelector(':scope > .eqoa-section-title');
        if (!title) return;
        if (section.classList.contains('eqoa-quest-expand-ready')) return;
        section.classList.add('eqoa-quest-expand-ready');
        title.addEventListener('click', function () {
            section.classList.toggle('is-open');
        });
    });
}
mw.hook('wikipage.content').add(initEqoaQuestIndexExpandableSections);
document.addEventListener('DOMContentLoaded', initEqoaQuestIndexExpandableSections);
setTimeout(initEqoaQuestIndexExpandableSections, 500);
setTimeout(initEqoaQuestIndexExpandableSections, 1500);
function centerSoaHomeBanner() {
    document.querySelectorAll('.soa-home-banner-wrap').forEach(function (wrap) {
        var image = wrap.querySelector('img');
        if (!image) return;
        wrap.style.display = 'flex';
        wrap.style.justifyContent = 'center';
        wrap.style.alignItems = 'center';
        wrap.style.width = '100%';
        wrap.style.overflow = 'hidden';
        wrap.style.background = 'transparent';
        image.style.display = 'block';
        image.style.width = '100%';
        image.style.maxWidth = '1200px';
        image.style.height = 'auto';
        image.style.margin = '0 auto';
        image.parentElement.style.display = 'block';
        image.parentElement.style.margin = '0 auto';
    });
}
document.addEventListener('DOMContentLoaded', centerSoaHomeBanner);
mw.hook('wikipage.content').add(centerSoaHomeBanner);
setTimeout(centerSoaHomeBanner, 250);
setTimeout(centerSoaHomeBanner, 1000);
function moveMobilePageActionsAfterNavigation() {
    var isMobile = window.matchMedia && window.matchMedia('(max-width: 700px)').matches;
    var actions = document.getElementById('p-cactions');
    var sidebar = document.getElementById('sidebar');
    var navigation = document.getElementById('p-navigation');
    var column = document.getElementById('column-one');
    var logo = document.getElementById('p-logo');
    if (!actions || !column) return;
    if (isMobile) {
        if (navigation && navigation.parentNode) {
            navigation.insertAdjacentElement('afterend', actions);
        } else if (sidebar) {
            sidebar.appendChild(actions);
        } else {
            column.appendChild(actions);
        }
        actions.classList.add('eqoa-mobile-page-actions-bottom');
        return;
    }
    actions.classList.remove('eqoa-mobile-page-actions-bottom');
    if (logo && logo.parentNode === column) {
        column.insertBefore(actions, logo);
    } else {
        column.insertBefore(actions, column.firstChild);
    }
}
document.addEventListener('DOMContentLoaded', moveMobilePageActionsAfterNavigation);
mw.hook('wikipage.content').add(moveMobilePageActionsAfterNavigation);
window.addEventListener('resize', moveMobilePageActionsAfterNavigation);
window.addEventListener('orientationchange', function () {
    setTimeout(moveMobilePageActionsAfterNavigation, 150);
});
setTimeout(moveMobilePageActionsAfterNavigation, 250);
setTimeout(moveMobilePageActionsAfterNavigation, 1000);
function initNpcPageItemTooltips() {
    var links = document.querySelectorAll('.eqoa-npc-page .eqoa-npc-zone-table td:nth-child(4) a');
    var items = window.EQOA_GENERATED_ITEMS || [];
    if (!links.length) return;
    if (!items.length) {
        setTimeout(initNpcPageItemTooltips, 500);
        return;
    }
    function normalizeNpcDropName(text) {
        return String(text || '')
            .replace(/\u2019/g, "'")
            .replace(/\s*\(\s*\d+%\s*\)\s*$/g, '')
            .replace(/\s+/g, ' ')
            .trim()
            .toLowerCase();
    }
    function normalizeNpcDropRarity(rarity) {
        return String(rarity || 'common')
            .toLowerCase()
            .replace(/\s+/g, '-');
    }
    function getNpcDropRarityStyle(rarity) {
        rarity = normalizeNpcDropRarity(rarity);
        if (rarity === 'uncommon') {
            return { color: '#20ff45', shadow: '0 0 6px rgba(32, 255, 69, 0.55)' };
        }
        if (rarity === 'rare') {
            return { color: '#1e88ff', shadow: '0 0 6px rgba(30, 136, 255, 0.65)' };
        }
        if (rarity === 'ultra-rare') {
            return { color: '#ff3cff', shadow: '0 0 8px rgba(255, 60, 255, 0.75)' };
        }
        if (rarity === 'legendary') {
            return { color: '#ff8000', shadow: '0 0 8px rgba(255, 128, 0, 0.85)' };
        }
        if (rarity === 'unique') {
            return { color: '#ffd700', shadow: '0 0 8px rgba(255, 215, 0, 0.85)' };
        }
        return { color: '#ffffff', shadow: '0 0 6px rgba(255, 255, 255, 0.45)' };
    }
    function getNpcDropItemFromLink(link) {
        var names = [];
        var href = link.getAttribute('href') || '';
        names.push(link.textContent || '');
        names.push(link.getAttribute('title') || '');
        names.push(link.getAttribute('data-class-item-name') || '');
        try {
            if (href.indexOf('title=') !== -1) {
                names.push(decodeURIComponent(href.split('title=')[1].split('&')[0]).replace(/_/g, ' '));
            } else {
                names.push(decodeURIComponent(href.substring(href.lastIndexOf('/') + 1)).replace(/_/g, ' '));
            }
        } catch (e) {}
        names = names.map(normalizeNpcDropName).filter(Boolean);
        return items.find(function (item) {
            var itemName = normalizeNpcDropName(item.name);
            return names.indexOf(itemName) !== -1;
        });
    }
function applyNpcDropRarityStyle(link, item) {
    var rarity = normalizeNpcDropRarity(item.rarity);
    var style = getNpcDropRarityStyle(rarity);
    var textSpan;
    [
        'class-item-rarity-common',
        'class-item-rarity-uncommon',
        'class-item-rarity-rare',
        'class-item-rarity-ultra-rare',
        'class-item-rarity-legendary',
        'class-item-rarity-unique'
    ].forEach(function (className) {
        link.classList.remove(className);
    });
    link.classList.add('class-item-tooltip-link');
    link.classList.add('class-item-rarity-' + rarity);
    link.setAttribute('data-class-item-name', encodeURIComponent(item.name));
    /*
    * Remove browser-native hover tooltip.
    * The CB-style tooltip handles item display instead.
    */
    link.removeAttribute('title');
    textSpan = link.querySelector('.npc-drop-rarity-text');
    if (!textSpan) {
        textSpan = document.createElement('span');
        textSpan.className = 'npc-drop-rarity-text';
        textSpan.textContent = link.textContent;
        link.textContent = '';
        link.appendChild(textSpan);
    }
    textSpan.textContent = item.name;
    link.style.setProperty('font-weight', '800', 'important');
    link.style.setProperty('text-decoration-color', style.color, 'important');
    textSpan.style.setProperty('color', style.color, 'important');
    textSpan.style.setProperty('-webkit-text-fill-color', style.color, 'important');
    textSpan.style.setProperty('font-weight', '800', 'important');
    textSpan.style.setProperty('text-shadow', style.shadow, 'important');
    textSpan.style.setProperty('text-decoration-color', style.color, 'important');
}
    links.forEach(function (link) {
        var item = getNpcDropItemFromLink(link);
        if (!item) return;
        applyNpcDropRarityStyle(link, item);
        if (link.getAttribute('data-npc-item-tooltip-bound') === '1') return;
        link.setAttribute('data-npc-item-tooltip-bound', '1');
        link.addEventListener('mouseenter', function (event) {
            applyNpcDropRarityStyle(link, item);
            if (typeof showClassItemTooltip === 'function') {
                showClassItemTooltip(item, event);
            }
        });
        link.addEventListener('mousemove', function (event) {
            applyNpcDropRarityStyle(link, item);
            if (typeof moveClassItemTooltip === 'function') {
                moveClassItemTooltip(event);
            }
        });
        link.addEventListener('mouseleave', function () {
            applyNpcDropRarityStyle(link, item);
            if (typeof hideClassItemTooltip === 'function') {
                hideClassItemTooltip();
            }
        });
    });
}
mw.hook('wikipage.content').add(initNpcPageItemTooltips);
document.addEventListener('DOMContentLoaded', initNpcPageItemTooltips);
setTimeout(initNpcPageItemTooltips, 500);
setTimeout(initNpcPageItemTooltips, 1500);
setTimeout(initNpcPageItemTooltips, 3000);
setTimeout(initNpcPageItemTooltips, 5000);
mw.hook('wikipage.content').add(initQuestRewardItemTooltips);
document.addEventListener('DOMContentLoaded', initQuestRewardItemTooltips);
setTimeout(initQuestRewardItemTooltips, 500);
setTimeout(initQuestRewardItemTooltips, 1500);
setTimeout(initQuestRewardItemTooltips, 3000);
mw.hook('wikipage.content').add(initQuestRewardItemTooltips);
document.addEventListener('DOMContentLoaded', initQuestRewardItemTooltips);
setTimeout(initQuestRewardItemTooltips, 500);
setTimeout(initQuestRewardItemTooltips, 1500);
mw.hook('wikipage.content').add(initEqoaCategoryPageLists);
document.addEventListener('DOMContentLoaded', initEqoaCategoryPageLists);
setTimeout(initEqoaCategoryPageLists, 500);
mw.hook('wikipage.content').add(initZoneNpcListFilters);
document.addEventListener('DOMContentLoaded', initZoneNpcListFilters);
setTimeout(initZoneNpcListFilters, 500);
mw.hook('wikipage.content').add(initEqoaSpellPathLists);
document.addEventListener('DOMContentLoaded', initEqoaSpellPathLists);
setTimeout(initEqoaSpellPathLists, 500);
setTimeout(initEqoaSpellPathLists, 1500);
setTimeout(initEqoaSpellPathLists, 3000);
function hidePageActionButtonsForNonContributors() {
    var username = mw.config.get('wgUserName') || '';
    var groups = mw.config.get('wgUserGroups') || [];
    var canSeePageActions =
        username === 'DevRenodor' ||
        groups.indexOf('contributor') !== -1 ||
        groups.indexOf('administrator') !== -1 ||
        groups.indexOf('bureaucrat') !== -1 ||
        groups.indexOf('interface-admin') !== -1;
    var actionIds = [
        'ca-view',
        'ca-edit',
        'ca-ve-edit',
        'ca-viewsource',
        'ca-history'
    ];
    actionIds.forEach(function (id) {
        var action = document.getElementById(id);
        if (!action) return;
        action.style.setProperty('display', canSeePageActions ? '' : 'none', 'important');
    });
}
document.addEventListener('DOMContentLoaded', hidePageActionButtonsForNonContributors);
mw.hook('wikipage.content').add(hidePageActionButtonsForNonContributors);
setTimeout(hidePageActionButtonsForNonContributors, 250);
setTimeout(hidePageActionButtonsForNonContributors, 1000);
function initSandstormChangesHeaderCollapse() {
    var page = document.querySelector('.eqoa-changes-page');
    if (!page) return;
    Array.prototype.forEach.call(
        page.querySelectorAll('.eqoa-changes-section.mw-collapsible'),
        function (section) {
            var header = section.querySelector('.eqoa-header');
            var defaultToggle = section.querySelector('.mw-collapsible-toggle');
            var plus;
            if (!header) return;
            if (section.classList.contains('eqoa-change-header-collapse-ready')) return;
            plus = document.createElement('span');
            plus.className = 'eqoa-change-header-plus';
            plus.textContent = section.classList.contains('mw-collapsed') ? '+' : '-';
            header.appendChild(plus);
            function updatePlus() {
                plus.textContent = section.classList.contains('mw-collapsed') ? '+' : '-';
            }
            header.addEventListener('click', function (event) {
                if (event.target.closest('a')) return;
                defaultToggle = section.querySelector('.mw-collapsible-toggle');
                if (defaultToggle) {
                    defaultToggle.click();
                    setTimeout(updatePlus, 50);
                    return;
                }
                section.classList.toggle('mw-collapsed');
                updatePlus();
            });
            new MutationObserver(updatePlus).observe(section, {
                attributes: true,
                attributeFilter: ['class']
            });
            updatePlus();
            section.classList.add('eqoa-change-header-collapse-ready');
        }
    );
}
mw.hook('wikipage.content').add(initSandstormChangesHeaderCollapse);
document.addEventListener('DOMContentLoaded', initSandstormChangesHeaderCollapse);
setTimeout(initSandstormChangesHeaderCollapse, 500);
setTimeout(initSandstormChangesHeaderCollapse, 1500);
function initEqoaExpCampToggleIcons(root) {
    root = root && root.jquery ? root[0] : (root || document);
    root.querySelectorAll('table.exp-camp-table.mw-collapsible caption').forEach(function (caption) {
        var table = caption.closest('table.exp-camp-table');
        var toggle = caption.querySelector('.mw-collapsible-toggle');
        if (!table || !toggle) return;
        function removeDefaultBrackets() {
            [caption, toggle].forEach(function (scope) {
                if (!scope) return;
                scope.querySelectorAll('.mw-collapsible-bracket').forEach(function (bracket) {
                    bracket.remove();
                });
                Array.from(scope.childNodes).forEach(function (node) {
                    if (node.nodeType === 3) {
                        node.nodeValue = node.nodeValue.replace(/\[|\]/g, '');
                    }
                    if (
                        node.nodeType === 1 &&
                        (
                            node.classList.contains('mw-collapsible-bracket') ||
                            node.textContent.trim() === '[' ||
                            node.textContent.trim() === ']'
                        )
                    ) {
                        node.remove();
                    }
                });
            });
        }
        function updateToggleIcon() {
            var isCollapsed =
                table.classList.contains('mw-collapsed') ||
                toggle.classList.contains('mw-collapsible-toggle-collapsed') ||
                toggle.getAttribute('aria-expanded') === 'false';
            removeDefaultBrackets();
            toggle.textContent = isCollapsed ? '+' : '−';
            toggle.title = isCollapsed ? 'Expand' : 'Collapse';
            toggle.setAttribute('aria-label', isCollapsed ? 'Expand' : 'Collapse');
            removeDefaultBrackets();
        }
        if (!caption.classList.contains('eqoa-exp-caption-ready')) {
            caption.classList.add('eqoa-exp-caption-ready');
            caption.addEventListener('click', function (event) {
                if (event.target.closest('.mw-collapsible-toggle')) {
                    setTimeout(updateToggleIcon, 0);
                    setTimeout(updateToggleIcon, 100);
                    return;
                }
                event.preventDefault();
                toggle.click();
                setTimeout(updateToggleIcon, 0);
                setTimeout(updateToggleIcon, 100);
            });
        }
        updateToggleIcon();
    });
}
mw.hook('wikipage.content').add(initEqoaExpCampToggleIcons);
document.addEventListener('DOMContentLoaded', function () {
    initEqoaExpCampToggleIcons(document);
});
setTimeout(function () {
    initEqoaExpCampToggleIcons(document);
}, 500);
setTimeout(function () {
    initEqoaExpCampToggleIcons(document);
}, 1500);
function initEqoaTemplateCopyButtons(root) {
    root = root && root.jquery ? root[0] : (root || document);
    root.querySelectorAll('.eqoa-template-copy-button').forEach(function (button) {
        if (button.classList.contains('eqoa-template-copy-ready')) return;
        button.classList.add('eqoa-template-copy-ready');
        button.addEventListener('click', function () {
            var card = button.closest('.eqoa-template-card');
            var code = card ? card.querySelector('.eqoa-template-code') : null;
            var text = code ? code.textContent.trim() : '';
            if (!text) return;
            navigator.clipboard.writeText(text).then(function () {
                button.textContent = 'Copied';
                setTimeout(function () {
                    button.textContent = 'Copy';
                }, 1200);
            });
        });
    });
}
mw.hook('wikipage.content').add(initEqoaTemplateCopyButtons);
document.addEventListener('DOMContentLoaded', function () {
    initEqoaTemplateCopyButtons(document);
});
function initEqoaZoneTabsLocked(root) {
    root = root && root.jquery ? root[0] : (root || document);
    root.querySelectorAll('.eqoa-zone-tabs').forEach(function (tabs) {
        if (tabs.classList.contains('eqoa-zone-tabs-locked-ready')) return;
        tabs.classList.add('eqoa-zone-tabs-locked-ready');
        tabs.querySelectorAll('.eqoa-zone-tab-button[data-tab]').forEach(function (button) {
            button.addEventListener('click', function () {
                var tabName = button.getAttribute('data-tab');
                tabs.querySelectorAll('.eqoa-zone-tab-button[data-tab]').forEach(function (btn) {
                    btn.classList.toggle('is-active', btn === button);
                });
                tabs.querySelectorAll('.eqoa-zone-tab-panel[data-tab-panel]').forEach(function (panel) {
                    panel.classList.toggle(
                        'is-active',
                        panel.getAttribute('data-tab-panel') === tabName
                    );
                });
            });
        });
    });
}
mw.hook('wikipage.content').add(initEqoaZoneTabsLocked);
document.addEventListener('DOMContentLoaded', function () {
    initEqoaZoneTabsLocked(document);
});
setTimeout(function () {
    initEqoaZoneTabsLocked(document);
}, 500);
/* ===== Zone NPC Tab Auto-Fill From Category ===== */
(function () {
    function normalizeEqoaText(value) {
        return String(value || '')
            .replace(/\s+/g, ' ')
            .trim()
            .toLowerCase();
    }
    function getCurrentZoneName() {
        return mw.config.get('wgTitle') || mw.config.get('wgPageName') || '';
    }
    function getNpcFamilyFromDoc(doc) {
        var rows = doc.querySelectorAll('.eqoa-npc-info-table tr');
        for (var i = 0; i < rows.length; i++) {
            var cells = rows[i].querySelectorAll('td');
            var label;
            if (cells.length < 2) continue;
            label = normalizeEqoaText(cells[0].textContent);
            if (label.indexOf('family') !== -1) {
                return cells[1].innerHTML.trim();
            }
        }
        return '';
    }
function getNpcZoneRowsFromDoc(doc, zoneName) {
    var wantedZone = normalizeEqoaText(zoneName);
    var rows = doc.querySelectorAll('.eqoa-npc-zone-table tr');
    var matches = [];
    for (var i = 0; i < rows.length; i++) {
        var cells = rows[i].querySelectorAll('td');
        if (cells.length < 7) continue;
        if (normalizeEqoaText(cells[0].textContent) === wantedZone) {
            matches.push({
                level: cells[1].innerHTML.trim(),
                drops: cells[3].innerHTML.trim(),
                quest: cells[5].innerHTML.trim(),
                notes: cells[6].innerHTML.trim()
            });
        }
    }
    return matches;
}
    function buildNpcLink(pageTitle) {
        var link = document.createElement('a');
        link.href = mw.util.getUrl(pageTitle);
        link.textContent = pageTitle;
        return link;
    }
function loadZoneNpcPage(api, pageTitle, zoneName) {
    return api.get({
        action: 'parse',
        page: pageTitle,
        prop: 'text',
        format: 'json'
    }).then(function (data) {
        var html = data && data.parse && data.parse.text && data.parse.text['*'];
        var doc;
        var zoneRows;
        var family;
        if (!html) return [];
        doc = new DOMParser().parseFromString(html, 'text/html');
        zoneRows = getNpcZoneRowsFromDoc(doc, zoneName);
        family = getNpcFamilyFromDoc(doc);
        if (!zoneRows.length) {
            return [{
                name: pageTitle,
                level: '',
                drops: '',
                family: family || '',
                notes: ''
            }];
        }
        return zoneRows.map(function (zoneRow) {
            return {
                name: pageTitle,
                level: zoneRow.level || '',
                drops: zoneRow.drops || '',
                family: family || '',
                notes: zoneRow.notes || ''
            };
        });
    });
}
    function renderZoneNpcRows(table, npcs) {
        var tbody = table.querySelector('tbody');
        if (!tbody) {
            tbody = document.createElement('tbody');
            table.appendChild(tbody);
        }
        tbody.innerHTML = '';
        if (!npcs.length) {
            tbody.innerHTML =
                '<tr class="zone-npc-row">' +
                    '<td colspan="5">No NPCs found.</td>' +
                '</tr>';
            return;
        }
        npcs.forEach(function (npc) {
            var row = document.createElement('tr');
            var nameCell = document.createElement('td');
            row.className = 'zone-npc-row';
            nameCell.className = 'zone-monster-name';
            nameCell.appendChild(buildNpcLink(npc.name));
            row.appendChild(nameCell);
            row.innerHTML +=
                '<td class="zone-monster-level">' + (npc.level || '') + '</td>' +
                '<td class="zone-monster-drops">' + (npc.drops || '') + '</td>' +
                '<td class="zone-monster-family">' + (npc.family || '') + '</td>' +
                '<td class="zone-monster-notes">' + (npc.notes || '') + '</td>';
            tbody.appendChild(row);
        });
    }
    function initZoneNpcCategoryAutoFill(root) {
        var zoneName = getCurrentZoneName();
        var categoryName = zoneName + ' NPCs';
        var panel;
        var table;
        var api;
        root = root && root.jquery ? root[0] : (root || document);
        panel = root.querySelector('[data-tab-panel="npcs"]');
        if (!panel || panel.classList.contains('eqoa-zone-npc-auto-ready')) return;
        table = panel.querySelector('.zone-monster-table');
        if (!table) return;
        panel.classList.add('eqoa-zone-npc-auto-ready');
        api = new mw.Api();
        api.get({
            action: 'query',
            list: 'categorymembers',
            cmtitle: 'Category:' + categoryName,
            cmnamespace: 0,
            cmlimit: 500,
            format: 'json'
        }).then(function (data) {
            var members = data &&
                data.query &&
                data.query.categorymembers
                ? data.query.categorymembers
                : [];
            return Promise.all(
                members.map(function (member) {
                    return loadZoneNpcPage(api, member.title, zoneName);
                })
            );
}).then(function (rows) {
    rows = rows.reduce(function (allRows, npcRows) {
        return allRows.concat(npcRows || []);
    }, []);
    rows = rows.filter(Boolean).sort(function (a, b) {
        var nameCompare = a.name.localeCompare(b.name);
        if (nameCompare !== 0) return nameCompare;
        return String(a.level || '').localeCompare(String(b.level || ''));
    });
            renderZoneNpcRows(table, rows);
            if (typeof initGeneratedItemTooltipsForWiki === 'function') {
                initGeneratedItemTooltipsForWiki(panel);
            }
            if (typeof initZoneNpcSearch === 'function') {
                initZoneNpcSearch(panel);
            }
            if (window.jQuery && jQuery.fn && jQuery.fn.tablesorter) {
                jQuery(table).trigger('destroy');
                jQuery(table).tablesorter();
            }
        }).catch(function (error) {
            console.error('EQOA zone NPC auto-fill failed:', error);
        });
    }
    mw.hook('wikipage.content').add(initZoneNpcCategoryAutoFill);
    document.addEventListener('DOMContentLoaded', function () {
        initZoneNpcCategoryAutoFill(document);
    });
    setTimeout(function () {
        initZoneNpcCategoryAutoFill(document);
    }, 500);
    setTimeout(function () {
        initZoneNpcCategoryAutoFill(document);
    }, 1500);
})();

Latest revision as of 02:50, 16 July 2026

mw.loader.load(
    mw.config.get('wgScript') +
    '?title=MediaWiki:AchievementTracker.js' +
    '&action=raw' +
    '&ctype=text/javascript' +
    '&v=20260716-1'
);

/* Any JavaScript here will be loaded for all users on every page load. */

(function () {
    function rawPage(title) {
        return mw.config.get('wgScript') +
            '?title=' + encodeURIComponent(title) +
            '&action=raw&ctype=text/javascript';
    }

    function loadScriptUrl(url) {
        return $.getScript(url);
    }

    function loadWikiScript(title) {
        return loadScriptUrl(rawPage(title));
    }

    function applyWikiTheme(theme) {
        var isLight = theme === 'light';
        var button = document.getElementById('eqoa-theme-toggle');

        document.documentElement.classList.toggle('eqoa-theme-light', isLight);
        document.documentElement.classList.toggle('eqoa-theme-dark', !isLight);

        if (document.body) {
            document.body.classList.toggle('eqoa-theme-light', isLight);
            document.body.classList.toggle('eqoa-theme-dark', !isLight);
        }

        try {
            localStorage.setItem('eqoa-wiki-theme', isLight ? 'light' : 'dark');
            localStorage.setItem('eqoa-builder-theme', isLight ? 'light' : 'dark');
            localStorage.setItem('eqoa-cm-theme', isLight ? 'light' : 'dark');
        } catch (e) {}

        if (button) {
            button.innerHTML = isLight ? '&#9790;' : '&#9788;';
            button.title = isLight ? 'Switch to Dark Theme' : 'Switch to White Theme';
            button.setAttribute(
                'aria-label',
                isLight ? 'Switch to Dark Theme' : 'Switch to White Theme'
            );
        }

       if (window.dispatchEvent) {
    window.dispatchEvent(new CustomEvent('eqoa-wiki-theme-change', {
        detail: {
            theme: isLight ? 'light' : 'dark'
        }
    }));
}

setTimeout(styleRareEncounterRows, 50);
setTimeout(buildRareEncounterMobileLayout, 50);
    }

    function getSavedWikiTheme() {
        try {
            return localStorage.getItem('eqoa-wiki-theme') === 'light'
                ? 'light'
                : 'dark';
        } catch (e) {
            return 'dark';
        }
    }

    function ensureThemeButton() {
        var button = document.getElementById('eqoa-theme-toggle');

        if (!button) {
            button = document.createElement('button');
            button.id = 'eqoa-theme-toggle';
            button.type = 'button';
            button.className = 'eqoa-theme-toggle';

            button.addEventListener('click', function () {
                var isLight = document.body.classList.contains('eqoa-theme-light');
                applyWikiTheme(isLight ? 'dark' : 'light');
            });

            document.body.appendChild(button);
        }

        applyWikiTheme(getSavedWikiTheme());
    }

    function initRaidChecks() {
        var containers = document.querySelectorAll('.raid-checkboxify');

        Array.prototype.forEach.call(containers, function (container) {
            var links = container.querySelectorAll('a');

            Array.prototype.forEach.call(links, function (a) {
                var cb;
                var label;
                var key;
                var stored;

                if (a.classList.contains('raid-processed')) return;

                cb = document.createElement('input');
                cb.type = 'checkbox';
                cb.className = 'raid-checkbox-input';
                cb.style.marginRight = '8px';
                cb.style.verticalAlign = 'middle';

                label = document.createElement('label');
                label.style.display = 'block';
                label.style.cursor = 'pointer';

                a.parentNode.insertBefore(label, a);
                label.appendChild(cb);
                label.appendChild(a);

                a.classList.add('raid-processed');

                key = 'raidcheck:' +
                    (document.title || location.pathname) +
                    ':' +
                    a.getAttribute('href');

                try {
                    stored = localStorage.getItem(key);
                    if (stored === '1') cb.checked = true;
                } catch (e) {}

                cb.addEventListener('change', function () {
                    try {
                        if (cb.checked) {
                            localStorage.setItem(key, '1');
                        } else {
                            localStorage.removeItem(key);
                        }
                    } catch (e) {}
                });
            });
        });
    }


function styleRareEncounterRows() {
    var isLightTheme =
        document.documentElement.classList.contains('eqoa-theme-light') ||
        document.body.classList.contains('eqoa-theme-light');

    var headerBg = isLightTheme ? '#3a2418' : '#3a3a3a';
    var headerColor = isLightTheme ? '#ffffff' : '#ffd45a';
    var headerBorder = isLightTheme ? '#5a3a25' : '#1f1f1f';

    var rowBg1 = isLightTheme ? '#ffffff' : '#0d0c0b';
    var rowBg2 = isLightTheme ? '#f7f5f1' : '#242428';
    var rowColor = isLightTheme ? '#1f140c' : '#ffd45a';
    var rowBorder = isLightTheme ? '#e6ded4' : '#252525';

    document.querySelectorAll('.raid-encounter-table table').forEach(function (table) {
        table.style.width = '100%';
        table.style.tableLayout = 'fixed';
        table.style.borderCollapse = 'collapse';

        table.querySelectorAll('th').forEach(function (th) {
            th.style.background = headerBg;
            th.style.color = headerColor;
            th.style.border = '1px solid ' + headerBorder;
            th.style.padding = '8px';
            th.style.textAlign = 'center';
            th.style.fontWeight = 'bold';
        });

        table.querySelectorAll('td').forEach(function (td) {
            td.style.verticalAlign = 'top';
            td.style.padding = '0';
            td.style.border = '1px solid ' + rowBorder;
            td.style.background = rowBg1;
        });
    });

    document.querySelectorAll('.raid-encounter-table .raid-checkboxify').forEach(function (container) {
        var rowIndex = 0;

        container.querySelectorAll('br').forEach(function (br) {
            br.style.display = 'none';
        });

        container.querySelectorAll('b').forEach(function (zone) {
            zone.style.display = 'block';
            zone.style.background = headerBg;
            zone.style.color = headerColor;
            zone.style.borderTop = '1px solid ' + headerBorder;
            zone.style.borderBottom = '1px solid ' + headerBorder;
            zone.style.padding = '7px 8px';
            zone.style.margin = '0';
            zone.style.textAlign = 'center';
            zone.style.fontSize = '15px';
            zone.style.lineHeight = '1.15';
            zone.style.fontWeight = 'bold';
        });

        container.querySelectorAll('label').forEach(function (label) {
            rowIndex += 1;

            label.style.display = 'block';
            label.style.background = rowIndex % 2 === 0 ? rowBg2 : rowBg1;
            label.style.color = rowColor;
            label.style.borderBottom = '1px solid ' + rowBorder;
            label.style.padding = '6px 8px';
            label.style.margin = '0';
            label.style.lineHeight = '1.25';
            label.style.fontSize = '13px';
            label.style.cursor = 'pointer';

            label.querySelectorAll('a').forEach(function (link) {
                link.style.color = rowColor;
                link.style.fontWeight = 'bold';
                link.style.textDecoration = 'none';
            });
        });
    });
}


function initItemFilters() {
    var table = document.getElementById('items-found-here-table');
    var box;
    var searchInput;
    var classSelect;
    var sortSelect;
    var sortButton;
    var clearButton;

    if (!table) return;
    if (document.getElementById('items-filter-box')) return;

    box = document.createElement('div');
    box.id = 'items-filter-box';
    box.className = 'items-filter-box class-item-filter-panel';

    box.innerHTML =
        '<div class="items-filter-left">' +
            '<input type="search" id="filter-item-search" placeholder="Search item">' +
        '</div>' +
        '<div class="items-filter-right">' +
            '<select id="items-found-here-sort-select">' +
                '<option value="name">Sort: Name</option>' +
                '<option value="level">Sort: Level</option>' +
                '<option value="class">Sort: Class</option>' +
                '<option value="dmg">Sort: DMG</option>' +
                '<option value="ac">Sort: AC</option>' +
                '<option value="str">Sort: STR</option>' +
                '<option value="sta">Sort: STA</option>' +
                '<option value="agi">Sort: AGI</option>' +
                '<option value="wis">Sort: WIS</option>' +
                '<option value="dex">Sort: DEX</option>' +
                '<option value="cha">Sort: CHA</option>' +
                '<option value="int">Sort: INT</option>' +
                '<option value="hp">Sort: HP</option>' +
                '<option value="pow">Sort: POW</option>' +
                '<option value="pr">Sort: PR</option>' +
                '<option value="dr">Sort: DR</option>' +
                '<option value="fr">Sort: FR</option>' +
                '<option value="cr">Sort: CR</option>' +
                '<option value="lr">Sort: LR</option>' +
                '<option value="ar">Sort: AR</option>' +
                '<option value="dur">Sort: DUR</option>' +
            '</select>' +
            '<button id="items-found-here-sort-button" type="button">Sort</button>' +
            '<select id="filter-class-select">' +
                '<option value="">All Classes</option>' +
                '<option value="WAR">WAR</option>' +
                '<option value="RAN">RAN</option>' +
                '<option value="PAL">PAL</option>' +
                '<option value="SK">SK</option>' +
                '<option value="MNK">MNK</option>' +
                '<option value="BRD">BRD</option>' +
                '<option value="RGE">RGE</option>' +
                '<option value="DRD">DRD</option>' +
                '<option value="SHA">SHA</option>' +
                '<option value="CL">CL</option>' +
                '<option value="MAG">MAG</option>' +
                '<option value="NEC">NEC</option>' +
                '<option value="ENC">ENC</option>' +
                '<option value="WIZ">WIZ</option>' +
                '<option value="ALC">ALC</option>' +
            '</select>' +
            '<button id="clear-item-filters" type="button">Clear</button>' +
        '</div>';

    table.parentNode.insertBefore(box, table);

    searchInput = document.getElementById('filter-item-search');
    classSelect = document.getElementById('filter-class-select');
    sortSelect = document.getElementById('items-found-here-sort-select');
    sortButton = document.getElementById('items-found-here-sort-button');
    clearButton = document.getElementById('clear-item-filters');

    box.style.setProperty('display', 'grid', 'important');
    box.style.setProperty('grid-template-columns', '1fr auto', 'important');
    box.style.setProperty('align-items', 'center', 'important');
    box.style.setProperty('gap', '12px', 'important');

    box.querySelector('.items-filter-left').style.setProperty('display', 'flex', 'important');
    box.querySelector('.items-filter-left').style.setProperty('justify-content', 'flex-start', 'important');

    box.querySelector('.items-filter-right').style.setProperty('display', 'flex', 'important');
    box.querySelector('.items-filter-right').style.setProperty('justify-content', 'flex-end', 'important');
    box.querySelector('.items-filter-right').style.setProperty('gap', '8px', 'important');

    [searchInput, classSelect, sortSelect, sortButton, clearButton].forEach(function (control) {
        if (!control) return;

        control.style.setProperty('height', '34px', 'important');
        control.style.setProperty('box-sizing', 'border-box', 'important');
        control.style.setProperty('margin', '0', 'important');
        control.style.setProperty('vertical-align', 'middle', 'important');
    });

    searchInput.style.setProperty('width', '660px', 'important');
    searchInput.style.setProperty('max-width', '100%', 'important');

    sortSelect.style.setProperty('width', '128px', 'important');
    classSelect.style.setProperty('width', '128px', 'important');

    sortButton.style.setProperty('width', '58px', 'important');
    clearButton.style.setProperty('width', '64px', 'important');

    function getCellText(row, index) {
        return row.cells[index] ? row.cells[index].textContent.trim() : '';
    }

    function getRowClassText(row) {
        return (
            row.getAttribute('data-class') ||
            getCellText(row, 2) ||
            ''
        ).toUpperCase();
    }

    function classMatches(row, selectedClass) {
        var classText = getRowClassText(row);

        if (!selectedClass) return true;
        if (classText === 'ALL') return true;
        if (classText.indexOf('ALL') !== -1) return true;

        return classText.split(/[\s,\/]+/).indexOf(selectedClass) !== -1;
    }

    function filterTable() {
        var query = String(searchInput.value || '').toLowerCase();
        var selectedClass = classSelect.value;
        var rows = table.querySelectorAll('tr.items-found-here-row');

        Array.prototype.forEach.call(rows, function (row) {
            var rowText = [
                row.textContent,
                row.getAttribute('data-name'),
                row.getAttribute('data-level'),
                row.getAttribute('data-class'),
                row.getAttribute('data-slot'),
                row.getAttribute('data-rarity')
            ].join(' ').toLowerCase();

            var show = true;

            if (query && rowText.indexOf(query) === -1) show = false;
            if (!classMatches(row, selectedClass)) show = false;

            row.style.display = show ? '' : 'none';
        });
    }

    function getSortValue(row, key) {
        var value = row.getAttribute('data-' + key) || '';

        if (!value) {
            if (key === 'name') value = getCellText(row, 0);
            if (key === 'level') value = getCellText(row, 1);
            if (key === 'class') value = getCellText(row, 2);
        }

        return String(value || '');
    }

    function sortTable() {
        var tbody = table.querySelector('tbody') || table;
        var rows = Array.prototype.slice.call(table.querySelectorAll('tr.items-found-here-row'));
        var key = sortSelect.value;

        rows.sort(function (a, b) {
            var aValue = getSortValue(a, key);
            var bValue = getSortValue(b, key);
            var aNumber = parseFloat(aValue.replace(/[^\d.-]/g, ''));
            var bNumber = parseFloat(bValue.replace(/[^\d.-]/g, ''));

            if (!isNaN(aNumber) || !isNaN(bNumber)) {
                return (bNumber || 0) - (aNumber || 0);
            }

            return aValue.localeCompare(bValue);
        });

        rows.forEach(function (row) {
            tbody.appendChild(row);
        });

        filterTable();
    }

    searchInput.addEventListener('input', filterTable);
    classSelect.addEventListener('change', filterTable);
    sortButton.addEventListener('click', sortTable);

    clearButton.addEventListener('click', function () {
        searchInput.value = '';
        classSelect.value = '';
        sortSelect.value = 'name';
        filterTable();
    });
}



function getItemNameFromLink(link) {
    var href = link.getAttribute('href') || '';
    var title = '';

    if (href.indexOf('title=') !== -1) {
        title = href.split('title=')[1].split('&')[0];
        return decodeURIComponent(title).replace(/_/g, ' ').trim();
    }

    if (href.indexOf('/index.php/') !== -1) {
        title = href.split('/index.php/')[1].split('?')[0];
        return decodeURIComponent(title).replace(/_/g, ' ').trim();
    }

    return link.textContent.trim();
}









function initEqoaGeneratedItemLinks($content) {
    var root = $content && $content[0] ? $content[0] : document;
    var links = root.querySelectorAll(
        '#items-found-here-table .items-found-here-name a:not([data-npc-item-tooltip-bound="1"]):not([data-quest-reward-tooltip-bound="1"]), ' +
        '#items-found-here-table td:first-child a:not([data-npc-item-tooltip-bound="1"]):not([data-quest-reward-tooltip-bound="1"]), ' +
        '.zone-monster-table .zone-monster-drops a:not([data-npc-item-tooltip-bound="1"]):not([data-quest-reward-tooltip-bound="1"]), ' +
        '.zone-monster-table td:nth-child(3) a:not([data-npc-item-tooltip-bound="1"]):not([data-quest-reward-tooltip-bound="1"]), ' +
        '.eqoa-related-items a:not([data-npc-item-tooltip-bound="1"]):not([data-quest-reward-tooltip-bound="1"])'
    );
    var items = window.EQOA_GENERATED_ITEMS || [];
    var lookup = {};

    if (!links.length) return;

    if (!items.length) {
        setTimeout(function () {
            initEqoaGeneratedItemLinks($content);
        }, 250);
        return;
    }

    function normalizeName(value) {
        return String(value || '')
            .replace(/\u00a0/g, ' ')
            .replace(/[’`]/g, "'")
            .replace(/\s+/g, ' ')
            .trim()
            .toLowerCase();
    }

    function looseName(value) {
        return normalizeName(value).replace(/[^a-z0-9]/g, '');
    }

    function stripExtraText(value) {
        return String(value || '')
            .replace(/\(\d+%\)/g, '')
            .replace(/\([^)]*\)$/g, '')
            .replace(/\s+/g, ' ')
            .trim();
    }

    function rarityRank(item) {
        var rarity = normalizeClassItemRarity(item && item.rarity);

        if (rarity === 'ultra-rare') return 4;
        if (rarity === 'rare') return 3;
        if (rarity === 'uncommon') return 2;

        return 1;
    }

    function storeItem(key, item) {
        var current;

        if (!key) return;

        current = lookup[key];

        if (!current || rarityRank(item) > rarityRank(current)) {
            lookup[key] = item;
        }
    }

    function getHrefTitle(link) {
        var href = link.getAttribute('href') || '';
        var title = '';

        if (href.indexOf('title=') !== -1) {
            title = href.split('title=')[1].split('&')[0];
            return decodeURIComponent(title).replace(/_/g, ' ').trim();
        }

        if (href.indexOf('/index.php/') !== -1) {
            title = href.split('/index.php/')[1].split('?')[0];
            return decodeURIComponent(title).replace(/_/g, ' ').trim();
        }

        return '';
    }

    function getCandidates(link) {
        var row = link.closest('tr');
        var names = [
            link.getAttribute('data-eqoa-item-name'),
            row && row.getAttribute('data-eqoa-item-name'),
            link.textContent,
            stripExtraText(link.textContent),
            getHrefTitle(link),
            stripExtraText(getHrefTitle(link))
        ];

        return names.filter(function (name) {
            return name && String(name).trim();
        });
    }

    function findItem(link) {
        var candidates = getCandidates(link);
        var i;
        var name;
        var item;

        for (i = 0; i < candidates.length; i++) {
            name = candidates[i];
            item = lookup[normalizeName(name)] || lookup[looseName(name)];

            if (item) return item;
        }

        return null;
    }

    function normalizeGeneratedItem(item) {
        return {
            name: item.name || '',
            rarity: item.rarity || 'common',
            slot: item.slot || '',
            level: item.level || item.lvl || '',
            race: item.race || '',
            className: Array.isArray(item.class) ? item.class.join(', ') : (item.class || item.classes || item.className || ''),
            notrade: item.notrade || item.noTrade || item.trade === 'NO TRADE',
            lore: item.lore || '',
            dmg: item.dmg || item.damage || '',
            ac: item.ac || '',
            str: item.str || '',
            sta: item.sta || '',
            agi: item.agi || '',
            dex: item.dex || '',
            wis: item.wis || '',
            int: item.int || '',
            cha: item.cha || '',
            hp: item.hp || '',
            hot: item.hot || '',
            pow: item.pow || '',
            pot: item.pot || '',
            pr: item.pr || '',
            dr: item.dr || '',
            fr: item.fr || '',
            cr: item.cr || '',
            lr: item.lr || '',
            ar: item.ar || '',
            dur: item.dur || '',
            gearhp: item.gearhp || item.gearHp || item.gear_hp || '',
            effect: item.effect || '',
            proc: item.proc || '',
            source: item.source || ''
        };
    }

    items.forEach(function (item) {
        if (!item || !item.name) return;

        storeItem(normalizeName(item.name), item);
        storeItem(looseName(item.name), item);
    });

    Array.prototype.forEach.call(links, function (link) {
        var item = findItem(link);
        var tooltipItem;
        var rarity;

        if (!item) return;

        tooltipItem = normalizeGeneratedItem(item);
        rarity = normalizeClassItemRarity(tooltipItem.rarity);

        link.classList.remove(
            'class-item-rarity-common',
            'class-item-rarity-uncommon',
            'class-item-rarity-rare',
            'class-item-rarity-ultra-rare',
            'eqoa-related-tooltip-ready',
            'items-found-tooltip-ready'
        );

        link.classList.add('class-item-tooltip-link');
        link.classList.add('class-item-rarity-' + rarity);
link.classList.add('cb-rarity-' + rarity);
        applyClassItemRarityStyle(link, rarity);

        if (link._eqoaGeneratedTooltipBound) return;
        link._eqoaGeneratedTooltipBound = true;

        link.addEventListener('mouseenter', function (event) {
            showClassItemTooltip(tooltipItem, event);
        });

        link.addEventListener('mousemove', moveClassItemTooltip);

        link.addEventListener('mouseleave', function () {
            hideClassItemTooltip();
        });
    });
}

function initItemsFoundHereRarityTooltips() {
    initEqoaGeneratedItemLinks();
}

function initRelatedItemTooltips($content) {
    initEqoaGeneratedItemLinks($content);
}













function ensureMobileSidebarButton() {
    if (document.getElementById('eqoa-mobile-sidebar-toggle')) return;

    var button = document.createElement('button');
    button.id = 'eqoa-mobile-sidebar-toggle';
    button.type = 'button';
    button.textContent = '☰';
    button.setAttribute('aria-label', 'Open sidebar');

    button.addEventListener('click', function () {
    document.documentElement.classList.toggle('eqoa-mobile-sidebar-open');

    setTimeout(removeMobilePageActions, 50);
    setTimeout(removeMobilePageActions, 250);
});

    document.body.appendChild(button);
}

function stackZoneColumnsOnMobile() {
    if (!window.matchMedia || !window.matchMedia('(max-width: 700px)').matches) return;
    if (document.querySelector('.eqoa-mobile-zone-stack')) return;

    var left = document.querySelector('.zone-left');
    var right = document.querySelector('.zone-right');

    if (!left || !right) return;

    var leftTable = left.closest('table');
    var rightTable = right.closest('table');
    var layoutTable = leftTable && leftTable === rightTable ? leftTable : null;

    var stack = document.createElement('div');
    stack.className = 'eqoa-mobile-zone-stack';

    if (layoutTable) {
        layoutTable.parentNode.insertBefore(stack, layoutTable);

        if (left.tagName.toLowerCase() === 'td') {
            var leftBox = document.createElement('div');
            leftBox.className = left.className;
            while (left.firstChild) leftBox.appendChild(left.firstChild);
            stack.appendChild(leftBox);
        } else {
            stack.appendChild(left);
        }

        if (right.tagName.toLowerCase() === 'td') {
            var rightBox = document.createElement('div');
            rightBox.className = right.className;
            while (right.firstChild) rightBox.appendChild(right.firstChild);
            stack.appendChild(rightBox);
        } else {
            stack.appendChild(right);
        }

        layoutTable.style.setProperty('display', 'none', 'important');
    } else {
        left.parentNode.insertBefore(stack, left);
        stack.appendChild(left);
        stack.appendChild(right);
    }

    stack.querySelectorAll('.zone-left, .zone-right').forEach(function (el) {
        el.style.setProperty('display', 'block', 'important');
        el.style.setProperty('float', 'none', 'important');
        el.style.setProperty('clear', 'both', 'important');
        el.style.setProperty('width', '100%', 'important');
        el.style.setProperty('max-width', '100%', 'important');
        el.style.setProperty('margin', '0 0 12px 0', 'important');
    });
}


function removeMobilePageActions() {
return;

    if (!window.matchMedia || !window.matchMedia('(max-width: 700px)').matches) return;

var selectors = [
    '#p-cactions',
    '#p-views',
    '#p-namespaces',
    '#left-navigation',
    '#right-navigation',
    '#head',
    '#mw-head',
    'li[id^="ca-"]',
    'a[id^="ca-"]',
    '[id^="ca-"]'
];

    selectors.forEach(function (selector) {
        document.querySelectorAll(selector).forEach(function (el) {
            el.remove();
        });
    });

    document.querySelectorAll('#column-one .portlet, #column-one .generated-sidebar, #column-one div').forEach(function (el) {
        var label = '';
        var heading = el.querySelector('h3, h4, h5');

        if (heading) {
            label = heading.textContent.trim().toUpperCase();
        } else {
            label = el.textContent.trim().toUpperCase();
        }

        if (label.indexOf('PAGE ACTIONS') !== -1) {
            el.remove();
        }
    });

    document.querySelectorAll('#column-one a, #column-one button, #column-one input, #column-one img').forEach(function (el) {
        if (el.closest('#p-search')) return;
        if (el.id === 'eqoa-mobile-sidebar-toggle' || el.id === 'eqoa-theme-toggle') return;

        var visibleText = (el.textContent || el.value || '').trim();
        var rect = el.getBoundingClientRect();

        if (
            !visibleText &&
            rect.width > 0 &&
            rect.height > 0 &&
            rect.width <= 70 &&
            rect.height <= 70
        ) {
            var removeTarget = el.closest('li, a, button, input, div') || el;
            removeTarget.remove();
        }
    });

    document.querySelectorAll('a, button, input, li, div, img').forEach(function (el) {
        if (el.id === 'eqoa-mobile-sidebar-toggle' || el.id === 'eqoa-theme-toggle') return;
        if (el.closest('#p-search')) return;

        var rect = el.getBoundingClientRect();
        var visibleText = (el.textContent || el.value || '').trim();

        if (
            !visibleText &&
            rect.width > 0 &&
            rect.height > 0 &&
            rect.width <= 70 &&
            rect.height <= 70 &&
            rect.top >= 0 &&
            rect.top <= 140
        ) {
            var removeTarget = el.closest('li, a, button, input, div') || el;
            removeTarget.remove();
        }
    });
}


  function loadBuilderScripts() {
    loadWikiScript('MediaWiki:EQOAConfig.js')
        .always(function () {
            loadWikiScript('MediaWiki:CharacterBuilderData.js')
                .done(function () {
                    $.when(
                        loadScriptUrl('https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js'),
                        loadScriptUrl('https://cdnjs.cloudflare.com/ajax/libs/lz-string/1.5.0/lz-string.min.js')
                    ).always(function () {
                        loadWikiScript('MediaWiki:CharacterBuilder.js')
                            .fail(function (error) {
                                console.error('Character Builder failed to load:', error);
                            });
                    });
                })
                .fail(function (error) {
                    console.error('Character Builder data failed to load:', error);
                });
        });
}





function loadClassMasteryScripts() {
    loadWikiScript('MediaWiki:ClassMasteryData.js')
        .done(function () {
            loadWikiScript('MediaWiki:ClassMasteryRawData.js')
                .always(function () {
                    loadWikiScript('MediaWiki:ClassMasteryIds.js')
                        .always(function () {
                            loadWikiScript('MediaWiki:ClassMasteryCalculator.js')
                                .fail(function (error) {
                                    console.error('Class Mastery calculator failed to load:', error);
                                });
                        });
                });
        })
        .fail(function (error) {
            console.error('Class Mastery data failed to load:', error);
        });
}
                       

    window.EQOA_APPLY_WIKI_THEME = applyWikiTheme;
    window.EQOA_GET_WIKI_THEME = getSavedWikiTheme;

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

    mw.hook('wikipage.content').add(function ($content) {
        ensureThemeButton();
        ensureMobileSidebarButton();
        stackZoneColumnsOnMobile();
        removeMobilePageActions();
        ensureMobileSidebarButton();

        setTimeout(stackZoneColumnsOnMobile, 250);
        setTimeout(stackZoneColumnsOnMobile, 1000);

        setTimeout(function () {
            removeMobilePageActions();
            ensureMobileSidebarButton();
        }, 250);

        setTimeout(function () {
            removeMobilePageActions();
            ensureMobileSidebarButton();
        }, 1000);

        initRaidChecks();
styleRareEncounterRows();
setTimeout(styleRareEncounterRows, 100);
setTimeout(styleRareEncounterRows, 500);
setTimeout(buildRareEncounterMobileLayout, 100);
setTimeout(buildRareEncounterMobileLayout, 500);
        initItemFilters();
initItemsFoundHereRarityTooltips();

        mw.loader.using('mediawiki.api').then(function () {
            initRelatedItemTooltips($content);
        });
    });

    mw.loader.using('mediawiki.util').then(function () {
        loadBuilderScripts();
        loadClassMasteryScripts();
    });
})();





function buildRareEncounterMobileLayout() {
    var isMobile = window.matchMedia('(max-width: 700px)').matches;
    var tables = document.querySelectorAll('.raid-encounter-table table');

    function styleRareMobileHeadings(root) {
        var isLightTheme =
            document.documentElement.classList.contains('eqoa-theme-light') ||
            document.body.classList.contains('eqoa-theme-light');

        Array.prototype.forEach.call(root.querySelectorAll('.rare-mobile-heading'), function (heading) {
            heading.style.padding = '9px 10px';
            heading.style.textAlign = 'center';
            heading.style.fontSize = '18px';
            heading.style.fontWeight = 'bold';
            heading.style.borderBottom = isLightTheme ? '1px solid #5a3a25' : '1px solid #1f1f1f';
            heading.style.background = isLightTheme ? '#3a2418' : '#3a3a3a';
            heading.style.color = isLightTheme ? '#ffffff' : '#ffd45a';
        });
    }

    Array.prototype.forEach.call(tables, function (table) {
        var wrapper = table.closest('.raid-encounter-table');
        if (!wrapper) return;

        var existingMobile = wrapper.querySelector('.rare-encounter-mobile-layout');

        if (!isMobile) {
            table.style.display = '';
            if (existingMobile) existingMobile.style.display = 'none';
            return;
        }

        table.style.display = 'none';

        if (existingMobile) {
            existingMobile.style.display = 'block';
            styleRareMobileHeadings(existingMobile);
            return;
        }

        var mobile = document.createElement('div');
        mobile.className = 'rare-encounter-mobile-layout';

        var rows = table.querySelectorAll('tr');

        function addMobileBox(title, cell) {
            if (!cell || !cell.textContent.trim()) return;

            var box = document.createElement('div');
            box.className = 'rare-mobile-box';

            var heading = document.createElement('div');
            heading.className = 'rare-mobile-heading';
            heading.textContent = title;

            var body = document.createElement('div');
            body.className = 'rare-mobile-body';
            body.innerHTML = cell.innerHTML;

            box.appendChild(heading);
            box.appendChild(body);
            mobile.appendChild(box);
        }

        if (rows[1]) {
            var baseCells = rows[1].querySelectorAll('td');

            addMobileBox('Base World 1', baseCells[0]);
            addMobileBox('Base World 2', baseCells[1]);
            addMobileBox('Base World 3', baseCells[2]);
            addMobileBox('Base World 4', baseCells[3]);
        }

        if (rows[3]) {
            var frontiersCells = rows[3].querySelectorAll('td');

            addMobileBox('Frontiers 1', frontiersCells[0]);
            addMobileBox('Frontiers 2', frontiersCells[1]);
            addMobileBox('Frontiers 3', frontiersCells[2]);
        }

        wrapper.appendChild(mobile);
        styleRareMobileHeadings(mobile);
    });
}




window.addEventListener('resize', buildRareEncounterMobileLayout);
window.addEventListener('orientationchange', function () {
    setTimeout(buildRareEncounterMobileLayout, 150);
});

function limitDevRenodorSidebarSections() {
    var username = mw.config.get('wgUserName');
    var isDevRenodor = username === 'DevRenodor';

    ['p-navigation', 'p-tb'].forEach(function (id) {
        var section = document.getElementById(id);

        if (section) {
            section.style.display = isDevRenodor ? '' : 'none';
        }
    });
}

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

$(function () {
    if (window.EQOA_APPLY_WIKI_THEME && window.EQOA_GET_WIKI_THEME) {
        window.EQOA_APPLY_WIKI_THEME(window.EQOA_GET_WIKI_THEME());
    }
});

function limitFlaggedRevsBadgeToDevRenodor() {
    var username = mw.config.get('wgUserName');
    var isDevRenodor = username === 'DevRenodor';

    [
        '#mw-fr-revisiontag',
        '.flaggedrevs_short',
        '.flaggedrevs_basic',
        '.flaggedrevs_preview',
        '.flaggedrevs_notice',
        '.flaggedrevs_reviewform',
        '.mw-fr-reviewlink'
    ].forEach(function (selector) {
        document.querySelectorAll(selector).forEach(function (el) {
            el.style.setProperty('display', isDevRenodor ? 'block' : 'none', 'important');
        });
    });
}

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

function fixPersonalNotificationButtons() {
    function fixButton(id, labelText) {
        var item = document.getElementById(id);
        if (!item) return;

        var link = item.querySelector('a');
        if (!link) return;

        var rawText = link.textContent || '';
        var countMatch = rawText.match(/\d+/);
        var count = countMatch ? countMatch[0] : '';

        link.textContent = count ? labelText + ' (' + count + ')' : labelText;
        link.setAttribute('title', labelText);
        link.setAttribute('aria-label', labelText);
    }

    fixButton('pt-notifications-alert', 'alerts');
    fixButton('pt-notifications-notice', 'notices');
}

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






mw.hook('wikipage.content').add(fixPersonalNotificationButtons);
setTimeout(fixPersonalNotificationButtons, 500);
setTimeout(fixPersonalNotificationButtons, 1500);


mw.hook('wikipage.content').add(limitFlaggedRevsBadgeToDevRenodor);


mw.hook('wikipage.content').add(limitDevRenodorSidebarSections);

mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:CharacterBuilderGeneratedItems.js&action=raw&ctype=text/javascript');




mw.loader.using( 'mediawiki.util', function () {
    var topbar = document.createElement( 'div' );
    topbar.id = 'eqoa-topbar';

    topbar.innerHTML =
        '<a class="eqoa-top-title" href="' + mw.util.getUrl( 'Main Page' ) + '">EQOA: Sandstorm</a>' +
        '<form class="eqoa-top-search" action="' + mw.util.getUrl( 'Special:Search' ) + '" method="get">' +
            '<input type="search" name="search" placeholder="Search EQOA: Sandstorm" />' +
            '<button type="submit">Search</button>' +
        '</form>' +
        '<a class="eqoa-discord-button" href="https://discord.gg/QK6r8Qd8Vu" target="_blank" rel="noopener">Join the Community</a>';

    document.body.insertBefore( topbar, document.body.firstChild );
} );

mw.loader.using( 'mediawiki.util', function () {
    if ( document.getElementById( 'eqoa-right-rail' ) ) {
        return;
    }

    var rail = document.createElement( 'aside' );
    rail.id = 'eqoa-right-rail';

    rail.innerHTML =
        '<iframe ' +
            'src="https://discord.com/widget?id=1251536624467050628&theme=dark" ' +
            'width="300" ' +
            'height="500" ' +
            'allowtransparency="true" ' +
            'frameborder="0" ' +
            'sandbox="allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts">' +
        '</iframe>';

    document.body.appendChild( rail );
} );

function eqoaKeepPersonalToolsVisible() {
    var personal = document.getElementById( 'p-personal' );

    if ( !personal ) {
        return;
    }

    personal.style.display = 'block';
    personal.style.visibility = 'visible';
    personal.style.opacity = '1';
    personal.style.position = 'fixed';
    personal.style.top = '72px';
    personal.style.right = '18px';
    personal.style.left = 'auto';
    personal.style.zIndex = '2147483647';
    personal.style.background = 'transparent';
    personal.style.overflow = 'visible';

    var body = personal.querySelector( '.pBody' );
    var list = personal.querySelector( 'ul' );

    [ body, list ].forEach( function ( el ) {
        if ( !el ) {
            return;
        }

        el.style.display = 'flex';
        el.style.visibility = 'visible';
        el.style.opacity = '1';
        el.style.gap = '4px';
        el.style.alignItems = 'center';
        el.style.justifyContent = 'flex-end';
        el.style.background = 'transparent';
        el.style.border = '0';
        el.style.margin = '0';
        el.style.padding = '0';
        el.style.listStyle = 'none';
    } );

    personal.querySelectorAll( 'li, a' ).forEach( function ( el ) {
        el.style.display = 'inline-flex';
        el.style.visibility = 'visible';
        el.style.opacity = '1';
    } );
}

document.addEventListener( 'DOMContentLoaded', eqoaKeepPersonalToolsVisible );
mw.hook( 'wikipage.content' ).add( eqoaKeepPersonalToolsVisible );
setTimeout( eqoaKeepPersonalToolsVisible, 250 );
setTimeout( eqoaKeepPersonalToolsVisible, 1000 );
setTimeout( eqoaKeepPersonalToolsVisible, 2000 );




function eqoaBuildSidebarAccountButtons() {
    var sidebar = document.getElementById('column-one');
    var personal = document.getElementById('p-personal');

    if (!sidebar || !personal) return;

    var oldBox = document.getElementById('eqoa-sidebar-account');
    if (oldBox) oldBox.remove();

    var links = personal.querySelectorAll('a');
    if (!links.length) return;

    var box = document.createElement('div');
    box.id = 'eqoa-sidebar-account';

    var title = document.createElement('div');
    title.className = 'eqoa-sidebar-account-title';
    title.textContent = 'Account';
box.appendChild(title);

var grid = document.createElement('div');
grid.className = 'eqoa-sidebar-account-grid';
box.appendChild(grid);

links.forEach(function (link) {
        var text = (link.textContent || '').trim();

        if (!text) return;

        var clone = document.createElement('a');
        clone.href = link.href;
        clone.textContent = text;
        clone.title = link.title || text;
        clone.className = 'eqoa-sidebar-account-button';

        grid.appendChild(clone);
    });

    var logo = document.getElementById('p-logo');

    if (logo && logo.parentNode === sidebar) {
        logo.insertAdjacentElement('afterend', box);
    } else {
        sidebar.insertBefore(box, sidebar.firstChild);
    }
}


/* =========================================================
   EQOA Interactive Maps - Load Leaflet
   ========================================================= */

mw.loader.load('https://unpkg.com/leaflet@1.9.4/dist/leaflet.js');
mw.loader.load('https://unpkg.com/leaflet@1.9.4/dist/leaflet.css', 'text/css');

/* =========================================================
   EQOA Interactive Maps
   ========================================================= */

function initEqoaInteractiveMaps() {
    if (typeof L === 'undefined') {
        setTimeout(initEqoaInteractiveMaps, 100);
        return;
    }

document.querySelectorAll('.eqoa-interactive-map').forEach(function (mapBox) {
        if (mapBox.classList.contains('eqoa-map-ready')) return;

        var imageUrl = mapBox.getAttribute('data-image');
        var width = parseInt(mapBox.getAttribute('data-width'), 10) || 2048;
        var height = parseInt(mapBox.getAttribute('data-height'), 10) || 2048;
        var points = mapBox.querySelectorAll('.eqoa-map-point');
        var bounds = [[0, 0], [height, width]];
        var layersByType = {};
        var markersByType = {};
        var map;

        if (!imageUrl) return;

mapBox.style.aspectRatio = width + ' / ' + height;
mapBox.style.width = '100%';
mapBox.style.height = 'auto';

mapBox.classList.add('eqoa-map-ready');

map = L.map(mapBox, {
    crs: L.CRS.Simple,
    minZoom: -2,
    maxZoom: 3,
    zoomSnap: 0.25,
    attributionControl: false,
    maxBounds: bounds,
    maxBoundsViscosity: 1.0
});

mapBox._leaflet_map_instance = map;

map.on('click', function (event) {
    console.log(
        'EQOA map coordinate:',
        'data-x="' + Math.round(event.latlng.lng) + '"',
        'data-y="' + Math.round(event.latlng.lat) + '"'
    );
});

var imageLayer = L.imageOverlay(imageUrl, bounds).addTo(map);

mapBox._leaflet_image_layer = imageLayer;

function sizeEqoaMobileMapBox() {
    var parent;
    var boxWidth;
    var boxHeight;

    if (!window.matchMedia || !window.matchMedia('(max-width: 700px)').matches) {
        return;
    }

    parent = mapBox.closest('.zone-interactive-map-content');

    if (parent) {
        parent.style.setProperty('padding', '0', 'important');
        parent.style.setProperty('height', 'auto', 'important');
        parent.style.setProperty('min-height', '0', 'important');
        parent.style.setProperty('background', 'transparent', 'important');
    }

    mapBox.style.setProperty('display', 'block', 'important');
    mapBox.style.setProperty('width', '100%', 'important');
    mapBox.style.setProperty('max-width', '650px', 'important');
    mapBox.style.setProperty('margin', '0 auto', 'important');
    mapBox.style.setProperty('padding', '0', 'important');
    mapBox.style.setProperty('aspect-ratio', width + ' / ' + height, 'important');
    mapBox.style.setProperty('overflow', 'hidden', 'important');
    mapBox.style.setProperty('background', 'transparent', 'important');

    boxWidth = mapBox.clientWidth || mapBox.getBoundingClientRect().width;

    if (!boxWidth) {
        return;
    }

    boxHeight = Math.round(boxWidth * height / width);

    mapBox.style.setProperty('height', boxHeight + 'px', 'important');
    mapBox.style.setProperty('min-height', boxHeight + 'px', 'important');
    mapBox.style.setProperty('max-height', boxHeight + 'px', 'important');
}

function repairEqoaMobileMapImage() {
    var imageElement;
    var northWest;
    var southEast;
    var imageWidth;
    var imageHeight;

    if (!window.matchMedia || !window.matchMedia('(max-width: 700px)').matches) {
        return;
    }

    imageLayer.setBounds(bounds);

    imageElement = imageLayer.getElement && imageLayer.getElement();

    if (!imageElement) {
        return;
    }

    northWest = map.latLngToLayerPoint([height, 0]);
    southEast = map.latLngToLayerPoint([0, width]);

    imageWidth = Math.ceil(Math.abs(southEast.x - northWest.x));
    imageHeight = Math.ceil(Math.abs(southEast.y - northWest.y));

    imageElement.style.setProperty('display', 'block', 'important');
    imageElement.style.setProperty('visibility', 'visible', 'important');
    imageElement.style.setProperty('opacity', '1', 'important');
    imageElement.style.setProperty('width', imageWidth + 'px', 'important');
    imageElement.style.setProperty('height', imageHeight + 'px', 'important');
    imageElement.style.setProperty('max-width', 'none', 'important');
    imageElement.style.setProperty('max-height', 'none', 'important');
}


function getMapCenter() {
    return [height / 2, width / 2];
}

function fitEqoaMapToBox() {
    var fitZoom;
    var isMobileMap =
        window.matchMedia &&
        window.matchMedia('(max-width: 700px)').matches;

    sizeEqoaMobileMapBox();

    if (isMobileMap) {
        map.options.zoomSnap = 0;
    }

    map.invalidateSize();

    fitZoom = map.getBoundsZoom(bounds, false);

    map.setMinZoom(fitZoom);
    map.setMaxZoom(fitZoom + 3);
    map.setMaxBounds(bounds);

    map.fitBounds(bounds, {
        animate: false,
        padding: [0, 0]
    });

    repairEqoaMobileMapImage();
    updateMapDragging();
}

function updateMapDragging() {
    var minZoom = map.getMinZoom();

    if (map.getZoom() <= minZoom + 0.001) {
        sizeEqoaMobileMapBox();
        map.invalidateSize();

        map.fitBounds(bounds, {
            animate: false,
            padding: [0, 0]
        });

        repairEqoaMobileMapImage();
        map.dragging.disable();
    } else {
        repairEqoaMobileMapImage();
        map.dragging.enable();
    }
}

imageLayer.on('load', function () {
    sizeEqoaMobileMapBox();
    fitEqoaMapToBox();
});

setTimeout(function () {
    fitEqoaMapToBox();
    repairEqoaMobileMapImage();
}, 50);

setTimeout(function () {
    fitEqoaMapToBox();
    repairEqoaMobileMapImage();
}, 250);

setTimeout(function () {
    fitEqoaMapToBox();
    repairEqoaMobileMapImage();
}, 750);

map.on('zoomend', updateMapDragging);

window.addEventListener('resize', function () {
    setTimeout(function () {
        fitEqoaMapToBox();
        repairEqoaMobileMapImage();
    }, 100);

    setTimeout(function () {
        fitEqoaMapToBox();
        repairEqoaMobileMapImage();
    }, 300);
});

window.addEventListener('orientationchange', function () {
    setTimeout(fitEqoaMapToBox, 250);
    setTimeout(fitEqoaMapToBox, 600);
});

function getEqoaMapTooltipDirection(x, y) {
    var edgePadding = 120;

    if (y < edgePadding) return 'bottom';
    if (y > height - edgePadding) return 'top';
    if (x < edgePadding) return 'right';
    if (x > width - edgePadding) return 'left';

    return 'top';
}

function getEqoaMapTooltipOffset(direction) {
    if (direction === 'bottom') return [0, 18];
    if (direction === 'top') return [0, -18];
    if (direction === 'left') return [-18, 0];
    if (direction === 'right') return [18, 0];

    return [0, -18];
}

        points.forEach(function (point) {
var type = point.getAttribute('data-type') || 'Marker';
var namesRaw = point.getAttribute('data-names') || point.getAttribute('data-name') || 'Unknown';
var pagesRaw = point.getAttribute('data-pages') || point.getAttribute('data-page') || namesRaw;

var names = namesRaw.split(',').map(function (name) {
    return name.trim();
}).filter(Boolean);

var pages = pagesRaw.split(',').map(function (page) {
    return page.trim();
}).filter(Boolean);

var name = names.join(' / ');
var page = pages[0] || names[0] || 'Unknown';
var level = point.getAttribute('data-level') || '';
var x = parseFloat(point.getAttribute('data-x'));
var y = parseFloat(point.getAttribute('data-y'));
            var marker;
            var label;

            if (isNaN(x) || isNaN(y)) return;



tooltipDirection = getEqoaMapTooltipDirection(x, y);

if (!layersByType[type]) {
    layersByType[type] = L.layerGroup().addTo(map);
    markersByType[type] = [];
}

            label = '<b>' + name + '</b>';
            if (level) label += '<br>Level: ' + level;
            label += '<br>Type: ' + type;

marker = L.circleMarker([y, x], {
    radius: 4,
    color: getEqoaMapMarkerOutlineColor(type),
    fillColor: getEqoaMapMarkerColor(type),
    fillOpacity: 0.95,
    weight: 2,
    className: 'eqoa-map-dot'
});

var typeLower = String(type || '').toLowerCase();
var isCamp = typeLower === 'camp' || typeLower === 'camps';
var hasMultiplePages = names.length > 1;

if (hasMultiplePages) {
    label = '<b>Shared Spawn</b>';
    if (level) label += '<br>Level: ' + level;
    label += '<br>Type: ' + type;
    label += '<br>';

    names.forEach(function (npcName, index) {
        var npcPage = pages[index] || npcName;
        var href = window.mw && mw.util && mw.util.getUrl
            ? mw.util.getUrl(npcPage)
            : '/index.php/' + encodeURIComponent(npcPage.replace(/ /g, '_'));

        label += '<br><a href="' + href + '">' + npcName + '</a>';
    });

marker.bindPopup(label, {
    closeButton: true,
    autoClose: false,
    closeOnClick: false,
    autoPan: true,
    keepInView: true,
    autoPanPaddingTopLeft: [24, 80],
    autoPanPaddingBottomRight: [24, 24],
    offset: tooltipDirection === 'bottom' ? [0, 22] : [0, -8],
    className: 'eqoa-map-popup'
});

var sharedPopupPinned = false;
var sharedPopupCloseTimer;

marker.on('mouseover', function () {
    clearTimeout(sharedPopupCloseTimer);
    marker.openPopup();
});

marker.on('mouseout', function () {
    clearTimeout(sharedPopupCloseTimer);

    sharedPopupCloseTimer = setTimeout(function () {
        if (!sharedPopupPinned) {
            marker.closePopup();
        }
    }, 350);
});

marker.on('click', function () {
    sharedPopupPinned = true;
    marker.openPopup();
});

marker.on('popupclose', function () {
    sharedPopupPinned = false;
});
} else if (isCamp) {
marker.bindTooltip(label, {
    direction: tooltipDirection,
    sticky: false,
    offset: getEqoaMapTooltipOffset(tooltipDirection),
    opacity: 1
});
} else {
marker.bindTooltip(label + '<br><span style="color:#ffd45a;">Click to open page</span>', {
    direction: tooltipDirection,
    sticky: false,
    offset: getEqoaMapTooltipOffset(tooltipDirection),
    opacity: 1
});

    marker.on('click', function () {
        if (window.mw && mw.util && mw.util.getUrl) {
            window.location.href = mw.util.getUrl(page);
        } else {
            window.location.href = '/index.php/' + encodeURIComponent(page.replace(/ /g, '_'));
        }
    });
}

/*
marker.addTo(layersByType[type]);

*/

markersByType[type].push({
    marker: marker,
    name: name,
    type: type
});        });

var filterControl = L.control({
    position: 'topright'
});

filterControl.onAdd = function () {
    var wrapper = L.DomUtil.create('div', 'eqoa-map-filter-control');
    var button = document.createElement('button');
    var panel = document.createElement('div');

    button.type = 'button';
    button.className = 'eqoa-map-filter-icon';
    button.setAttribute('aria-label', 'Map filters');
button.innerHTML =
    '<span class="eqoa-map-layer-icon">' +
        '<span></span><span></span><span></span>' +
    '</span>';

    panel.className = 'eqoa-map-filter-panel';

    L.DomEvent.disableClickPropagation(wrapper);
    L.DomEvent.disableScrollPropagation(wrapper);

    Object.keys(markersByType).sort().forEach(function (type) {
        var group = document.createElement('div');
        group.className = 'eqoa-map-filter-group';

        var header = document.createElement('div');
        header.className = 'eqoa-map-filter-header';

        var expand = document.createElement('button');
        expand.type = 'button';
        expand.className = 'eqoa-map-filter-expand';
        expand.textContent = '+';

        var typeCheck = document.createElement('input');
        typeCheck.type = 'checkbox';
        typeCheck.checked = false;

        typeCheck.className = 'eqoa-map-filter-type-check';

        var label = document.createElement('span');
        label.className = 'eqoa-map-filter-type-label';
        label.textContent = type;

        var list = document.createElement('div');
        list.className = 'eqoa-map-filter-list';
        list.style.display = 'none';

        header.appendChild(expand);
        header.appendChild(typeCheck);
        header.appendChild(label);
        group.appendChild(header);
        group.appendChild(list);

        markersByType[type].forEach(function (entry) {
            var row = document.createElement('label');
            row.className = 'eqoa-map-filter-row';

            var check = document.createElement('input');
            check.type = 'checkbox';
            check.checked = false;
entry.check = check;

            var nameText = document.createElement('span');
            nameText.textContent = entry.name;

            row.appendChild(check);
            row.appendChild(nameText);
            list.appendChild(row);

check.addEventListener('change', function () {
    refreshTypeMarkers();
    updateTypeCheck();
});
        });

function refreshTypeMarkers() {
    layersByType[type].clearLayers();

    markersByType[type].forEach(function (entry) {
        if (entry.check && entry.check.checked) {
            entry.marker.addTo(layersByType[type]);
        }
    });
}

function updateTypeCheck() {
    var checks = list.querySelectorAll('input[type="checkbox"]');
    var checked = list.querySelectorAll('input[type="checkbox"]:checked');

    typeCheck.checked = checked.length > 0;
    typeCheck.indeterminate = checked.length > 0 && checked.length < checks.length;
}

        expand.addEventListener('click', function (event) {
            event.preventDefault();
            event.stopPropagation();

            var isOpen = list.style.display !== 'none';

            list.style.display = isOpen ? 'none' : 'block';
            expand.textContent = isOpen ? '+' : '-';
        });

typeCheck.addEventListener('change', function () {
    var checks = list.querySelectorAll('input[type="checkbox"]');

    Array.prototype.forEach.call(checks, function (check) {
        check.checked = typeCheck.checked;
    });

    typeCheck.indeterminate = false;
    refreshTypeMarkers();
});

        panel.appendChild(group);
    });

var closeTimer;

function openFilterPanel() {
    clearTimeout(closeTimer);
    wrapper.classList.add('eqoa-map-filter-open');
}

function closeFilterPanelSoon() {
    clearTimeout(closeTimer);
    closeTimer = setTimeout(function () {
        wrapper.classList.remove('eqoa-map-filter-open');
    }, 500);
}

button.addEventListener('mouseenter', openFilterPanel);
panel.addEventListener('mouseenter', openFilterPanel);

wrapper.addEventListener('mouseleave', closeFilterPanelSoon);
panel.addEventListener('mouseleave', closeFilterPanelSoon);

button.addEventListener('click', function (event) {
    event.preventDefault();
    event.stopPropagation();
    wrapper.classList.toggle('eqoa-map-filter-open');
});

wrapper.appendChild(button);
wrapper.appendChild(panel);

return wrapper;
};

filterControl.addTo(map);
    });
}

function getEqoaMapMarkerColor(type) {
    type = String(type || '').toLowerCase();

    if (type === 'npc') return '#3fcf5f';
    if (type === 'quest') return '#ffd84d';
    if (type === 'enemy') return '#e85b5b';
    if (type === 'named') return '#b51f1f';
    if (type === 'camps') return '#22d3ee';
    if (type === 'camp') return '#22d3ee';
if (type === 'charm pet') return '#d946ef';
if (type === 'charm pets') return '#d946ef';
if (type === 'charmpet') return '#d946ef';
if (type === 'charmpets') return '#d946ef';

    return '#f2ab31';
}

function getEqoaMapMarkerOutlineColor(type) {
    type = String(type || '').toLowerCase();

    if (type === 'npc') return '#0d4f24';
    if (type === 'quest') return '#7a5100';
    if (type === 'enemy') return '#5c1010';
    if (type === 'named') return '#3a0505';
    if (type === 'camps') return '#0e7490';
    if (type === 'camp') return '#0e7490';
if (type === 'charm pet') return '#86198f';
if (type === 'charm pets') return '#86198f';
if (type === 'charmpet') return '#86198f';
if (type === 'charmpets') return '#86198f';

    return '#3a2508';
}

mw.hook('wikipage.content').add(initEqoaInteractiveMaps);
document.addEventListener('DOMContentLoaded', initEqoaInteractiveMaps);
setTimeout(initEqoaInteractiveMaps, 500);
setTimeout(initEqoaInteractiveMaps, 1500);







document.addEventListener('DOMContentLoaded', eqoaBuildSidebarAccountButtons);
mw.hook('wikipage.content').add(eqoaBuildSidebarAccountButtons);
setTimeout(eqoaBuildSidebarAccountButtons, 250);
setTimeout(eqoaBuildSidebarAccountButtons, 1000);
setTimeout(eqoaBuildSidebarAccountButtons, 2000);

/* =========================================================
   FLAGGEDREVS ACCEPT BUTTON FIX
   Fixes broken review.js Accept revision button for DevRenodor.
   Does not grant permissions. It only uses the normal review API.
   ========================================================= */

function eqoaFixFlaggedRevsAcceptButton() {
    if (!window.mw || !mw.Api || mw.config.get('wgUserName') !== 'DevRenodor') {
        return;
    }

    var acceptButton = document.getElementById('mw-fr-submit-accept');

    if (!acceptButton || acceptButton.getAttribute('data-eqoa-fixed') === '1') {
        return;
    }

    acceptButton.setAttribute('data-eqoa-fixed', '1');

    acceptButton.addEventListener('click', function (event) {
        var api;
        var form;
        var oldidInput;
        var commentInput;
        var revid;
        var comment;

        event.preventDefault();
        event.stopPropagation();
        event.stopImmediatePropagation();

        form = acceptButton.closest('form');
        oldidInput = document.getElementById('mw-fr-input-oldid');
        commentInput = document.getElementById('mw-fr-commentbox');

        revid = oldidInput && oldidInput.value
            ? oldidInput.value
            : mw.config.get('wgRevisionId');

        comment = commentInput && commentInput.value
            ? commentInput.value
            : 'Approved by DevRenodor';

        if (!revid) {
            alert('Could not find revision ID to approve.');
            return;
        }

        acceptButton.disabled = true;
        acceptButton.value = 'Approving...';

        api = new mw.Api();

        api.get({
            action: 'query',
            meta: 'tokens',
            type: 'csrf',
            format: 'json'
        }).then(function (tokenData) {
            return api.post({
                action: 'review',
                revid: revid,
                token: tokenData.query.tokens.csrftoken,
                flag_accuracy: 1,
                comment: comment,
                format: 'json'
            });
        }).then(function () {
            acceptButton.value = 'Approved';
            window.location.reload();
        }).catch(function (error) {
            console.error('EQOA FlaggedRevs approve failed:', error);

            acceptButton.disabled = false;
            acceptButton.value = 'Accept revision';

            alert('Review failed. Check console for EQOA FlaggedRevs approve failed.');
        });
    }, true);
}

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

mw.hook('wikipage.content').add(eqoaFixFlaggedRevsAcceptButton);

setTimeout(eqoaFixFlaggedRevsAcceptButton, 250);
setTimeout(eqoaFixFlaggedRevsAcceptButton, 1000);
setTimeout(eqoaFixFlaggedRevsAcceptButton, 2000);


/* =========================================================
   EQOA ZONE RELATED TABS
   Turns zone page sections into Wowhead-style tabs.
   No Screenshots tab.
   ========================================================= */

function initEqoaZoneTabs() {
    var tabSections = [
        { id: 'quests', label: 'Quests' },
        { id: 'npcs', label: 'NPCs' },
        { id: 'items', label: 'Drops' },
        { id: 'achievements', label: 'Achievements' }
    ];

    var headings = [];
    var activators = {};
    var initialHash = window.location.hash ? window.location.hash.replace('#', '') : '';

    function getHeadingById(id) {
        var el = document.getElementById(id);

        if (!el) {
            return null;
        }

        if (/^h2$/i.test(el.tagName)) {
            return el;
        }

        return el.closest('h2');
    }

    tabSections.forEach(function (section) {
        var heading = getHeadingById(section.id);

        if (heading) {
            headings.push({
                id: section.id,
                label: section.label,
                heading: heading
            });
        }
    });

    if (!headings.length) {
        return;
    }

    if (document.querySelector('.eqoa-zone-tabs')) {
        return;
    }

    var wrapper = document.createElement('div');
    wrapper.className = 'eqoa-zone-tabs';

    var title = document.createElement('div');
    title.className = 'eqoa-zone-tabs-title';
    title.textContent = 'Related';

    var nav = document.createElement('div');
    nav.className = 'eqoa-zone-tabs-nav';

    var body = document.createElement('div');
    body.className = 'eqoa-zone-tabs-body';

    wrapper.appendChild(title);
    wrapper.appendChild(nav);
    wrapper.appendChild(body);

    headings[0].heading.parentNode.insertBefore(wrapper, headings[0].heading);

    headings.forEach(function (section, index) {
        var button = document.createElement('button');
        var panel = document.createElement('div');
        var current = section.heading.nextSibling;
        var next;

        button.type = 'button';
        button.className = 'eqoa-zone-tab-button';
        button.textContent = section.label;
        button.setAttribute('data-tab', section.id);

        panel.className = 'eqoa-zone-tab-panel';
        panel.setAttribute('data-tab-panel', section.id);
        panel.id = section.id;

        if (index === 0) {
            button.classList.add('is-active');
            panel.classList.add('is-active');
        }

        section.heading.parentNode.removeChild(section.heading);

        while (current) {
            next = current.nextSibling;

            if (
                current.nodeType === 1 &&
                /^h2$/i.test(current.tagName) &&
                headings.some(function (other) {
                    return other.heading === current;
                })
            ) {
                break;
            }

            panel.appendChild(current);
            current = next;
        }

        function activateTab(pushHash) {
            wrapper.querySelectorAll('.eqoa-zone-tab-button').forEach(function (btn) {
                btn.classList.remove('is-active');
            });

            wrapper.querySelectorAll('.eqoa-zone-tab-panel').forEach(function (tabPanel) {
                tabPanel.classList.remove('is-active');
            });

            button.classList.add('is-active');
            panel.classList.add('is-active');

            if (pushHash && window.history && window.history.pushState) {
                window.history.pushState(null, '', '#' + section.id);
            }

            wrapper.scrollIntoView({
                behavior: 'smooth',
                block: 'start'
            });
        }

        activators[section.id] = activateTab;

        button.addEventListener('click', function () {
            activateTab(true);
        });

        document.querySelectorAll('a[href="#' + section.id + '"]').forEach(function (link) {
            link.addEventListener('click', function (event) {
                event.preventDefault();
                activateTab(true);
            });
        });

        nav.appendChild(button);
        body.appendChild(panel);
    });

    if (initialHash && activators[initialHash]) {
        activators[initialHash](false);
    }
}

mw.hook('wikipage.content').add(initEqoaZoneTabs);

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

setTimeout(initEqoaZoneTabs, 500);
setTimeout(initEqoaZoneTabs, 1500);


/* =========================================================
   EQOA CLASS PAGE TABS
   Durable delegated handler for class tabs + URL hashes
   ========================================================= */

function eqoaActivateClassTab(tabs, tabName, updateUrl) {
    var button;
    var panel;

    if (!tabs || !tabName) return;

    button = tabs.querySelector('.eqoa-class-tab-button[data-class-tab="' + tabName + '"]');
    panel = tabs.querySelector('.eqoa-class-tab-panel[data-class-tab-panel="' + tabName + '"]');

    if (!button || !panel) return;

    tabs.querySelectorAll('.eqoa-class-tab-button').forEach(function (btn) {
        btn.classList.remove('is-active');
    });

    tabs.querySelectorAll('.eqoa-class-tab-panel').forEach(function (tabPanel) {
        tabPanel.classList.remove('is-active');
    });

    button.classList.add('is-active');
    panel.classList.add('is-active');

    if (updateUrl && window.history && window.history.pushState) {
        window.history.pushState(null, '', '#' + tabName);
    }
}

function initEqoaClassTabs() {
    document.querySelectorAll('.eqoa-class-tabs').forEach(function (tabs) {
        var initialTab;

        if (tabs.getAttribute('data-eqoa-class-tabs-ready') === '1') {
            return;
        }

        tabs.setAttribute('data-eqoa-class-tabs-ready', '1');

        initialTab = window.location.hash
            ? window.location.hash.replace('#', '')
            : 'races';

        if (!tabs.querySelector('.eqoa-class-tab-button[data-class-tab="' + initialTab + '"]')) {
            initialTab = 'races';
        }

        eqoaActivateClassTab(tabs, initialTab, false);
    });
}

document.addEventListener('click', function (event) {
    var button = event.target.closest('.eqoa-class-tab-button');
    var tabs;

    if (!button) return;

    tabs = button.closest('.eqoa-class-tabs');

    if (!tabs) return;

    event.preventDefault();
    event.stopPropagation();

    eqoaActivateClassTab(tabs, button.getAttribute('data-class-tab'), true);
});

document.addEventListener('click', function (event) {
    var link = event.target.closest('a[href^="#"]');
    var tabs;
    var tabName;

    if (!link) return;

    tabName = link.getAttribute('href').replace('#', '');
    tabs = document.querySelector('.eqoa-class-tabs');

    if (!tabs) return;

    if (!tabs.querySelector('.eqoa-class-tab-button[data-class-tab="' + tabName + '"]')) {
        return;
    }

    event.preventDefault();

    eqoaActivateClassTab(tabs, tabName, true);

    tabs.scrollIntoView({
        behavior: 'smooth',
        block: 'start'
    });
});

window.addEventListener('hashchange', function () {
    var tabs = document.querySelector('.eqoa-class-tabs');
    var tabName = window.location.hash.replace('#', '');

    if (!tabs || !tabName) return;

    eqoaActivateClassTab(tabs, tabName, false);
});

mw.hook('wikipage.content').add(initEqoaClassTabs);

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

setTimeout(initEqoaClassTabs, 250);
setTimeout(initEqoaClassTabs, 1000);
setTimeout(initEqoaClassTabs, 2000);

mw.hook('wikipage.content').add(initEqoaClassTabs);

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

setTimeout(initEqoaClassTabs, 250);
setTimeout(initEqoaClassTabs, 1000);
setTimeout(initEqoaClassTabs, 2000);


/* =========================================================
   EQOA CLASS ITEM LISTS
   Loads class item tabs from Category:MAG Items, etc.
   ========================================================= */

function initClassItemLists() {
    if (!window.mw || !mw.Api) return;

    document.querySelectorAll('.class-item-list').forEach(function (box) {
        var classCode = (box.getAttribute('data-class') || '').trim().toUpperCase();
        var loading = box.querySelector('.class-item-list-loading');
        var table = box.querySelector('.class-item-table');

        if (!classCode || box.classList.contains('class-item-list-ready')) return;

        box.classList.add('class-item-list-ready');

        loadClassCategoryItems(classCode).then(function (items) {
            renderClassItemList(box, table, loading, items);
        }).catch(function (error) {
            console.error('Class item list failed:', error);
            if (loading) loading.textContent = 'Failed to load class items.';
        });
    });
}

function loadClassCategoryItems(classCode) {
    var api = new mw.Api();
    var category = 'Category:' + classCode + ' Items';
    var pages = [];

    function getBatch(cmcontinue) {
        var request = {
            action: 'query',
            list: 'categorymembers',
            cmtitle: category,
            cmnamespace: 0,
            cmlimit: 'max',
            format: 'json'
        };

        if (cmcontinue) {
            request.cmcontinue = cmcontinue;
        }

        return api.get(request).then(function (data) {
            pages = pages.concat(data.query.categorymembers || []);

            if (data.continue && data.continue.cmcontinue) {
                return getBatch(data.continue.cmcontinue);
            }

            return pages;
        });
    }

    return getBatch().then(function (categoryPages) {
        return loadClassItemPageData(categoryPages.map(function (page) {
            return page.title;
        }));
    });
}

function loadClassItemPageData(titles) {
    var api = new mw.Api();
    var chunks = [];
    var i;

    for (i = 0; i < titles.length; i += 50) {
        chunks.push(titles.slice(i, i + 50));
    }

    return Promise.all(chunks.map(function (chunk) {
        return api.get({
            action: 'query',
            prop: 'revisions',
            titles: chunk.join('|'),
            rvprop: 'content',
            rvslots: 'main',
            formatversion: '2',
            format: 'json'
        });
    })).then(function (responses) {
        var items = [];

        responses.forEach(function (data) {
            (data.query.pages || []).forEach(function (page) {
                var text = '';

                if (page.revisions && page.revisions[0]) {
                    if (page.revisions[0].slots && page.revisions[0].slots.main) {
                        text = page.revisions[0].slots.main.content || '';
                    } else {
                        text = page.revisions[0].content || '';
                    }
                }

                items.push(parseClassItemPage(page.title, text));
            });
        });

        items.sort(function (a, b) {
            if (a.level !== b.level) return a.level - b.level;
            if (a.slot !== b.slot) return a.slot.localeCompare(b.slot);
            return a.name.localeCompare(b.name);
        });

        return items;
    });
}

function parseClassItemPage(title, text) {
    function field(name) {
        var match = text.match(new RegExp('\\|' + name + '\\s*=\\s*([^\\n]*)', 'i'));
        return match ? match[1].trim() : '';
    }

    function numberField(name) {
        var value = field(name);
        var number = parseFloat(value);

        return isNaN(number) ? 0 : number;
    }

    var name = field('name') || title;
    var rawSlot = field('slot') || '';
    var slot = normalizeClassItemSlot(rawSlot);
    var level = parseInt(field('lvl'), 10) || 0;
    var droppedby = cleanClassItemSource(field('droppedby'));
    var zone = cleanClassItemSource(field('zone'));
    var quests = cleanClassItemSource(field('quests'));
    var notes = cleanClassItemSource(field('notes'));

    return {
        name: name,
        rarity: field('rarity'),
        slot: slot,
        level: level,
        race: field('race') || 'ALL',
        className: field('class') || '',
        notrade: field('notrade'),
        lore: field('lore'),

        dmg: numberField('dmg'),
        ac: numberField('ac'),
        str: numberField('str'),
        sta: numberField('sta'),
        agi: numberField('agi'),
        dex: numberField('dex'),
        wis: numberField('wis'),
        int: numberField('int'),
        cha: numberField('cha'),
        hp: numberField('hp'),
        hot: numberField('hot'),
        pow: numberField('pow'),
        pot: numberField('pot'),
        pr: numberField('pr'),
        dr: numberField('dr'),
        fr: numberField('fr'),
        cr: numberField('cr'),
        lr: numberField('lr'),
        ar: numberField('ar'),
        dur: numberField('dur'),
        gearhp: numberField('gearhp'),

        effect: field('effect'),
        proc: field('proc'),
        desc: field('desc'),

        droppedby: droppedby,
        zone: zone,
        quests: quests,
        notes: notes,
        type: getClassItemType(name, slot, droppedby, zone, quests, notes),
        source: getClassItemSource(droppedby, zone, quests, notes)
    };
}


function normalizeClassItemSlot(slot) {
    slot = String(slot || '').trim();

    if (!slot) return '';

    if (/^\d+$/.test(slot)) return '';

    if (slot === 'Helm') return 'Head';
    if (slot === 'Helmet') return 'Head';
    if (slot === 'Necklace') return 'Neck';
    if (slot === 'Earring') return 'Ear';
    if (slot === 'Leggings') return 'Legs';
    if (slot === 'Bow') return 'Range';
    if (slot === 'Thrown') return 'Range';

    return slot;
}

function cleanClassItemSource(value) {
    value = String(value || '').trim();

    if (!value) return '';
    if (value.toLowerCase() === 'unknown') return '';
    if (value.toLowerCase() === 'none') return '';
    if (value.toLowerCase() === 'no screenshots uploaded.') return '';
    if (value.toLowerCase() === 'no additional lore available.') return '';

    return value;
}

function getClassItemSource(droppedby, zone, quests, notes) {
    var parts = [];

    if (droppedby) parts.push(droppedby);
    if (zone) parts.push(zone);

    if (quests && quests !== 'None') {
        parts.push('Quest: ' + quests);
    }

    if (!parts.length && notes && /quest reward/i.test(notes)) {
        parts.push('Quest Reward');
    }

    return parts.join('<br>');
}

function getClassItemType(name, slot, droppedby, zone, quests, notes) {
    var text = [
        name,
        slot,
        droppedby,
        zone,
        quests,
        notes
    ].join(' ').toLowerCase();

    if (text.indexOf('recipe') !== -1) return 'Recipe';
    if (text.indexOf('quest') !== -1 || text.indexOf('reward') !== -1) return 'Quest';
    if (text.indexOf('clicky') !== -1 || text.indexOf('clickies') !== -1 || text.indexOf('effect:') !== -1) return 'Clickies';

    if (slot) return slot;

    return 'Miscellaneous';
}

function renderClassItemList(box, table, loading, items) {
    var tbody;
    var controls;
    var searchInput;
    var filterBox;
    var activeFilter = 'All';

    if (!table) return;

    tbody = table.querySelector('tbody') || table;
    controls = box.querySelector('.class-item-controls');
    var searchHolder = box.querySelector('.class-item-search-holder');

var searchHolder = box.querySelector('.class-item-search-holder');

searchInput = box.querySelector('.class-item-search');

if (!searchInput && searchHolder) {
    searchInput = document.createElement('input');
    searchInput.className = 'class-item-search';
    searchInput.type = 'search';
    searchInput.placeholder = 'Search items';
    searchHolder.appendChild(searchInput);
}
    filterBox = box.querySelector('.class-item-filter-buttons');

    if (loading) {
        loading.style.display = 'none';
    }

    if (controls) {
        controls.style.display = '';
    }

    table.style.display = '';

    buildClassItemFilters(filterBox, items, activeFilter, function (filterName) {
        activeFilter = filterName;
        drawClassItemRows(tbody, items, activeFilter, searchInput ? searchInput.value : '');
    });

    if (searchInput) {
        searchInput.addEventListener('input', function () {
            drawClassItemRows(tbody, items, activeFilter, searchInput.value);
        });
    }

    drawClassItemRows(tbody, items, activeFilter, '');
}

function buildClassItemFilters(filterBox, items, activeFilter, onFilterClick) {
    var orderedFilters = [
        'All',
        'Head',
        'Chest',
        'Robe',
        'Hands',
        'Legs',
        'Feet',
        'Forearm',
        'Bracelet',
        'Waist',
        'Neck',
        'Ear',
        'Ring',
        'Primary',
        'Secondary',
        'Shield',
        'Held',
        'Range',
        'Recipe',
        'Quest',
        'Clickies',
        'Miscellaneous'
    ];

    var counts = {};

    if (!filterBox) return;

    items.forEach(function (item) {
        counts.All = (counts.All || 0) + 1;
        counts[item.type] = (counts[item.type] || 0) + 1;
    });

    filterBox.innerHTML = '';

    orderedFilters.forEach(function (filterName) {
        var button;

        if (filterName !== 'All' && !counts[filterName]) return;

        button = document.createElement('button');
        button.type = 'button';
        button.className = 'class-item-filter-button';

        if (filterName === activeFilter) {
            button.className += ' is-active';
        }

        button.textContent = filterName + ' (' + (counts[filterName] || 0) + ')';

        button.addEventListener('click', function () {
            filterBox.querySelectorAll('.class-item-filter-button').forEach(function (btn) {
                btn.classList.remove('is-active');
            });

            button.classList.add('is-active');
            onFilterClick(filterName);
        });

        filterBox.appendChild(button);
    });
}

function drawClassItemRows(tbody, items, activeFilter, searchText) {
    var filtered;
    var query = String(searchText || '').toLowerCase();

    tbody.innerHTML = '';

    filtered = items.filter(function (item) {
        var haystack;

        if (activeFilter && activeFilter !== 'All' && item.type !== activeFilter) {
            return false;
        }

        if (!query) return true;

        haystack = [
            item.name,
            item.slot,
            item.level,
            item.source,
            item.type
        ].join(' ').toLowerCase();

        return haystack.indexOf(query) !== -1;
    });

    filtered.forEach(function (item) {
        var row = document.createElement('tr');

row.innerHTML =
'<td><a class="class-item-tooltip-link class-item-rarity-' + normalizeClassItemRarity(item.rarity) + '" data-class-item-name="' + encodeURIComponent(item.name) + '" href="' + mw.util.getUrl(item.name) + '">' + escapeClassItemHtml(item.name) + '</a></td>' +
    '<td>' + (item.level || '') + '</td>' +
    '<td>' + escapeClassItemHtml(item.slot || '') + '</td>' +
    '<td>' + item.source + '</td>';

        tbody.appendChild(row);
bindClassItemTooltip(row, item);
    });

    if (!filtered.length) {
        var emptyRow = document.createElement('tr');
        emptyRow.innerHTML = '<td colspan="4">No class items found.</td>';
        tbody.appendChild(emptyRow);
    }
}

function bindClassItemTooltip(row, item) {
    var link = row.querySelector('.class-item-tooltip-link');

    if (!link || !item) return;

    link.addEventListener('mouseenter', function (event) {
        showClassItemTooltip(item, event);
    });

    link.addEventListener('mousemove', moveClassItemTooltip);

    link.addEventListener('mouseleave', function () {
        hideClassItemTooltip();
    });
}

function getClassItemTooltipBox() {
    var tooltip = document.getElementById('class-item-tooltip');

    if (!tooltip) {
        tooltip = document.createElement('div');
        tooltip.id = 'class-item-tooltip';
        tooltip.className = 'class-item-tooltip';
        tooltip.style.display = 'none';
        document.body.appendChild(tooltip);
    }

    return tooltip;
}

function showClassItemTooltip(item, event) {
    var tooltip = getClassItemTooltipBox();
    var rarity = normalizeClassItemRarity(item.rarity);
    var html = '';

    function statLine(label, value) {
        if (!value || parseFloat(value) === 0) return '';
        return '<div><span>' + label + ':</span> ' + escapeClassItemHtml(value) + '</div>';
    }

    tooltip.className = 'class-item-tooltip class-item-tooltip-rarity-' + rarity;

    html += '<div class="class-item-tooltip-title class-item-rarity-' + rarity + ' cb-rarity-' + rarity + '" style="' + getClassItemRarityInlineStyle(rarity) + '">' +
        escapeClassItemHtml(item.name) +
        '</div>';

    if (item.notrade || item.lore) {
        html += '<div class="class-item-tooltip-flags">';
        if (item.notrade) html += '<span>NO TRADE</span>';
        if (item.lore) html += '<span>LORE</span>';
        html += '</div>';
    }

    if (item.level) html += '<div><b>Level:</b> ' + item.level + '</div>';
    if (item.slot) html += '<div><b>Slot:</b> ' + escapeClassItemHtml(item.slot) + '</div>';
    if (item.dur) html += '<div><b>DUR:</b> ' + item.dur + '</div>';
    if (item.gearhp) html += '<div><b>Gear HP:</b> ' + item.gearhp + '</div>';
    if (item.race) html += '<div><b>Race:</b> ' + escapeClassItemHtml(item.race) + '</div>';
    if (item.className) html += '<div><b>Class:</b> ' + escapeClassItemHtml(item.className) + '</div>';

    html += '<div class="class-item-tooltip-section">Item Stats</div>';
    html += '<div class="class-item-tooltip-stats">';
    html += statLine('DMG', item.dmg);
    html += statLine('AC', item.ac);
    html += statLine('STR', item.str);
    html += statLine('STA', item.sta);
    html += statLine('AGI', item.agi);
    html += statLine('DEX', item.dex);
    html += statLine('WIS', item.wis);
    html += statLine('INT', item.int);
    html += statLine('CHA', item.cha);
    html += statLine('HP', item.hp);
    html += statLine('HoT', item.hot);
    html += statLine('POW', item.pow);
    html += statLine('PoT', item.pot);
    html += statLine('PR', item.pr);
    html += statLine('DR', item.dr);
    html += statLine('FR', item.fr);
    html += statLine('CR', item.cr);
    html += statLine('LR', item.lr);
    html += statLine('AR', item.ar);
    html += '</div>';

    if (item.effect) html += '<div><b>Effect:</b> ' + escapeClassItemHtml(item.effect) + '</div>';
    if (item.proc) html += '<div><b>Proc:</b> ' + escapeClassItemHtml(item.proc) + '</div>';

    if (item.source) {
        html += '<div class="class-item-tooltip-section">Source</div>';
        html += '<div>' + item.source + '</div>';
    }

    tooltip.innerHTML = html;
    tooltip.style.display = 'block';

    moveClassItemTooltip(event);
}

function normalizeClassItemRarity(rarity) {
    rarity = String(rarity || 'common')
        .toLowerCase()
        .replace(/\s+/g, '-')
        .trim();

    if (rarity === 'ultrarare') return 'ultra-rare';
    if (rarity === 'ultra-rare') return 'ultra-rare';
    if (rarity === 'legendary') return 'legendary';
    if (rarity === 'unique') return 'unique';
    if (rarity === 'rare') return 'rare';
    if (rarity === 'uncommon') return 'uncommon';
    if (rarity === 'common') return 'common';

    return 'common';
}


function getClassItemRarityInlineStyle(rarity) {
    rarity = normalizeClassItemRarity(rarity);

    if (rarity === 'unique') {
        return 'color:#ffd700!important;text-shadow:0 0 8px rgba(255,215,0,0.85)!important;';
    }

    if (rarity === 'legendary') {
        return 'color:#ff8000!important;text-shadow:0 0 8px rgba(255,128,0,0.85)!important;';
    }

    if (rarity === 'ultra-rare') {
        return 'color:#ff33ff!important;text-shadow:0 0 8px rgba(255,51,255,0.85)!important;';
    }

    if (rarity === 'rare') {
        return 'color:#1e90ff!important;text-shadow:0 0 8px rgba(30,144,255,0.85)!important;';
    }

    if (rarity === 'uncommon') {
        return 'color:#00ff55!important;text-shadow:0 0 8px rgba(0,255,85,0.85)!important;';
    }

    return 'color:#ffffff!important;text-shadow:0 0 6px rgba(255,255,255,0.75)!important;';
}

function applyClassItemRarityStyle(element, rarity) {
    rarity = normalizeClassItemRarity(rarity);

    if (!element) return;

    if (rarity === 'unique') {
        element.style.setProperty('color', '#ffd700', 'important');
        element.style.setProperty('text-shadow', '0 0 8px rgba(255,215,0,0.85)', 'important');
        return;
    }

    if (rarity === 'legendary') {
        element.style.setProperty('color', '#ff8000', 'important');
        element.style.setProperty('text-shadow', '0 0 8px rgba(255,128,0,0.85)', 'important');
        return;
    }

    if (rarity === 'ultra-rare') {
        element.style.setProperty('color', '#ff33ff', 'important');
        element.style.setProperty('text-shadow', '0 0 8px rgba(255,51,255,0.85)', 'important');
        return;
    }

    if (rarity === 'rare') {
        element.style.setProperty('color', '#1e90ff', 'important');
        element.style.setProperty('text-shadow', '0 0 8px rgba(30,144,255,0.85)', 'important');
        return;
    }

    if (rarity === 'uncommon') {
        element.style.setProperty('color', '#00ff55', 'important');
        element.style.setProperty('text-shadow', '0 0 8px rgba(0,255,85,0.85)', 'important');
        return;
    }

    element.style.setProperty('color', '#ffffff', 'important');
    element.style.setProperty('text-shadow', '0 0 6px rgba(255,255,255,0.75)', 'important');
}

function moveClassItemTooltip(event) {
    var tooltip = getClassItemTooltipBox();
    var left = event.pageX + 16;
    var top = event.pageY + 16;

    if (left + 360 > window.scrollX + window.innerWidth) {
        left = event.pageX - 360;
    }

    tooltip.style.left = left + 'px';
    tooltip.style.top = top + 'px';
}

function hideClassItemTooltip() {
    var tooltip = document.getElementById('class-item-tooltip');

    if (tooltip) {
        tooltip.style.display = 'none';
    }
}

function escapeClassItemHtml(value) {
    return String(value == null ? '' : value)
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#039;');
}

function initGeneratedItemTooltipsForWiki($content) {
    var root = $content && $content[0] ? $content[0] : document;
    var items = window.EQOA_GENERATED_ITEMS || [];
    var lookup = {};
    var links;

    if (!items.length) {
        setTimeout(function () {
            initGeneratedItemTooltipsForWiki($content);
        }, 250);
        return;
    }

    function normalizeItemName(value) {
        return String(value || '')
            .replace(/\u00a0/g, ' ')
            .replace(/[’`]/g, "'")
            .replace(/\s+/g, ' ')
            .trim()
            .toLowerCase();
    }

    function looseItemName(value) {
        return normalizeItemName(value).replace(/[^a-z0-9]/g, '');
    }

    function stripDropText(value) {
        return String(value || '')
            .replace(/\(\d+%\)/g, '')
            .replace(/\([^)]*\)$/g, '')
            .replace(/\s+/g, ' ')
            .trim();
    }

    function rarityRank(item) {
        var rarity = normalizeClassItemRarity(item && item.rarity);

        if (rarity === 'ultra-rare') return 4;
        if (rarity === 'rare') return 3;
        if (rarity === 'uncommon') return 2;

        return 1;
    }

    function storeItem(key, item) {
        var current;

        if (!key) return;

        current = lookup[key];

        if (!current || rarityRank(item) > rarityRank(current)) {
            lookup[key] = item;
        }
    }

    function getItemFromLink(link) {
        var text = stripDropText(link.textContent);
        var href = link.getAttribute('href') || '';
        var title = '';

        if (href.indexOf('title=') !== -1) {
            title = decodeURIComponent(href.split('title=')[1].split('&')[0]).replace(/_/g, ' ');
        } else if (href.indexOf('/index.php?title=') !== -1) {
            title = decodeURIComponent(href.split('/index.php?title=')[1].split('&')[0]).replace(/_/g, ' ');
        }

        return (
            lookup[normalizeItemName(text)] ||
            lookup[looseItemName(text)] ||
            lookup[normalizeItemName(title)] ||
            lookup[looseItemName(title)] ||
            null
        );
    }

    function normalizeGeneratedItem(item) {
        return {
            name: item.name || '',
            rarity: item.rarity || 'common',
            slot: item.slot || '',
            level: item.level || item.lvl || '',
            race: item.race || '',
            className: Array.isArray(item.class) ? item.class.join(', ') : (item.class || item.classes || item.className || ''),
            notrade: item.notrade || item.noTrade || item.trade === 'NO TRADE',
            lore: item.lore || '',
            dmg: item.dmg || item.damage || '',
            ac: item.ac || '',
            str: item.str || '',
            sta: item.sta || '',
            agi: item.agi || '',
            dex: item.dex || '',
            wis: item.wis || '',
            int: item.int || '',
            cha: item.cha || '',
            hp: item.hp || '',
            hot: item.hot || '',
            pow: item.pow || '',
            pot: item.pot || '',
            pr: item.pr || '',
            dr: item.dr || '',
            fr: item.fr || '',
            cr: item.cr || '',
            lr: item.lr || '',
            ar: item.ar || '',
            dur: item.dur || '',
            gearhp: item.gearhp || item.gearHp || item.gear_hp || '',
            effect: item.effect || '',
            proc: item.proc || '',
            source: item.source || ''
        };
    }

    items.forEach(function (item) {
        if (!item || !item.name) return;

        storeItem(normalizeItemName(item.name), item);
        storeItem(looseItemName(item.name), item);
    });

    links = root.querySelectorAll(
        '#items-found-here-table td:first-child a, ' +
        '#items-found-here-table .items-found-here-name a, ' +
        '.zone-monster-table .zone-monster-drops a, ' +
        '.zone-monster-table td:nth-child(3) a, ' +
        '.eqoa-related-items a'
    );

    Array.prototype.forEach.call(links, function (link) {
        var item = getItemFromLink(link);
        var tooltipItem;
        var rarity;

        if (!item) return;

        tooltipItem = normalizeGeneratedItem(item);
        rarity = normalizeClassItemRarity(tooltipItem.rarity);

        link.classList.remove(
            'class-item-rarity-common',
            'class-item-rarity-uncommon',
            'class-item-rarity-rare',
            'class-item-rarity-ultra-rare',
            'class-item-rarity-legendary',
            'class-item-rarity-unique',
            'cb-rarity-common',
            'cb-rarity-uncommon',
            'cb-rarity-rare',
            'cb-rarity-ultra-rare',
            'cb-rarity-legendary',
            'cb-rarity-unique',
            'eqoa-related-tooltip-ready',
            'items-found-tooltip-ready'
        );

        link.classList.add('class-item-tooltip-link');
        link.classList.add('class-item-rarity-' + rarity);
        link.classList.add('cb-rarity-' + rarity);
        applyClassItemRarityStyle(link, rarity);

        link.onmouseenter = function (event) {
            showClassItemTooltip(tooltipItem, event);
        };

        link.onmousemove = moveClassItemTooltip;

        link.onmouseleave = function () {
            hideClassItemTooltip();
        };
    });
}

mw.hook('wikipage.content').add(initGeneratedItemTooltipsForWiki);
document.addEventListener('DOMContentLoaded', initGeneratedItemTooltipsForWiki);
setTimeout(initGeneratedItemTooltipsForWiki, 500);
setTimeout(initGeneratedItemTooltipsForWiki, 1500);

mw.hook('wikipage.content').add(initClassItemLists);

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

setTimeout(initClassItemLists, 500);
setTimeout(initClassItemLists, 1500);






function initItemsFoundHereSortControls(root) {
    root = root && root.jquery ? root[0] : (root || document);

    var table = root.querySelector('#items-found-here-table');
    if (!table) return;

    var wrapper = table.closest('.zone-drops-auto-wrapper') || table.parentElement;
    if (!wrapper) return;

    if (wrapper.querySelector('.zone-drops-controls')) return;

    var controls = document.createElement('div');
    controls.className = 'zone-drops-controls';

    controls.innerHTML =
        '<div class="zone-drops-controls-left">' +
            '<input class="zone-drops-search" type="search" placeholder="Search item">' +
        '</div>' +
        '<div class="zone-drops-controls-right">' +
            '<select class="zone-drops-sort-select">' +
                '<option value="name">Sort: Name</option>' +
                '<option value="level">Sort: Level</option>' +
                '<option value="class">Sort: Class</option>' +
                '<option value="dmg">Sort: DMG</option>' +
                '<option value="ac">Sort: AC</option>' +
                '<option value="slot">Sort: Slot</option>' +
                '<option value="rarity">Sort: Rarity</option>' +
            '</select>' +
            '<button type="button" class="zone-drops-sort-button">Sort</button>' +
            '<select class="zone-drops-class-select">' +
                '<option value="">All Classes</option>' +
                '<option value="WAR">WAR</option>' +
                '<option value="RAN">RAN</option>' +
                '<option value="PAL">PAL</option>' +
                '<option value="SK">SK</option>' +
                '<option value="MNK">MNK</option>' +
                '<option value="BRD">BRD</option>' +
                '<option value="RGE">RGE</option>' +
                '<option value="DRD">DRD</option>' +
                '<option value="SHA">SHA</option>' +
                '<option value="CL">CL</option>' +
                '<option value="MAG">MAG</option>' +
                '<option value="NEC">NEC</option>' +
                '<option value="ENC">ENC</option>' +
                '<option value="WIZ">WIZ</option>' +
                '<option value="ALC">ALC</option>' +
            '</select>' +
            '<button type="button" class="zone-drops-clear">Clear</button>' +
        '</div>';

    wrapper.insertBefore(controls, table);

    var searchInput = controls.querySelector('.zone-drops-search');
    var sortSelect = controls.querySelector('.zone-drops-sort-select');
    var sortButton = controls.querySelector('.zone-drops-sort-button');
    var classSelect = controls.querySelector('.zone-drops-class-select');
    var clearButton = controls.querySelector('.zone-drops-clear');

    function getRows() {
        return Array.from(table.querySelectorAll('tbody tr'));
    }

    function getCellText(row, index) {
        var cell = row.children[index];
        return cell ? cell.textContent.trim() : '';
    }

    function getSortValue(row, key) {
        if (key === 'name') return row.getAttribute('data-name') || getCellText(row, 0);
        if (key === 'level') return row.getAttribute('data-level') || getCellText(row, 1);
        if (key === 'class') return row.getAttribute('data-class') || getCellText(row, 2);

        return row.getAttribute('data-' + key) || '';
    }

    function numberOrText(value) {
        var number = parseFloat(String(value).replace(/[^\d.-]/g, ''));
        return isNaN(number) ? String(value).toLowerCase() : number;
    }

    function applyFilters() {
        var query = searchInput.value.trim().toLowerCase();
        var classFilter = classSelect.value.trim().toUpperCase();

        getRows().forEach(function (row) {
            var rowText = row.textContent.toLowerCase();
            var rowClass = String(row.getAttribute('data-class') || getCellText(row, 2)).toUpperCase();
            var matchesSearch = !query || rowText.indexOf(query) !== -1;
            var matchesClass = !classFilter || rowClass.indexOf(classFilter) !== -1;

            row.style.display = matchesSearch && matchesClass ? '' : 'none';
        });
    }

    function sortRows() {
        var tbody = table.querySelector('tbody');
        var key = sortSelect.value;
        var rows = getRows();

        rows.sort(function (a, b) {
            var aValue = numberOrText(getSortValue(a, key));
            var bValue = numberOrText(getSortValue(b, key));

            if (typeof aValue === 'number' && typeof bValue === 'number') {
                return aValue - bValue;
            }

            return String(aValue).localeCompare(String(bValue));
        });

        rows.forEach(function (row) {
            tbody.appendChild(row);
        });

        applyFilters();
    }

    searchInput.addEventListener('input', applyFilters);
    classSelect.addEventListener('change', applyFilters);
    sortButton.addEventListener('click', sortRows);

    clearButton.addEventListener('click', function () {
        searchInput.value = '';
        classSelect.value = '';
        sortSelect.value = 'name';
        applyFilters();
    });
}






function initEqoaCategoryLists() {
    document.querySelectorAll('.eqoa-category-list').forEach(function (box) {
        var category = box.getAttribute('data-category');

        if (!category || box.classList.contains('eqoa-category-list-ready')) return;

        box.classList.add('eqoa-category-list-ready');
        box.innerHTML = 'Loading...';

        new mw.Api().get({
            action: 'query',
            list: 'categorymembers',
            cmtitle: 'Category:' + category,
            cmlimit: 500,
            format: 'json'
        }).then(function (data) {
            var pages = data.query.categorymembers || [];

            if (!pages.length) {
                box.innerHTML = 'No entries found.';
                return;
            }

            pages.sort(function (a, b) {
                return a.title.localeCompare(b.title);
            });

box.innerHTML =
    '<table class="zone-data-table zone-quest-table">' +
        '<tr>' +
            '<th class="zone-quest-name">Quest</th>' +
            '<th class="zone-quest-type">Type</th>' +
            '<th class="zone-quest-starter">Starter</th>' +
            '<th class="zone-quest-location">Location</th>' +
        '</tr>' +
        pages.map(function (page) {
            return '<tr>' +
                '<td class="zone-quest-name"><a href="' + mw.util.getUrl(page.title) + '">' + page.title + '</a></td>' +
                '<td class="zone-quest-type"></td>' +
                '<td class="zone-quest-starter"></td>' +
                '<td class="zone-quest-location"></td>' +
            '</tr>';
        }).join('') +
    '</table>';
        });
    });
}

mw.hook('wikipage.content').add(initEqoaCategoryLists);
document.addEventListener('DOMContentLoaded', initEqoaCategoryLists);



function initEqoaClassIndex() {
    document.querySelectorAll('.eqoa-class-index').forEach(function (root) {
        if (root.classList.contains('eqoa-class-index-ready')) return;

        var grid = root.querySelector('.eqoa-class-index-grid');
        var cards = root.querySelectorAll('.eqoa-class-card');
        var toolbar = document.createElement('div');
        var filters = document.createElement('div');
        var roles;
        var activeRole = 'all';

        if (!grid) return;

        root.classList.add('eqoa-class-index-ready');

        toolbar.className = 'eqoa-class-index-toolbar';
        filters.className = 'eqoa-class-index-filters';

        if (root.classList.contains('eqoa-race-index')) {
            roles = [
                ['all', 'All'],
                ['good', 'Good'],
                ['evil', 'Evil'],
                ['neutral', 'Neutral']
            ];
        } else {
            roles = [
                ['all', 'All'],
                ['tank', 'Tank'],
                ['melee', 'Melee'],
                ['healer', 'Healer'],
                ['support', 'Support'],
                ['caster', 'Caster']
            ];
        }

        roles.forEach(function (role) {
            var button = document.createElement('button');

            button.type = 'button';
            button.textContent = role[1];
            button.setAttribute('data-role-filter', role[0]);

            if (role[0] === 'all') {
                button.classList.add('is-active');
            }

            button.addEventListener('click', function () {
                activeRole = role[0];

                filters.querySelectorAll('button').forEach(function (btn) {
                    btn.classList.remove('is-active');
                });

                button.classList.add('is-active');
                applyFilters();
            });

            filters.appendChild(button);
        });

        toolbar.appendChild(filters);
        root.insertBefore(toolbar, grid);

        function cardHasRole(card, role) {
            if (role === 'all') return true;
            return card.classList.contains('eqoa-role-' + role);
        }

        function applyFilters() {
            cards.forEach(function (card) {
                card.style.display = cardHasRole(card, activeRole) ? '' : 'none';
            });
        }

        applyFilters();
    });
}

     

mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:ClassSpellGeneratedSpells1.js&action=raw&ctype=text/javascript');
mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:ClassSpellGeneratedSpells2.js&action=raw&ctype=text/javascript');
mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:ClassSpellGeneratedSpells3.js&action=raw&ctype=text/javascript');
mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:ClassSpellGeneratedSpells4.js&action=raw&ctype=text/javascript');
mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:ClassSpellGeneratedSpells5.js&action=raw&ctype=text/javascript');

mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:ClassSpellGroupOverrides.js&action=raw&ctype=text/javascript&v=20260604');

mw.loader.load(mw.config.get('wgScript') + '?title=MediaWiki:ClassSpellGroupOverrides.js&action=raw&ctype=text/javascript');

mw.hook('wikipage.content').add(initEqoaClassIndex);
document.addEventListener('DOMContentLoaded', initEqoaClassIndex);
setTimeout(initEqoaClassIndex, 500);



function initEqoaSpellLists() {
    var lists = document.querySelectorAll('[data-eqoa-spell-list]');
    var spells = window.EQOA_GENERATED_SPELLS || [];

    if (!lists.length) return;

    if (!spells.length) {
        setTimeout(initEqoaSpellLists, 250);
        return;
    }

    function escapeHtml(value) {
        return String(value == null ? '' : value)
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;')
            .replace(/'/g, '&#039;');
    }

    function fileUrl(fileName) {
        fileName = String(fileName || '').trim();

        if (!fileName) return '';
        if (/^https?:\/\//i.test(fileName)) return fileName;

        return mw.config.get('wgScript') + '?title=Special:Redirect/file/' + encodeURIComponent(fileName);
    }

    function pageUrl(title) {
        if (window.mw && mw.util && mw.util.getUrl) {
            return mw.util.getUrl(title);
        }

        return '/index.php?title=' + encodeURIComponent(String(title || '').replace(/ /g, '_'));
    }

    function iconHtml(fileName, className, altText) {
        var url = fileUrl(fileName);

        if (!url) return '';

        return '<img class="' + className + '" src="' + url + '" alt="' + escapeHtml(altText || '') + '">';
    }

    function classIconFile(classCode) {
        return 'ClassIcon' + classCode + '.png';
    }

    function classIconsHtml(spell) {
        return (spell.classes || []).map(function (classCode) {
            return iconHtml(classIconFile(classCode), 'eqoa-spell-class-icon', classCode);
        }).join('');
    }

function spellIconFile(spell) {
    var iconName = String(spell.icon || '').trim();

    if (!iconName) return '';

    iconName = iconName
        .replace(/\.[a-z0-9]+$/i, '')
        .replace(/[^a-z0-9]+/gi, '');

    return 'SpellIcon' + iconName + '.png';
}

    function classText(spell) {
        return (spell.classNames || spell.classes || []).join(', ');
    }

    function sortSpells(rows, sortKey) {
        rows.sort(function (a, b) {
            if (sortKey === 'name') {
                return String(a.name || '').localeCompare(String(b.name || ''));
            }

            if (sortKey === 'level') {
                return (parseFloat(a.level) || 0) - (parseFloat(b.level) || 0);
            }

            return String(classText(a)).localeCompare(String(classText(b)));
        });
    }

    function getSpellTooltipBox() {
        var tooltip = document.getElementById('eqoa-spell-tooltip');

        if (!tooltip) {
            tooltip = document.createElement('div');
            tooltip.id = 'eqoa-spell-tooltip';
            tooltip.className = 'eqoa-spell-tooltip';
            tooltip.style.display = 'none';
            document.body.appendChild(tooltip);
        }

        return tooltip;
    }

    function formatSpellValue(value) {
        if (value === undefined || value === null || value === '') {
            return '0';
        }

        return escapeHtml(value);
    }

    function showSpellTooltip(spell, event) {
        var tooltip = getSpellTooltipBox();
        var name = escapeHtml(spell.name || '');
        var scope = escapeHtml(spell.scope || '');
        var cast = formatSpellValue(spell.castTime);
        var recast = formatSpellValue(spell.recast);
        var power = formatSpellValue(spell.powerCost);
        var equipReq = escapeHtml((spell.classNames || spell.classes || ['ANY']).join(', ') || 'ANY');
        var level = formatSpellValue(spell.level);
        var range = formatSpellValue(spell.range);
        var description = escapeHtml(spell.description || '');

        tooltip.innerHTML =
            '<div class="eqoa-spell-tooltip-main">' +
                '<div class="eqoa-spell-tooltip-name">' + name + '</div>' +
                '<div>Scope: ' + scope + ' <span>Cast: ' + cast + '</span></div>' +
                '<div>Recast: ' + recast + ' <span>Power: ' + power + '</span></div>' +
                '<div>EquipReq: ' + equipReq + '</div>' +
                '<div>Level: ' + level + ' <span>Range: ' + range + '</span></div>' +
            '</div>' +
            '<div class="eqoa-spell-tooltip-desc">' +
                description +
            '</div>';

        tooltip.style.display = 'block';
        moveSpellTooltip(event);
    }

    function moveSpellTooltip(event) {
        var tooltip = getSpellTooltipBox();
        var left = event.pageX + 18;
        var top = event.pageY + 18;

        if (left + 430 > window.scrollX + window.innerWidth) {
            left = event.pageX - 430;
        }

        tooltip.style.left = left + 'px';
        tooltip.style.top = top + 'px';
    }

    function hideSpellTooltip() {
        var tooltip = document.getElementById('eqoa-spell-tooltip');

        if (tooltip) {
            tooltip.style.display = 'none';
        }
    }

    function bindSpellTooltip(row, spell) {
        row.addEventListener('mouseenter', function (event) {
            showSpellTooltip(spell, event);
        });

        row.addEventListener('mousemove', moveSpellTooltip);

        row.addEventListener('mouseleave', hideSpellTooltip);
    }

    function renderRows(tbody, rows) {
        tbody.innerHTML = '';

        rows.forEach(function (spell) {
            var row = document.createElement('tr');
            var name = escapeHtml(spell.name || '');
            var level = escapeHtml(spell.level || '');
            var classes = classIconsHtml(spell);
            var skill = escapeHtml(classText(spell));
            var icon = iconHtml(spellIconFile(spell), 'eqoa-spell-icon', spell.name);

            row.className = 'eqoa-spell-row';

row.innerHTML =
    '<td class="eqoa-spell-name-cell">' + icon + '<a href="' + pageUrl(spell.name) + '">' + name + '</a></td>' +
    '<td class="eqoa-spell-level-cell">' + level + '</td>' +
    '<td class="eqoa-spell-classes-cell">' + classes + '</td>';

            bindSpellTooltip(row, spell);
            tbody.appendChild(row);
        });
    }

    lists.forEach(function (box) {
        var classCode = String(box.getAttribute('data-class') || '').toUpperCase().trim();
        var rows;
        var table;
        var tbody;
        var sortSelect;
        var loading;

        if (box.classList.contains('eqoa-spell-list-ready')) return;
        box.classList.add('eqoa-spell-list-ready');

        rows = spells.filter(function (spell) {
            if (String(spell.spellGroup || 'Base') !== 'Base') return false;
            if (!classCode) return true;
            return (spell.classes || []).indexOf(classCode) !== -1;
        });

        sortSpells(rows, 'level');

        if (!rows.length) {
            box.innerHTML += '<div class="eqoa-spell-empty">No spells found.</div>';
            return;
        }

        loading = box.querySelector('.eqoa-spell-list__loading');
        if (loading) loading.remove();

        box.insertAdjacentHTML('beforeend',
            '<div class="eqoa-spell-toolbar">' +
                '<div class="eqoa-spell-count">' + rows.length + ' spells</div>' +
                '<div class="eqoa-spell-sort-wrap">' +
                    '<select class="eqoa-spell-sort">' +
                        '<option value="level">Sort: Level</option>' +
                        '<option value="name">Sort: Name</option>' +
                        '<option value="class">Sort: Class</option>' +
                    '</select>' +
                '</div>' +
            '</div>' +
            '<table class="eqoa-spell-list-table">' +
                '<tr>' +
                    '<th>Name</th>' +
                    '<th>Level</th>' +
                    '<th>Classes</th>' +
                '</tr>' +
            '</table>'
        );

        table = box.querySelector('.eqoa-spell-list-table');
        tbody = document.createElement('tbody');
        table.appendChild(tbody);

        renderRows(tbody, rows);

        sortSelect = box.querySelector('.eqoa-spell-sort');
        sortSelect.addEventListener('change', function () {
            sortSpells(rows, sortSelect.value);
            renderRows(tbody, rows);
        });
    });
}

mw.hook('wikipage.content').add(initEqoaSpellLists);
document.addEventListener('DOMContentLoaded', initEqoaSpellLists);
setTimeout(initEqoaSpellLists, 500);
setTimeout(initEqoaSpellLists, 1500);

function initEqoaSpellPathLists() {
    var spellData = window.EQOA_GENERATED_SPELLS || [];

    document.querySelectorAll('[data-eqoa-spell-path-list]').forEach(function (box) {
        var classCode;
        var spellGroup;
        var title;
        var rows;
        var path1;
        var path2;
        var loading;

        classCode = String(box.getAttribute('data-class') || '').trim().toUpperCase();
        spellGroup = normalizeEqoaSpellPathGroup(box.getAttribute('data-spell-group'));
        title = box.querySelector('.eqoa-spell-list__title')
            ? box.querySelector('.eqoa-spell-list__title').textContent.trim()
            : 'Spell Path';

        if (!spellData.length) {
            setTimeout(initEqoaSpellPathLists, 250);
            return;
        }

        loading = box.querySelector('.eqoa-spell-list__loading');
        if (loading) loading.remove();

        rows = spellData.filter(function (spell) {
            var currentGroup = normalizeEqoaSpellPathGroup(spell.spellGroup);

            if (currentGroup !== spellGroup) return false;
            if (!classCode) return true;

            return (spell.classes || []).map(function (code) {
                return String(code || '').toUpperCase();
            }).indexOf(classCode) !== -1;
        });

        path1 = rows.filter(function (spell) {
            var spellPath = String(spell.spellPath || '');
            return spellPath === '1' || spellPath === 'both';
        });

        path2 = rows.filter(function (spell) {
            var spellPath = String(spell.spellPath || '');
            return spellPath === '2' || spellPath === 'both';
        });

        box.innerHTML =
            '<div class="eqoa-spell-list__title">' + escapeEqoaSpellPathHtml(title) + '</div>' +
            '<div class="eqoa-spell-path-grid">' +
                buildEqoaSpellPathSection('Path 1', path1) +
                buildEqoaSpellPathSection('Path 2', path2) +
            '</div>';

        bindEqoaSpellPathTooltipEvents(box, rows);
    });
}

function normalizeEqoaSpellPathGroup(value) {
    value = String(value || '').toLowerCase().replace(/\s+/g, '');

    if (value === 'classpath' || value === 'path') return 'ClassPath';
    if (value === 'exemplarpath' || value === 'exemplar') return 'ExemplarPath';

    return value;
}

function buildEqoaSpellPathSection(title, spells) {
    var html = '';

    html += '<div class="eqoa-spell-path-section">';
    html += '<div class="eqoa-spell-path-title">' + escapeEqoaSpellPathHtml(title) + '</div>';

    html += '<table class="eqoa-spell-list-table">';
html += '<tr>';
html += '<th>Name</th>';
html += '<th>Level</th>';
html += '<th>Classes</th>';
html += '</tr>';

    if (!spells.length) {
        html += '<tr class="eqoa-spell-row"><td colspan="4">No spells found.</td></tr>';
    } else {
        spells.forEach(function (spell) {
            html += '<tr class="eqoa-spell-row" data-eqoa-spell-name="' + escapeEqoaSpellPathHtml(spell.name) + '">';
            html += '<td class="eqoa-spell-name-cell">';
            html += buildEqoaSpellPathIcon(spell);
            html += '<a class="eqoa-spell-link" href="' + eqoaSpellPathPageUrl(spell.name) + '" data-eqoa-spell-name="' + escapeEqoaSpellPathHtml(spell.name) + '">';
            html += escapeEqoaSpellPathHtml(spell.name);
            html += '</a>';
            html += '</td>';
            html += '<td class="eqoa-spell-level-cell">' + escapeEqoaSpellPathHtml(spell.level || '') + '</td>';
            html += '<td class="eqoa-spell-classes-cell">' + buildEqoaSpellPathClassIcons(spell) + '</td>';
            html += '</tr>';
        });
    }

    html += '</table>';
    html += '</div>';

    return html;
}

function buildEqoaSpellPathIcon(spell) {
    var fileName = spellIconNameToFile(spell.icon);

    if (!fileName) return '';

    return '<img class="eqoa-spell-icon" src="' + eqoaSpellPathFileUrl(fileName) + '" alt="' + escapeEqoaSpellPathHtml(spell.name || '') + '">';
}

function spellIconNameToFile(iconName) {
    iconName = String(iconName || '').trim();

    if (!iconName) return '';

    iconName = iconName
        .replace(/\.[a-z0-9]+$/i, '')
        .replace(/[^a-z0-9]+/gi, '');

    return 'SpellIcon' + iconName + '.png';
}

function buildEqoaSpellPathClassIcons(spell) {
    var classes = spell.classes || [];

    if (!classes.length) return '';

    return classes.map(function (classCode) {
        return '<img class="eqoa-spell-class-icon" src="' + eqoaSpellPathFileUrl('ClassIcon' + classCode + '.png') + '" alt="' + escapeEqoaSpellPathHtml(classCode) + '" title="' + escapeEqoaSpellPathHtml(classCode) + '">';
    }).join('');
}

function bindEqoaSpellPathTooltipEvents(root, spells) {
    var lookup = {};

    spells.forEach(function (spell) {
        lookup[String(spell.name || '').toLowerCase()] = spell;
    });

    root.querySelectorAll('.eqoa-spell-row, .eqoa-spell-link').forEach(function (el) {
        var spellName = String(el.getAttribute('data-eqoa-spell-name') || el.textContent || '').trim();
        var spell = lookup[spellName.toLowerCase()];

        if (!spell) return;

        el.onmouseenter = function (event) {
            showEqoaSpellPathTooltip(spell, event);
        };

        el.onmousemove = function (event) {
            moveEqoaSpellPathTooltip(event);
        };

        el.onmouseleave = function () {
            hideEqoaSpellPathTooltip();
        };
    });
}

function getEqoaSpellPathTooltipBox() {
    var tooltip = document.getElementById('eqoa-spell-tooltip');

    if (!tooltip) {
        tooltip = document.createElement('div');
        tooltip.id = 'eqoa-spell-tooltip';
        tooltip.className = 'eqoa-spell-tooltip';
        tooltip.style.display = 'none';
        document.body.appendChild(tooltip);
    }

    return tooltip;
}

function showEqoaSpellPathTooltip(spell, event) {
    var tooltip = getEqoaSpellPathTooltipBox();
    var name = escapeEqoaSpellPathHtml(spell.name || '');
    var scope = escapeEqoaSpellPathHtml(spell.scope || '');
    var cast = formatEqoaSpellPathValue(spell.castTime);
    var recast = formatEqoaSpellPathValue(spell.recast);
    var power = formatEqoaSpellPathValue(spell.powerCost);
    var equipReq = escapeEqoaSpellPathHtml(spell.reqEquipment || 'ANY');
    var level = formatEqoaSpellPathValue(spell.level);
    var range = formatEqoaSpellPathValue(spell.range);
    var description = escapeEqoaSpellPathHtml(spell.description || '');

    tooltip.innerHTML =
        '<div class="eqoa-spell-tooltip-main">' +
            '<div class="eqoa-spell-tooltip-name">' + name + '</div>' +
            '<div>Scope: ' + scope + ' <span>Cast: ' + cast + '</span></div>' +
            '<div>Recast: ' + recast + ' <span>Power: ' + power + '</span></div>' +
            '<div>EquipReq: ' + equipReq + '</div>' +
            '<div>Level: ' + level + ' <span>Range: ' + range + '</span></div>' +
        '</div>' +
        '<div class="eqoa-spell-tooltip-desc">' +
            description +
        '</div>';

    tooltip.style.display = 'block';
    moveEqoaSpellPathTooltip(event);
}

function moveEqoaSpellPathTooltip(event) {
    var tooltip = getEqoaSpellPathTooltipBox();
    var left = event.pageX + 18;
    var top = event.pageY + 18;

    if (left + 430 > window.scrollX + window.innerWidth) {
        left = event.pageX - 430;
    }

    tooltip.style.left = left + 'px';
    tooltip.style.top = top + 'px';
}

function hideEqoaSpellPathTooltip() {
    var tooltip = document.getElementById('eqoa-spell-tooltip');

    if (tooltip) {
        tooltip.style.display = 'none';
    }
}

function formatEqoaSpellPathValue(value) {
    if (value === undefined || value === null || value === '') {
        return '0';
    }

    return escapeEqoaSpellPathHtml(value);
}

function eqoaSpellPathFileUrl(fileName) {
    fileName = String(fileName || '').trim();

    if (!fileName) return '';
    if (/^https?:\/\//i.test(fileName)) return fileName;

    return mw.config.get('wgScript') + '?title=Special:Redirect/file/' + encodeURIComponent(fileName);
}

function eqoaSpellPathPageUrl(title) {
    if (window.mw && mw.util && mw.util.getUrl) {
        return mw.util.getUrl(title);
    }

    return '/index.php?title=' + encodeURIComponent(String(title || '').replace(/ /g, '_'));
}

function escapeEqoaSpellPathHtml(value) {
    return String(value || '')
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#039;');
}

function initZoneNpcListFilters() {
    document.querySelectorAll('.zone-npc-list-wrapper').forEach(function (wrapper) {
        var table = wrapper.querySelector('.zone-monster-table');
        var bar;
        var input;
        var clear;

        if (!table) return;
        if (wrapper.classList.contains('zone-npc-filter-ready')) return;

        wrapper.classList.add('zone-npc-filter-ready');

        bar = document.createElement('div');
        bar.className = 'zone-npc-filter-bar';

        input = document.createElement('input');
        input.className = 'zone-npc-search-input';
        input.type = 'search';
        input.placeholder = 'Search NPCs';

        clear = document.createElement('button');
        clear.className = 'zone-npc-search-clear';
        clear.type = 'button';
        clear.textContent = 'Clear';

        bar.appendChild(input);
        bar.appendChild(clear);

        wrapper.insertBefore(bar, table);

        function filterRows() {
            var query = String(input.value || '').toLowerCase();
            var rows = table.querySelectorAll('tr');

            Array.prototype.forEach.call(rows, function (row, index) {
                var text;

                if (index === 0) return;

                text = String(row.textContent || '').toLowerCase();
                row.style.display = !query || text.indexOf(query) !== -1 ? '' : 'none';
            });
        }

        input.addEventListener('input', filterRows);

        clear.addEventListener('click', function () {
            input.value = '';
            filterRows();
            input.focus();
        });
    });
}

function initEqoaCategoryPageLists() {
    var boxes = document.querySelectorAll('.eqoa-category-page-list');

    if (!boxes.length) return;

    boxes.forEach(function (box) {
        var category = box.getAttribute('data-category');
        var api;

        if (!category) return;
        if (box.classList.contains('eqoa-category-page-list-ready')) return;

        box.classList.add('eqoa-category-page-list-ready');
        box.textContent = 'Loading...';

        api = new mw.Api();

        api.get({
            action: 'query',
            list: 'categorymembers',
            cmtitle: 'Category:' + category,
            cmnamespace: 0,
            cmlimit: 500,
            cmsort: 'sortkey',
            cmdir: 'asc',
            format: 'json'
        }).then(function (data) {
            var pages = data.query && data.query.categorymembers
                ? data.query.categorymembers
                : [];

            var list;

            box.innerHTML = '';

            if (!pages.length) {
                box.textContent = 'No pages found.';
                return;
            }

            list = document.createElement('ul');
            list.className = 'eqoa-category-page-list-items';

            pages.forEach(function (page) {
                var item = document.createElement('li');
                var link = document.createElement('a');

                link.href = mw.util.getUrl(page.title);
                link.textContent = page.title;

                item.appendChild(link);
                list.appendChild(item);
            });

            box.appendChild(list);
        }).catch(function () {
            box.textContent = 'Unable to load category.';
        });
    });
}

function initQuestRewardItemTooltips() {
    var links = document.querySelectorAll('.quest-reward-items a');
    var items = window.EQOA_GENERATED_ITEMS || [];

    if (!links.length) return;

    if (!items.length) {
        setTimeout(initQuestRewardItemTooltips, 500);
        return;
    }

    function normalizeQuestRewardRarity(rarity) {
        return String(rarity || 'common')
            .toLowerCase()
            .replace(/\s+/g, '-');
    }

    function getQuestRewardRarityStyle(rarity) {
        rarity = normalizeQuestRewardRarity(rarity);

        if (rarity === 'uncommon') {
            return {
                color: '#20ff45',
                shadow: '0 0 6px rgba(32, 255, 69, 0.55)'
            };
        }

        if (rarity === 'rare') {
            return {
                color: '#1e88ff',
                shadow: '0 0 6px rgba(30, 136, 255, 0.65)'
            };
        }

        if (rarity === 'ultra-rare') {
            return {
                color: '#ff3cff',
                shadow: '0 0 8px rgba(255, 60, 255, 0.75)'
            };
        }

        if (rarity === 'legendary') {
            return {
                color: '#ff8000',
                shadow: '0 0 8px rgba(255, 128, 0, 0.85)'
            };
        }

        if (rarity === 'unique') {
            return {
                color: '#ffd700',
                shadow: '0 0 8px rgba(255, 215, 0, 0.85)'
            };
        }

        return {
            color: '#ffffff',
            shadow: '0 0 6px rgba(255, 255, 255, 0.45)'
        };
    }

    function applyQuestRewardRarityStyle(link, item) {
        var rarity = normalizeQuestRewardRarity(item.rarity);
        var style = getQuestRewardRarityStyle(rarity);

        [
            'class-item-rarity-common',
            'class-item-rarity-uncommon',
            'class-item-rarity-rare',
            'class-item-rarity-ultra-rare',
            'class-item-rarity-legendary',
            'class-item-rarity-unique'
        ].forEach(function (className) {
            link.classList.remove(className);
        });

        link.classList.add('class-item-rarity-' + rarity);

        link.style.setProperty('color', style.color, 'important');
        link.style.setProperty('font-weight', '800', 'important');
        link.style.setProperty('text-shadow', style.shadow, 'important');
        link.style.setProperty('text-decoration-color', style.color, 'important');
    }

    links.forEach(function (link) {
        var itemName;
        var item;

        itemName = (link.getAttribute('data-class-item-name') || link.textContent || '')
            .replace(/_/g, ' ')
            .trim();

        try {
            itemName = decodeURIComponent(itemName);
        } catch (e) {}

        if (!itemName) return;

        item = items.find(function (entry) {
            return String(entry.name || '').toLowerCase() === itemName.toLowerCase();
        });

        if (!item) return;

        link.classList.add('quest-reward-tooltip-ready');
        link.classList.add('class-item-tooltip-link');
        link.setAttribute('data-class-item-name', encodeURIComponent(item.name));

        applyQuestRewardRarityStyle(link, item);

        if (link.getAttribute('data-quest-reward-tooltip-bound') === '1') return;
        link.setAttribute('data-quest-reward-tooltip-bound', '1');

        link.addEventListener('mouseenter', function (event) {
            applyQuestRewardRarityStyle(link, item);

            if (typeof showClassItemTooltip === 'function') {
                showClassItemTooltip(item, event);
            }
        });

        link.addEventListener('mousemove', function (event) {
            if (typeof moveClassItemTooltip === 'function') {
                moveClassItemTooltip(event);
            }
        });

        link.addEventListener('mouseleave', function () {
            applyQuestRewardRarityStyle(link, item);

            if (typeof hideClassItemTooltip === 'function') {
                hideClassItemTooltip();
            }
        });
    });
}





function initEqoaQuestIndexFilters() {
    document.querySelectorAll('.eqoa-quest-index-section').forEach(function (section) {
        var filterRow = section.querySelector('.eqoa-quest-filter-row');
        var filters = section.querySelectorAll('.eqoa-quest-filter');
        var panels = section.querySelectorAll('.eqoa-quest-panel');
        var search;

        if (!filterRow || !filters.length || !panels.length) return;

        search = section.querySelector('.eqoa-quest-search');

        if (!search) {
            search = document.createElement('input');
            search.type = 'search';
            search.className = 'eqoa-quest-search';
            search.placeholder = 'Search quests';
            filterRow.insertAdjacentElement('afterend', search);
        }

        function setActive(target) {
            filters.forEach(function (filter) {
                filter.classList.toggle(
                    'is-active',
                    filter.getAttribute('data-quest-filter') === target
                );
            });

            panels.forEach(function (panel) {
                var active = panel.getAttribute('data-quest-panel') === target;

                panel.classList.toggle('is-active', active);
                panel.style.display = active ? 'block' : 'none';
            });

            filterQuestList();
        }

        function filterQuestList() {
            var activePanel = section.querySelector('.eqoa-quest-panel.is-active');
            var query = String(search.value || '').toLowerCase();

            if (!activePanel) return;

            activePanel.querySelectorAll('li').forEach(function (li) {
                li.style.display = li.textContent.toLowerCase().indexOf(query) !== -1
                    ? ''
                    : 'none';
            });
        }

        if (!section.classList.contains('eqoa-quest-index-ready')) {
            section.classList.add('eqoa-quest-index-ready');

            filters.forEach(function (filter) {
                filter.addEventListener('click', function () {
                    setActive(filter.getAttribute('data-quest-filter'));
                });
            });

            search.addEventListener('input', filterQuestList);
        }

        var activeFilter = section.querySelector('.eqoa-quest-filter.is-active') || filters[0];

        setActive(activeFilter.getAttribute('data-quest-filter'));
    });
}

mw.hook('wikipage.content').add(initEqoaQuestIndexFilters);
document.addEventListener('DOMContentLoaded', initEqoaQuestIndexFilters);
setTimeout(initEqoaQuestIndexFilters, 500);
setTimeout(initEqoaQuestIndexFilters, 1500);

        



function initEqoaQuestIndexExpandableSections() {
    document.querySelectorAll('.eqoa-quest-index-section').forEach(function (section) {
        var title = section.querySelector(':scope > .eqoa-section-title');

        if (!title) return;
        if (section.classList.contains('eqoa-quest-expand-ready')) return;

        section.classList.add('eqoa-quest-expand-ready');

        title.addEventListener('click', function () {
            section.classList.toggle('is-open');
        });
    });
}

mw.hook('wikipage.content').add(initEqoaQuestIndexExpandableSections);
document.addEventListener('DOMContentLoaded', initEqoaQuestIndexExpandableSections);
setTimeout(initEqoaQuestIndexExpandableSections, 500);
setTimeout(initEqoaQuestIndexExpandableSections, 1500);




function centerSoaHomeBanner() {
    document.querySelectorAll('.soa-home-banner-wrap').forEach(function (wrap) {
        var image = wrap.querySelector('img');

        if (!image) return;

        wrap.style.display = 'flex';
        wrap.style.justifyContent = 'center';
        wrap.style.alignItems = 'center';
        wrap.style.width = '100%';
        wrap.style.overflow = 'hidden';
        wrap.style.background = 'transparent';

        image.style.display = 'block';
        image.style.width = '100%';
        image.style.maxWidth = '1200px';
        image.style.height = 'auto';
        image.style.margin = '0 auto';

        image.parentElement.style.display = 'block';
        image.parentElement.style.margin = '0 auto';
    });
}

document.addEventListener('DOMContentLoaded', centerSoaHomeBanner);
mw.hook('wikipage.content').add(centerSoaHomeBanner);
setTimeout(centerSoaHomeBanner, 250);
setTimeout(centerSoaHomeBanner, 1000);









function moveMobilePageActionsAfterNavigation() {
    var isMobile = window.matchMedia && window.matchMedia('(max-width: 700px)').matches;
    var actions = document.getElementById('p-cactions');
    var sidebar = document.getElementById('sidebar');
    var navigation = document.getElementById('p-navigation');
    var column = document.getElementById('column-one');
    var logo = document.getElementById('p-logo');

    if (!actions || !column) return;

    if (isMobile) {
        if (navigation && navigation.parentNode) {
            navigation.insertAdjacentElement('afterend', actions);
        } else if (sidebar) {
            sidebar.appendChild(actions);
        } else {
            column.appendChild(actions);
        }

        actions.classList.add('eqoa-mobile-page-actions-bottom');
        return;
    }

    actions.classList.remove('eqoa-mobile-page-actions-bottom');

    if (logo && logo.parentNode === column) {
        column.insertBefore(actions, logo);
    } else {
        column.insertBefore(actions, column.firstChild);
    }
}

document.addEventListener('DOMContentLoaded', moveMobilePageActionsAfterNavigation);
mw.hook('wikipage.content').add(moveMobilePageActionsAfterNavigation);
window.addEventListener('resize', moveMobilePageActionsAfterNavigation);
window.addEventListener('orientationchange', function () {
    setTimeout(moveMobilePageActionsAfterNavigation, 150);
});

setTimeout(moveMobilePageActionsAfterNavigation, 250);
setTimeout(moveMobilePageActionsAfterNavigation, 1000);






function initNpcPageItemTooltips() {
    var links = document.querySelectorAll('.eqoa-npc-page .eqoa-npc-zone-table td:nth-child(4) a');
    var items = window.EQOA_GENERATED_ITEMS || [];

    if (!links.length) return;

    if (!items.length) {
        setTimeout(initNpcPageItemTooltips, 500);
        return;
    }

    function normalizeNpcDropName(text) {
        return String(text || '')
            .replace(/\u2019/g, "'")
            .replace(/\s*\(\s*\d+%\s*\)\s*$/g, '')
            .replace(/\s+/g, ' ')
            .trim()
            .toLowerCase();
    }

    function normalizeNpcDropRarity(rarity) {
        return String(rarity || 'common')
            .toLowerCase()
            .replace(/\s+/g, '-');
    }

    function getNpcDropRarityStyle(rarity) {
        rarity = normalizeNpcDropRarity(rarity);

        if (rarity === 'uncommon') {
            return { color: '#20ff45', shadow: '0 0 6px rgba(32, 255, 69, 0.55)' };
        }

        if (rarity === 'rare') {
            return { color: '#1e88ff', shadow: '0 0 6px rgba(30, 136, 255, 0.65)' };
        }

        if (rarity === 'ultra-rare') {
            return { color: '#ff3cff', shadow: '0 0 8px rgba(255, 60, 255, 0.75)' };
        }

        if (rarity === 'legendary') {
            return { color: '#ff8000', shadow: '0 0 8px rgba(255, 128, 0, 0.85)' };
        }

        if (rarity === 'unique') {
            return { color: '#ffd700', shadow: '0 0 8px rgba(255, 215, 0, 0.85)' };
        }

        return { color: '#ffffff', shadow: '0 0 6px rgba(255, 255, 255, 0.45)' };
    }

    function getNpcDropItemFromLink(link) {
        var names = [];
        var href = link.getAttribute('href') || '';

        names.push(link.textContent || '');
        names.push(link.getAttribute('title') || '');
        names.push(link.getAttribute('data-class-item-name') || '');

        try {
            if (href.indexOf('title=') !== -1) {
                names.push(decodeURIComponent(href.split('title=')[1].split('&')[0]).replace(/_/g, ' '));
            } else {
                names.push(decodeURIComponent(href.substring(href.lastIndexOf('/') + 1)).replace(/_/g, ' '));
            }
        } catch (e) {}

        names = names.map(normalizeNpcDropName).filter(Boolean);

        return items.find(function (item) {
            var itemName = normalizeNpcDropName(item.name);

            return names.indexOf(itemName) !== -1;
        });
    }

function applyNpcDropRarityStyle(link, item) {
    var rarity = normalizeNpcDropRarity(item.rarity);
    var style = getNpcDropRarityStyle(rarity);
    var textSpan;

    [
        'class-item-rarity-common',
        'class-item-rarity-uncommon',
        'class-item-rarity-rare',
        'class-item-rarity-ultra-rare',
        'class-item-rarity-legendary',
        'class-item-rarity-unique'
    ].forEach(function (className) {
        link.classList.remove(className);
    });

    link.classList.add('class-item-tooltip-link');
    link.classList.add('class-item-rarity-' + rarity);
    link.setAttribute('data-class-item-name', encodeURIComponent(item.name));

    /*
     * Remove browser-native hover tooltip.
     * The CB-style tooltip handles item display instead.
     */
    link.removeAttribute('title');

    textSpan = link.querySelector('.npc-drop-rarity-text');

    if (!textSpan) {
        textSpan = document.createElement('span');
        textSpan.className = 'npc-drop-rarity-text';
        textSpan.textContent = link.textContent;
        link.textContent = '';
        link.appendChild(textSpan);
    }

    textSpan.textContent = item.name;

    link.style.setProperty('font-weight', '800', 'important');
    link.style.setProperty('text-decoration-color', style.color, 'important');

    textSpan.style.setProperty('color', style.color, 'important');
    textSpan.style.setProperty('-webkit-text-fill-color', style.color, 'important');
    textSpan.style.setProperty('font-weight', '800', 'important');
    textSpan.style.setProperty('text-shadow', style.shadow, 'important');
    textSpan.style.setProperty('text-decoration-color', style.color, 'important');
}

    links.forEach(function (link) {
        var item = getNpcDropItemFromLink(link);

        if (!item) return;

        applyNpcDropRarityStyle(link, item);

        if (link.getAttribute('data-npc-item-tooltip-bound') === '1') return;

        link.setAttribute('data-npc-item-tooltip-bound', '1');

        link.addEventListener('mouseenter', function (event) {
            applyNpcDropRarityStyle(link, item);

            if (typeof showClassItemTooltip === 'function') {
                showClassItemTooltip(item, event);
            }
        });

        link.addEventListener('mousemove', function (event) {
            applyNpcDropRarityStyle(link, item);

            if (typeof moveClassItemTooltip === 'function') {
                moveClassItemTooltip(event);
            }
        });

        link.addEventListener('mouseleave', function () {
            applyNpcDropRarityStyle(link, item);

            if (typeof hideClassItemTooltip === 'function') {
                hideClassItemTooltip();
            }
        });
    });
}

mw.hook('wikipage.content').add(initNpcPageItemTooltips);
document.addEventListener('DOMContentLoaded', initNpcPageItemTooltips);
setTimeout(initNpcPageItemTooltips, 500);
setTimeout(initNpcPageItemTooltips, 1500);
setTimeout(initNpcPageItemTooltips, 3000);
setTimeout(initNpcPageItemTooltips, 5000);


mw.hook('wikipage.content').add(initQuestRewardItemTooltips);
document.addEventListener('DOMContentLoaded', initQuestRewardItemTooltips);
setTimeout(initQuestRewardItemTooltips, 500);
setTimeout(initQuestRewardItemTooltips, 1500);
setTimeout(initQuestRewardItemTooltips, 3000);

mw.hook('wikipage.content').add(initQuestRewardItemTooltips);
document.addEventListener('DOMContentLoaded', initQuestRewardItemTooltips);
setTimeout(initQuestRewardItemTooltips, 500);
setTimeout(initQuestRewardItemTooltips, 1500);

mw.hook('wikipage.content').add(initEqoaCategoryPageLists);
document.addEventListener('DOMContentLoaded', initEqoaCategoryPageLists);
setTimeout(initEqoaCategoryPageLists, 500);

mw.hook('wikipage.content').add(initZoneNpcListFilters);
document.addEventListener('DOMContentLoaded', initZoneNpcListFilters);
setTimeout(initZoneNpcListFilters, 500);

mw.hook('wikipage.content').add(initEqoaSpellPathLists);
document.addEventListener('DOMContentLoaded', initEqoaSpellPathLists);
setTimeout(initEqoaSpellPathLists, 500);
setTimeout(initEqoaSpellPathLists, 1500);
setTimeout(initEqoaSpellPathLists, 3000);

function hidePageActionButtonsForNonContributors() {
    var username = mw.config.get('wgUserName') || '';
    var groups = mw.config.get('wgUserGroups') || [];

    var canSeePageActions =
        username === 'DevRenodor' ||
        groups.indexOf('contributor') !== -1 ||
        groups.indexOf('administrator') !== -1 ||
        groups.indexOf('bureaucrat') !== -1 ||
        groups.indexOf('interface-admin') !== -1;

    var actionIds = [
        'ca-view',
        'ca-edit',
        'ca-ve-edit',
        'ca-viewsource',
        'ca-history'
    ];

    actionIds.forEach(function (id) {
        var action = document.getElementById(id);

        if (!action) return;

        action.style.setProperty('display', canSeePageActions ? '' : 'none', 'important');
    });
}

document.addEventListener('DOMContentLoaded', hidePageActionButtonsForNonContributors);
mw.hook('wikipage.content').add(hidePageActionButtonsForNonContributors);
setTimeout(hidePageActionButtonsForNonContributors, 250);
setTimeout(hidePageActionButtonsForNonContributors, 1000);


function initSandstormChangesHeaderCollapse() {
    var page = document.querySelector('.eqoa-changes-page');

    if (!page) return;

    Array.prototype.forEach.call(
        page.querySelectorAll('.eqoa-changes-section.mw-collapsible'),
        function (section) {
            var header = section.querySelector('.eqoa-header');
            var defaultToggle = section.querySelector('.mw-collapsible-toggle');
            var plus;

            if (!header) return;
            if (section.classList.contains('eqoa-change-header-collapse-ready')) return;

            plus = document.createElement('span');
            plus.className = 'eqoa-change-header-plus';
            plus.textContent = section.classList.contains('mw-collapsed') ? '+' : '-';
            header.appendChild(plus);

            function updatePlus() {
                plus.textContent = section.classList.contains('mw-collapsed') ? '+' : '-';
            }

            header.addEventListener('click', function (event) {
                if (event.target.closest('a')) return;

                defaultToggle = section.querySelector('.mw-collapsible-toggle');

                if (defaultToggle) {
                    defaultToggle.click();
                    setTimeout(updatePlus, 50);
                    return;
                }

                section.classList.toggle('mw-collapsed');
                updatePlus();
            });

            new MutationObserver(updatePlus).observe(section, {
                attributes: true,
                attributeFilter: ['class']
            });

            updatePlus();
            section.classList.add('eqoa-change-header-collapse-ready');
        }
    );
}

mw.hook('wikipage.content').add(initSandstormChangesHeaderCollapse);
document.addEventListener('DOMContentLoaded', initSandstormChangesHeaderCollapse);
setTimeout(initSandstormChangesHeaderCollapse, 500);
setTimeout(initSandstormChangesHeaderCollapse, 1500);


function initEqoaExpCampToggleIcons(root) {
    root = root && root.jquery ? root[0] : (root || document);

    root.querySelectorAll('table.exp-camp-table.mw-collapsible caption').forEach(function (caption) {
        var table = caption.closest('table.exp-camp-table');
        var toggle = caption.querySelector('.mw-collapsible-toggle');

        if (!table || !toggle) return;

        function removeDefaultBrackets() {
            [caption, toggle].forEach(function (scope) {
                if (!scope) return;

                scope.querySelectorAll('.mw-collapsible-bracket').forEach(function (bracket) {
                    bracket.remove();
                });

                Array.from(scope.childNodes).forEach(function (node) {
                    if (node.nodeType === 3) {
                        node.nodeValue = node.nodeValue.replace(/\[|\]/g, '');
                    }

                    if (
                        node.nodeType === 1 &&
                        (
                            node.classList.contains('mw-collapsible-bracket') ||
                            node.textContent.trim() === '[' ||
                            node.textContent.trim() === ']'
                        )
                    ) {
                        node.remove();
                    }
                });
            });
        }

        function updateToggleIcon() {
            var isCollapsed =
                table.classList.contains('mw-collapsed') ||
                toggle.classList.contains('mw-collapsible-toggle-collapsed') ||
                toggle.getAttribute('aria-expanded') === 'false';

            removeDefaultBrackets();

            toggle.textContent = isCollapsed ? '+' : '−';
            toggle.title = isCollapsed ? 'Expand' : 'Collapse';
            toggle.setAttribute('aria-label', isCollapsed ? 'Expand' : 'Collapse');

            removeDefaultBrackets();
        }

        if (!caption.classList.contains('eqoa-exp-caption-ready')) {
            caption.classList.add('eqoa-exp-caption-ready');

            caption.addEventListener('click', function (event) {
                if (event.target.closest('.mw-collapsible-toggle')) {
                    setTimeout(updateToggleIcon, 0);
                    setTimeout(updateToggleIcon, 100);
                    return;
                }

                event.preventDefault();
                toggle.click();

                setTimeout(updateToggleIcon, 0);
                setTimeout(updateToggleIcon, 100);
            });
        }

        updateToggleIcon();
    });
}

mw.hook('wikipage.content').add(initEqoaExpCampToggleIcons);

document.addEventListener('DOMContentLoaded', function () {
    initEqoaExpCampToggleIcons(document);
});

setTimeout(function () {
    initEqoaExpCampToggleIcons(document);
}, 500);

setTimeout(function () {
    initEqoaExpCampToggleIcons(document);
}, 1500);


function initEqoaTemplateCopyButtons(root) {
    root = root && root.jquery ? root[0] : (root || document);

    root.querySelectorAll('.eqoa-template-copy-button').forEach(function (button) {
        if (button.classList.contains('eqoa-template-copy-ready')) return;

        button.classList.add('eqoa-template-copy-ready');

        button.addEventListener('click', function () {
            var card = button.closest('.eqoa-template-card');
            var code = card ? card.querySelector('.eqoa-template-code') : null;
            var text = code ? code.textContent.trim() : '';

            if (!text) return;

            navigator.clipboard.writeText(text).then(function () {
                button.textContent = 'Copied';
                setTimeout(function () {
                    button.textContent = 'Copy';
                }, 1200);
            });
        });
    });
}

mw.hook('wikipage.content').add(initEqoaTemplateCopyButtons);

document.addEventListener('DOMContentLoaded', function () {
    initEqoaTemplateCopyButtons(document);
});

















function initEqoaZoneTabsLocked(root) {
    root = root && root.jquery ? root[0] : (root || document);

    root.querySelectorAll('.eqoa-zone-tabs').forEach(function (tabs) {
        if (tabs.classList.contains('eqoa-zone-tabs-locked-ready')) return;
        tabs.classList.add('eqoa-zone-tabs-locked-ready');

        tabs.querySelectorAll('.eqoa-zone-tab-button[data-tab]').forEach(function (button) {
            button.addEventListener('click', function () {
                var tabName = button.getAttribute('data-tab');

                tabs.querySelectorAll('.eqoa-zone-tab-button[data-tab]').forEach(function (btn) {
                    btn.classList.toggle('is-active', btn === button);
                });

                tabs.querySelectorAll('.eqoa-zone-tab-panel[data-tab-panel]').forEach(function (panel) {
                    panel.classList.toggle(
                        'is-active',
                        panel.getAttribute('data-tab-panel') === tabName
                    );
                });
            });
        });
    });
}

mw.hook('wikipage.content').add(initEqoaZoneTabsLocked);

document.addEventListener('DOMContentLoaded', function () {
    initEqoaZoneTabsLocked(document);
});

setTimeout(function () {
    initEqoaZoneTabsLocked(document);
}, 500);




/* ===== Zone NPC Tab Auto-Fill From Category ===== */
(function () {
    function normalizeEqoaText(value) {
        return String(value || '')
            .replace(/\s+/g, ' ')
            .trim()
            .toLowerCase();
    }

    function getCurrentZoneName() {
        return mw.config.get('wgTitle') || mw.config.get('wgPageName') || '';
    }

    function getNpcFamilyFromDoc(doc) {
        var rows = doc.querySelectorAll('.eqoa-npc-info-table tr');

        for (var i = 0; i < rows.length; i++) {
            var cells = rows[i].querySelectorAll('td');
            var label;

            if (cells.length < 2) continue;

            label = normalizeEqoaText(cells[0].textContent);

            if (label.indexOf('family') !== -1) {
                return cells[1].innerHTML.trim();
            }
        }

        return '';
    }

function getNpcZoneRowsFromDoc(doc, zoneName) {
    var wantedZone = normalizeEqoaText(zoneName);
    var rows = doc.querySelectorAll('.eqoa-npc-zone-table tr');
    var matches = [];

    for (var i = 0; i < rows.length; i++) {
        var cells = rows[i].querySelectorAll('td');

        if (cells.length < 7) continue;

        if (normalizeEqoaText(cells[0].textContent) === wantedZone) {
            matches.push({
                level: cells[1].innerHTML.trim(),
                drops: cells[3].innerHTML.trim(),
                quest: cells[5].innerHTML.trim(),
                notes: cells[6].innerHTML.trim()
            });
        }
    }

    return matches;
}

    function buildNpcLink(pageTitle) {
        var link = document.createElement('a');

        link.href = mw.util.getUrl(pageTitle);
        link.textContent = pageTitle;

        return link;
    }

function loadZoneNpcPage(api, pageTitle, zoneName) {
    return api.get({
        action: 'parse',
        page: pageTitle,
        prop: 'text',
        format: 'json'
    }).then(function (data) {
        var html = data && data.parse && data.parse.text && data.parse.text['*'];
        var doc;
        var zoneRows;
        var family;

        if (!html) return [];

        doc = new DOMParser().parseFromString(html, 'text/html');
        zoneRows = getNpcZoneRowsFromDoc(doc, zoneName);
        family = getNpcFamilyFromDoc(doc);

        if (!zoneRows.length) {
            return [{
                name: pageTitle,
                level: '',
                drops: '',
                family: family || '',
                notes: ''
            }];
        }

        return zoneRows.map(function (zoneRow) {
            return {
                name: pageTitle,
                level: zoneRow.level || '',
                drops: zoneRow.drops || '',
                family: family || '',
                notes: zoneRow.notes || ''
            };
        });
    });
}

    function renderZoneNpcRows(table, npcs) {
        var tbody = table.querySelector('tbody');

        if (!tbody) {
            tbody = document.createElement('tbody');
            table.appendChild(tbody);
        }

        tbody.innerHTML = '';

        if (!npcs.length) {
            tbody.innerHTML =
                '<tr class="zone-npc-row">' +
                    '<td colspan="5">No NPCs found.</td>' +
                '</tr>';
            return;
        }

        npcs.forEach(function (npc) {
            var row = document.createElement('tr');
            var nameCell = document.createElement('td');

            row.className = 'zone-npc-row';

            nameCell.className = 'zone-monster-name';
            nameCell.appendChild(buildNpcLink(npc.name));

            row.appendChild(nameCell);

            row.innerHTML +=
                '<td class="zone-monster-level">' + (npc.level || '') + '</td>' +
                '<td class="zone-monster-drops">' + (npc.drops || '') + '</td>' +
                '<td class="zone-monster-family">' + (npc.family || '') + '</td>' +
                '<td class="zone-monster-notes">' + (npc.notes || '') + '</td>';

            tbody.appendChild(row);
        });
    }

    function initZoneNpcCategoryAutoFill(root) {
        var zoneName = getCurrentZoneName();
        var categoryName = zoneName + ' NPCs';
        var panel;
        var table;
        var api;

        root = root && root.jquery ? root[0] : (root || document);

        panel = root.querySelector('[data-tab-panel="npcs"]');
        if (!panel || panel.classList.contains('eqoa-zone-npc-auto-ready')) return;

        table = panel.querySelector('.zone-monster-table');
        if (!table) return;

        panel.classList.add('eqoa-zone-npc-auto-ready');

        api = new mw.Api();

        api.get({
            action: 'query',
            list: 'categorymembers',
            cmtitle: 'Category:' + categoryName,
            cmnamespace: 0,
            cmlimit: 500,
            format: 'json'
        }).then(function (data) {
            var members = data &&
                data.query &&
                data.query.categorymembers
                ? data.query.categorymembers
                : [];

            return Promise.all(
                members.map(function (member) {
                    return loadZoneNpcPage(api, member.title, zoneName);
                })
            );
}).then(function (rows) {
    rows = rows.reduce(function (allRows, npcRows) {
        return allRows.concat(npcRows || []);
    }, []);

    rows = rows.filter(Boolean).sort(function (a, b) {
        var nameCompare = a.name.localeCompare(b.name);

        if (nameCompare !== 0) return nameCompare;

        return String(a.level || '').localeCompare(String(b.level || ''));
    });

            renderZoneNpcRows(table, rows);

            if (typeof initGeneratedItemTooltipsForWiki === 'function') {
                initGeneratedItemTooltipsForWiki(panel);
            }

            if (typeof initZoneNpcSearch === 'function') {
                initZoneNpcSearch(panel);
            }

            if (window.jQuery && jQuery.fn && jQuery.fn.tablesorter) {
                jQuery(table).trigger('destroy');
                jQuery(table).tablesorter();
            }
        }).catch(function (error) {
            console.error('EQOA zone NPC auto-fill failed:', error);
        });
    }

    mw.hook('wikipage.content').add(initZoneNpcCategoryAutoFill);

    document.addEventListener('DOMContentLoaded', function () {
        initZoneNpcCategoryAutoFill(document);
    });

    setTimeout(function () {
        initZoneNpcCategoryAutoFill(document);
    }, 500);

    setTimeout(function () {
        initZoneNpcCategoryAutoFill(document);
    }, 1500);
})();