Anonymous
×
Create a new article
Write your page title here:
We currently have 922 articles on WIKI - Flat MMO. Type your article name above or click on one of the titles below and start writing!



WIKI - Flat MMO
922Articles

MediaWiki:Common.js: Difference between revisions

No edit summary
No edit summary
 
(36 intermediate revisions by 2 users not shown)
Line 1: Line 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. */
/* ============================================================
  THEME TOGGLE — Dark/Light mode switcher
  Defaults to dark. Saves preference to localStorage.
  ============================================================ */
(function() {
    const saved = localStorage.getItem('flatmmo-theme') || 'dark';
    document.documentElement.className += ' ' + saved + '-theme';
    const searchMode = localStorage.getItem('flatmmo-search') || 'top';
    document.documentElement.className += ' search-' + searchMode;
    document.addEventListener('DOMContentLoaded', function() {
        document.body.classList.add(saved + '-theme');
        document.body.classList.add('search-' + searchMode);
    });
})();
$(document).ready(function () {
$(document).ready(function () {
console.log("loaded")
console.log("loaded");
if (document.getElementById("interactiveMap")) {
 
console.log("map loaded")
// --- Theme toggle button ---
$.getScript(mw.util.getUrl("MediaWiki:InteractiveMap/Objects.js") + "?action=raw&ctype=text/javascript", function() {
(function() {
            $.getScript(mw.util.getUrl("MediaWiki:InteractiveMap/NPCs.js") + "?action=raw&ctype=text/javascript", function() {
    let currentTheme = localStorage.getItem('flatmmo-theme') || 'dark';
                $.getScript(mw.util.getUrl("MediaWiki:InteractiveMap/Maps.js") + "?action=raw&ctype=text/javascript", function() {
 
                    $.getScript(mw.util.getUrl("MediaWiki:InteractiveMap.js") + "?action=raw&ctype=text/javascript", function() {
    document.body.classList.remove('dark-theme', 'light-theme');
                    });
    document.body.classList.add(currentTheme + '-theme');
 
    const btn = document.createElement('button');
    btn.className = 'theme-toggle-btn';
    btn.title = 'Toggle light/dark mode';
    btn.textContent = currentTheme === 'dark' ? '☀️' : '🌙';
 
    btn.addEventListener('click', function() {
        const isDark = document.body.classList.contains('dark-theme');
        const newTheme = isDark ? 'light' : 'dark';
 
        document.body.classList.remove('dark-theme', 'light-theme');
        document.body.classList.add(newTheme + '-theme');
        document.documentElement.classList.remove('dark-theme', 'light-theme');
        document.documentElement.classList.add(newTheme + '-theme');
 
        localStorage.setItem('flatmmo-theme', newTheme);
        btn.textContent = newTheme === 'dark' ? '☀️' : '🌙';
    });
 
    const target = document.querySelector('.cosmos-header__wiki-buttons')
              || document.querySelector('.cosmos-actions')
              || document.querySelector('.wds-button-group')
              || document.querySelector('#content');
    if (target) {
        if (target.id === 'content') {
            target.insertBefore(btn, target.firstChild);
        } else {
            target.appendChild(btn);
        }
    }
})();
 
// --- Search bar toggle + compact header search bar ---
(function() {
    const searchMode = localStorage.getItem('flatmmo-search') || 'top';
 
    document.body.classList.remove('search-top', 'search-header');
    document.body.classList.add('search-' + searchMode);
 
    if (searchMode === 'header') {
        window.scrollTo(0, 0);
        document.addEventListener('DOMContentLoaded', function() {
            window.scrollTo(0, 0);
        });
    }
 
    const searchToggle = document.createElement('button');
    searchToggle.className = 'search-toggle-btn';
    searchToggle.title = 'Toggle search bar position';
    searchToggle.textContent = searchMode === 'top' ? '🔍' : '🔎';
 
    searchToggle.addEventListener('click', function() {
        const isTop = document.body.classList.contains('search-top');
        const newMode = isTop ? 'header' : 'top';
 
        document.body.classList.remove('search-top', 'search-header');
        document.body.classList.add('search-' + newMode);
        document.documentElement.classList.remove('search-top', 'search-header');
        document.documentElement.classList.add('search-' + newMode);
 
        localStorage.setItem('flatmmo-search', newMode);
        searchToggle.textContent = newMode === 'top' ? '🔍' : '🔎';
    });
 
    const searchWrap = document.createElement('div');
    searchWrap.className = 'header-search-wrap';
 
    const searchInput = document.createElement('input');
    searchInput.type = 'text';
    searchInput.className = 'header-search-input';
    searchInput.placeholder = 'Search wiki...';
    searchInput.setAttribute('autocomplete', 'off');
 
    const searchBtn = document.createElement('button');
    searchBtn.className = 'header-search-btn';
    searchBtn.innerHTML = '🔍';
    searchBtn.title = 'Search';
 
    function doSearch() {
        const q = searchInput.value.trim();
        if (q) {
            window.location.href = mw.util.getUrl('Special:Search') + '?search=' + encodeURIComponent(q);
        }
    }
 
    searchBtn.addEventListener('click', doSearch);
    searchInput.addEventListener('keydown', function(e) {
        if (e.key === 'Enter') doSearch();
    });
 
    searchWrap.appendChild(searchInput);
    searchWrap.appendChild(searchBtn);
 
    const target = document.querySelector('.cosmos-header__wiki-buttons')
              || document.querySelector('.cosmos-actions')
              || document.querySelector('.wds-button-group')
              || document.querySelector('#content');
    if (target && target.id !== 'content') {
        target.appendChild(searchToggle);
        target.style.position = 'relative';
        target.appendChild(searchWrap);
 
        const bannerUser = document.querySelector('.cosmos-userButton-label')
                      || document.querySelector('#p-personal-label');
        const userName = bannerUser ? bannerUser.textContent.trim() : '';
        if (userName) {
            const userLink = document.createElement('a');
            userLink.className = 'relocated-user';
            userLink.href = mw.util.getUrl('User:' + userName);
            userLink.title = userName;
 
            const userIcon = document.createElement('span');
            userIcon.className = 'relocated-user-icon';
            userIcon.innerHTML = '👤';
 
            const userText = document.createElement('span');
            userText.className = 'relocated-user-name';
            userText.textContent = userName;
 
            userLink.appendChild(userIcon);
            userLink.appendChild(userText);
            target.appendChild(userLink);
        }
    }
})();
 
// --- Script loaders ---
$.getScript(mw.util.getUrl("MediaWiki:ProfileTags.js") + "?action=raw&ctype=text/javascript");
 
if (document.getElementById("interactiveMap")) {
    console.log("map loaded");
    $.getScript(mw.util.getUrl("MediaWiki:InteractiveMap/Objects.js") + "?action=raw&ctype=text/javascript", function() {
        $.getScript(mw.util.getUrl("MediaWiki:InteractiveMap/NPCs.js") + "?action=raw&ctype=text/javascript", function() {
            $.getScript(mw.util.getUrl("MediaWiki:InteractiveMap/Maps.js") + "?action=raw&ctype=text/javascript", function() {
                $.getScript(mw.util.getUrl("MediaWiki:InteractiveMap.js") + "?action=raw&ctype=text/javascript", function() {
                 });
                 });
             });
             });
         });
         });
}
    });
}


if (document.getElementById("wardrobe")) {
if (document.getElementById("wardrobe")) {
$.getScript(mw.util.getUrl("MediaWiki:Wardrobe.js") + "?action=raw&ctype=text/javascript");
    $.getScript(mw.util.getUrl("MediaWiki:Wardrobe.js") + "?action=raw&ctype=text/javascript");
console.log("wardrobe loaded")
    console.log("wardrobe loaded");
}
}


    if (document.getElementById("mapEditor")) {
if (document.getElementById("mapEditor")) {
        $.getScript("https://cdn.jsdelivr.net/npm/interactjs/dist/interact.min.js", function() {
    $.getScript("https://cdn.jsdelivr.net/npm/interactjs/dist/interact.min.js", function() {
            $.getScript(mw.util.getUrl("MediaWiki:InteractiveMap/Objects.js") + "?action=raw&ctype=text/javascript", function() {
        $.getScript(mw.util.getUrl("MediaWiki:InteractiveMap/Objects.js") + "?action=raw&ctype=text/javascript", function() {
                $.getScript(mw.util.getUrl("MediaWiki:InteractiveMap/NPCs.js") + "?action=raw&ctype=text/javascript", function() {
            $.getScript(mw.util.getUrl("MediaWiki:InteractiveMap/NPCs.js") + "?action=raw&ctype=text/javascript", function() {
                    $.getScript(mw.util.getUrl("MediaWiki:InteractiveMap/Maps.js") + "?action=raw&ctype=text/javascript", function() {
                $.getScript(mw.util.getUrl("MediaWiki:InteractiveMap/Maps.js") + "?action=raw&ctype=text/javascript", function() {
                        $.getScript(mw.util.getUrl("MediaWiki:MapEditor.js") + "?action=raw&ctype=text/javascript", function() {
                    $.getScript(mw.util.getUrl("MediaWiki:MapEditor.js") + "?action=raw&ctype=text/javascript", function() {
                        });
                     });
                     });
                 });
                 });
             });
             });
         });
         });
     }
     });
}
 
if (document.querySelector(".code")) {
if (document.querySelector(".code")) {
$.getScript("../custom/highlight.min.js", ()=>{
    $.getScript("../custom/highlight.min.js", function() {
document.querySelectorAll(".code").forEach(el=>hljs.highlightElement(el))
        document.querySelectorAll(".code").forEach(function(el) { hljs.highlightElement(el); });
console.log('Code highlighted');
        console.log('Code highlighted');
});
    });
}
 
if (document.querySelector('[class*="Tracker"]') && !document.querySelector('[class*="page-MediaWiki"]')) {
    $.getScript(mw.util.getUrl("MediaWiki:Trackers.js") + "?action=raw&ctype=text/javascript");
    console.log("Tracker loaded");
}
}


if (document.getElementById("enemiesDB")) {
if (document.getElementById("enemiesDB")) {
$.getScript(mw.util.getUrl("MediaWiki:Update_DB.js") + "?action=raw&ctype=text/javascript");
    $.getScript(mw.util.getUrl("MediaWiki:Update_DB.js") + "?action=raw&ctype=text/javascript");
console.log("DB Updater Loaded")
    console.log("DB Updater Loaded");
}
 
if (document.getElementById("xpCalculator")) {
    $.getScript(mw.util.getUrl("MediaWiki:XpCalculator.js") + "?action=raw&ctype=text/javascript");
    console.log("XP Calculator Loaded");
}
 
// --- Hit chance calculator ---
function hitChance(accuracy, defence) {
    accuracy = parseInt(accuracy);
    defence = parseInt(defence);
   
    if (accuracy >= defence) {
        return 100;
    }
   
    const diff = defence - accuracy;
    const hc = Math.max(100 - (diff * 10), 5);
   
    return hc.toFixed(2);
}
 
if (document.querySelector('.hitChance')) {
    document.querySelectorAll(".hitChance").forEach(function(el) {
        const parts = el.innerText.split(",");
        let acc = parts[0];
        let def = parts[1];
        el.innerText = "";
        const accSpan = document.createElement("span");
        const defSpan = document.createElement("span");
        const hitSpan = document.createElement("span");
        const accInput = document.createElement("input");
        const defInput = document.createElement("input");
        const hitValueSpan = document.createElement("span");
 
        accSpan.innerText = "Accuracy:";
        defSpan.innerText = "Defence:";
        hitSpan.innerText = "Hit Chance:";
 
        accInput.type = "number";
        defInput.type = "number";
        accInput.min = 0;
        defInput.min = 0;
        if (acc !== "0") {
            accInput.disabled = true;
        }
        if (def !== "0") {
            defInput.disabled = true;
        }
        def = def < 0 ? 0 : def;
        acc = acc < 0 ? 0 : acc;
        accInput.defaultValue = acc;
        defInput.defaultValue = def;
        accInput.onchange = function() {
            hitValueSpan.innerText = hitChance(accInput.value, defInput.value) + "%";
        };
        defInput.onchange = function() {
            hitValueSpan.innerText = hitChance(accInput.value, defInput.value) + "%";
        };
        el.append(accSpan, accInput, defSpan, defInput, hitSpan, hitValueSpan);
        hitValueSpan.innerText = hitChance(accInput.value, defInput.value) + "%";
    });
}
}
});
 
// --- Page Preview Popup ---
(function() {
    let popup = null;
    let hideTimer = null;
    let showTimer = null;
    const previewCache = {};
    const isMobile = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0);
 
    function createPopup() {
        popup = document.createElement('div');
        popup.className = 'flatmmo-preview';
        document.body.appendChild(popup);
 
        popup.addEventListener('mouseenter', function() {
            clearTimeout(hideTimer);
        });
        popup.addEventListener('mouseleave', function() {
            hideTimer = setTimeout(function() {
                popup.style.display = 'none';
            }, 200);
        });
    }
 
    function positionPopup(e) {
        // On mobile, let CSS handle positioning (fixed bottom sheet)
        if (isMobile) {
            popup.style.left = '';
            popup.style.top = '';
            return;
        }
        let x = e.pageX + 15;
        let y = e.pageY + 15;
        const popupWidth = 340;
        const popupHeight = 200;
 
        if (x + popupWidth > document.documentElement.scrollLeft + window.innerWidth) {
            x = e.pageX - popupWidth - 15;
        }
        if (y + popupHeight > document.documentElement.scrollTop + window.innerHeight) {
            y = e.pageY - popupHeight - 15;
        }
 
        popup.style.left = x + 'px';
        popup.style.top = y + 'px';
    }
 
    function fetchPreview(title, callback) {
        if (previewCache[title]) {
            callback(previewCache[title]);
            return;
        }
 
        const apiUrl = mw.util.wikiScript('api') + '?action=parse&page=' + encodeURIComponent(title) + '&prop=text&format=json&redirects=1';
 
        $.ajax({
            url: apiUrl,
            dataType: 'json',
            success: function(data) {
                if (data.parse && data.parse.text) {
                    const html = data.parse.text['*'];
                    const temp = document.createElement('div');
                    temp.innerHTML = html;
 
                    let img = null;
                    const infoboxImg = temp.querySelector('.flatmmo-infobox-image img, .flatmmo-infobox img');
                    if (infoboxImg) {
                        img = infoboxImg.src;
                    } else {
                        const firstImg = temp.querySelector('img');
                        if (firstImg && firstImg.width > 20) {
                            img = firstImg.src;
                        }
                    }
 
                    let text = '';
                    const paragraphs = temp.querySelectorAll('p');
                    for (let i = 0; i < paragraphs.length; i++) {
                        const t = paragraphs[i].textContent.trim();
                        if (t.length > 10) {
                            text = t;
                            break;
                        }
                    }
 
                    if (text.length > 250) {
                        text = text.substring(0, 247) + '...';
                    }
 
                    const result = { image: img, text: text, title: title };
                    previewCache[title] = result;
                    callback(result);
                }
            },
            error: function() {
                console.log('Preview request failed for:', title);
            }
        });
    }
 
    function buildPopupHTML(data) {
        let html = '';
        html += '<div class="flatmmo-preview-title">' + data.title + '</div>';
        if (data.image) {
            html += '<div class="flatmmo-preview-image"><img src="' + data.image + '"></div>';
        }
        if (data.text) {
            html += '<div class="flatmmo-preview-text">' + data.text + '</div>';
        }
        if (!data.image && !data.text) {
            html += '<div class="flatmmo-preview-text" style="color:#999;">No preview available.</div>';
        }
        return html;
    }
 
    function showPreview(link, e) {
        if (!popup) createPopup();
 
        const href = link.getAttribute('href');
        if (!href) return;
 
        const match = href.match(/\/index\.php\/(.+)/);
        if (!match) return;
 
        const title = decodeURIComponent(match[1]).replace(/_/g, ' ');
 
        if (title.match(/^Special:|^Talk:|^User:|^Category:|^File:|^Template:|^MediaWiki:/i)) return;
        if (href.indexOf('action=edit') !== -1) return;
        if (link.classList.contains('new')) return;
 
        positionPopup(e);
        popup.innerHTML = '<div class="flatmmo-preview-title">' + title + '</div><div class="flatmmo-preview-loading">Loading...</div>';
        popup.style.display = 'block';
 
        fetchPreview(title, function(data) {
            popup.innerHTML = buildPopupHTML(data);
        });
    }
 
    const content = document.getElementById('mw-content-text') || document.getElementById('bodyContent') || document.body;
 
    // Strip all native tooltips from content links
    $(content).find('[title]').removeAttr('title');
 
    if (isMobile) {
        // Mobile: first tap shows preview, second tap follows link
        $(content).on('click', 'a:not(.new)', function(e) {
            const link = this;
            if ($(link).closest('.flatmmo-preview').length) return;
 
            const href = link.getAttribute('href');
            if (!href || !href.match(/\/index\.php\//)) return;
            if (href.indexOf('action=edit') !== -1) return;
 
            if (link.getAttribute('data-preview-shown') === 'true') {
                link.removeAttribute('data-preview-shown');
                return;
            }
 
            e.preventDefault();
            link.setAttribute('data-preview-shown', 'true');
            showPreview(link, e);
 
            setTimeout(function() {
                link.removeAttribute('data-preview-shown');
            }, 3000);
        });
 
        // Tap outside closes popup
        $(document).on('click', function(e) {
            if (popup && !$(e.target).closest('.flatmmo-preview').length && !$(e.target).closest('#mw-content-text a').length) {
                popup.style.display = 'none';
            }
        });
    } else {
        // Desktop: hover behavior
        $(content).on('mouseover', 'a', function(e) {
            const link = this;
            if ($(link).closest('.flatmmo-preview').length) return;
 
            clearTimeout(hideTimer);
            clearTimeout(showTimer);
 
            if (link.getAttribute('title')) {
                link.removeAttribute('title');
            }
 
            showTimer = setTimeout(function() {
                showPreview(link, e);
            }, 300);
        });
 
        $(content).on('mouseout', 'a', function() {
            clearTimeout(showTimer);
            hideTimer = setTimeout(function() {
                if (popup) popup.style.display = 'none';
            }, 200);
        });
    }
 
    console.log('Page preview popup initialized');
})();
 
// --- Mobile table scroll fix ---
(function() {
    if (window.innerWidth > 768) return;
    const tables = document.querySelectorAll('#mw-content-text table');
    for (let i = 0; i < tables.length; i++) {
        const table = tables[i];
        if (table.parentElement.classList.contains('table-scroll-wrap')) continue;
        table.style.overflow = 'visible';
        const wrapper = document.createElement('div');
        wrapper.className = 'table-scroll-wrap';
        table.parentNode.insertBefore(wrapper, table);
        wrapper.appendChild(table);
    }
})();
 
}); // end $(document).ready

Latest revision as of 17:01, 21 June 2026

  1 /* Any JavaScript here will be loaded for all users on every page load. */
  2 
  3 /* ============================================================
  4    THEME TOGGLE — Dark/Light mode switcher
  5    Defaults to dark. Saves preference to localStorage.
  6    ============================================================ */
  7 (function() {
  8     const saved = localStorage.getItem('flatmmo-theme') || 'dark';
  9     document.documentElement.className += ' ' + saved + '-theme';
 10 
 11     const searchMode = localStorage.getItem('flatmmo-search') || 'top';
 12     document.documentElement.className += ' search-' + searchMode;
 13 
 14     document.addEventListener('DOMContentLoaded', function() {
 15         document.body.classList.add(saved + '-theme');
 16         document.body.classList.add('search-' + searchMode);
 17     });
 18 })();
 19 
 20 $(document).ready(function () {
 21 console.log("loaded");
 22 
 23 // --- Theme toggle button ---
 24 (function() {
 25     let currentTheme = localStorage.getItem('flatmmo-theme') || 'dark';
 26 
 27     document.body.classList.remove('dark-theme', 'light-theme');
 28     document.body.classList.add(currentTheme + '-theme');
 29 
 30     const btn = document.createElement('button');
 31     btn.className = 'theme-toggle-btn';
 32     btn.title = 'Toggle light/dark mode';
 33     btn.textContent = currentTheme === 'dark' ? '☀️' : '🌙';
 34 
 35     btn.addEventListener('click', function() {
 36         const isDark = document.body.classList.contains('dark-theme');
 37         const newTheme = isDark ? 'light' : 'dark';
 38 
 39         document.body.classList.remove('dark-theme', 'light-theme');
 40         document.body.classList.add(newTheme + '-theme');
 41         document.documentElement.classList.remove('dark-theme', 'light-theme');
 42         document.documentElement.classList.add(newTheme + '-theme');
 43 
 44         localStorage.setItem('flatmmo-theme', newTheme);
 45         btn.textContent = newTheme === 'dark' ? '☀️' : '🌙';
 46     });
 47 
 48     const target = document.querySelector('.cosmos-header__wiki-buttons')
 49               || document.querySelector('.cosmos-actions')
 50               || document.querySelector('.wds-button-group')
 51               || document.querySelector('#content');
 52     if (target) {
 53         if (target.id === 'content') {
 54             target.insertBefore(btn, target.firstChild);
 55         } else {
 56             target.appendChild(btn);
 57         }
 58     }
 59 })();
 60 
 61 // --- Search bar toggle + compact header search bar ---
 62 (function() {
 63     const searchMode = localStorage.getItem('flatmmo-search') || 'top';
 64 
 65     document.body.classList.remove('search-top', 'search-header');
 66     document.body.classList.add('search-' + searchMode);
 67 
 68     if (searchMode === 'header') {
 69         window.scrollTo(0, 0);
 70         document.addEventListener('DOMContentLoaded', function() {
 71             window.scrollTo(0, 0);
 72         });
 73     }
 74 
 75     const searchToggle = document.createElement('button');
 76     searchToggle.className = 'search-toggle-btn';
 77     searchToggle.title = 'Toggle search bar position';
 78     searchToggle.textContent = searchMode === 'top' ? '🔍' : '🔎';
 79 
 80     searchToggle.addEventListener('click', function() {
 81         const isTop = document.body.classList.contains('search-top');
 82         const newMode = isTop ? 'header' : 'top';
 83 
 84         document.body.classList.remove('search-top', 'search-header');
 85         document.body.classList.add('search-' + newMode);
 86         document.documentElement.classList.remove('search-top', 'search-header');
 87         document.documentElement.classList.add('search-' + newMode);
 88 
 89         localStorage.setItem('flatmmo-search', newMode);
 90         searchToggle.textContent = newMode === 'top' ? '🔍' : '🔎';
 91     });
 92 
 93     const searchWrap = document.createElement('div');
 94     searchWrap.className = 'header-search-wrap';
 95 
 96     const searchInput = document.createElement('input');
 97     searchInput.type = 'text';
 98     searchInput.className = 'header-search-input';
 99     searchInput.placeholder = 'Search wiki...';
100     searchInput.setAttribute('autocomplete', 'off');
101 
102     const searchBtn = document.createElement('button');
103     searchBtn.className = 'header-search-btn';
104     searchBtn.innerHTML = '&#x1F50D;';
105     searchBtn.title = 'Search';
106 
107     function doSearch() {
108         const q = searchInput.value.trim();
109         if (q) {
110             window.location.href = mw.util.getUrl('Special:Search') + '?search=' + encodeURIComponent(q);
111         }
112     }
113 
114     searchBtn.addEventListener('click', doSearch);
115     searchInput.addEventListener('keydown', function(e) {
116         if (e.key === 'Enter') doSearch();
117     });
118 
119     searchWrap.appendChild(searchInput);
120     searchWrap.appendChild(searchBtn);
121 
122     const target = document.querySelector('.cosmos-header__wiki-buttons')
123               || document.querySelector('.cosmos-actions')
124               || document.querySelector('.wds-button-group')
125               || document.querySelector('#content');
126     if (target && target.id !== 'content') {
127         target.appendChild(searchToggle);
128         target.style.position = 'relative';
129         target.appendChild(searchWrap);
130 
131         const bannerUser = document.querySelector('.cosmos-userButton-label')
132                       || document.querySelector('#p-personal-label');
133         const userName = bannerUser ? bannerUser.textContent.trim() : '';
134         if (userName) {
135             const userLink = document.createElement('a');
136             userLink.className = 'relocated-user';
137             userLink.href = mw.util.getUrl('User:' + userName);
138             userLink.title = userName;
139 
140             const userIcon = document.createElement('span');
141             userIcon.className = 'relocated-user-icon';
142             userIcon.innerHTML = '👤';
143 
144             const userText = document.createElement('span');
145             userText.className = 'relocated-user-name';
146             userText.textContent = userName;
147 
148             userLink.appendChild(userIcon);
149             userLink.appendChild(userText);
150             target.appendChild(userLink);
151         }
152     }
153 })();
154 
155 // --- Script loaders ---
156 $.getScript(mw.util.getUrl("MediaWiki:ProfileTags.js") + "?action=raw&ctype=text/javascript");
157 
158 if (document.getElementById("interactiveMap")) {
159     console.log("map loaded");
160     $.getScript(mw.util.getUrl("MediaWiki:InteractiveMap/Objects.js") + "?action=raw&ctype=text/javascript", function() {
161         $.getScript(mw.util.getUrl("MediaWiki:InteractiveMap/NPCs.js") + "?action=raw&ctype=text/javascript", function() {
162             $.getScript(mw.util.getUrl("MediaWiki:InteractiveMap/Maps.js") + "?action=raw&ctype=text/javascript", function() {
163                 $.getScript(mw.util.getUrl("MediaWiki:InteractiveMap.js") + "?action=raw&ctype=text/javascript", function() {
164                 });
165             });
166         });
167     });
168 }
169 
170 if (document.getElementById("wardrobe")) {
171     $.getScript(mw.util.getUrl("MediaWiki:Wardrobe.js") + "?action=raw&ctype=text/javascript");
172     console.log("wardrobe loaded");
173 }
174 
175 if (document.getElementById("mapEditor")) {
176     $.getScript("https://cdn.jsdelivr.net/npm/interactjs/dist/interact.min.js", function() {
177         $.getScript(mw.util.getUrl("MediaWiki:InteractiveMap/Objects.js") + "?action=raw&ctype=text/javascript", function() {
178             $.getScript(mw.util.getUrl("MediaWiki:InteractiveMap/NPCs.js") + "?action=raw&ctype=text/javascript", function() {
179                 $.getScript(mw.util.getUrl("MediaWiki:InteractiveMap/Maps.js") + "?action=raw&ctype=text/javascript", function() {
180                     $.getScript(mw.util.getUrl("MediaWiki:MapEditor.js") + "?action=raw&ctype=text/javascript", function() {
181                     });
182                 });
183             });
184         });
185     });
186 }
187 
188 if (document.querySelector(".code")) {
189     $.getScript("../custom/highlight.min.js", function() {
190         document.querySelectorAll(".code").forEach(function(el) { hljs.highlightElement(el); });
191         console.log('Code highlighted');
192     });
193 }
194 
195 if (document.querySelector('[class*="Tracker"]') && !document.querySelector('[class*="page-MediaWiki"]')) {
196     $.getScript(mw.util.getUrl("MediaWiki:Trackers.js") + "?action=raw&ctype=text/javascript");
197     console.log("Tracker loaded");
198 }
199 
200 if (document.getElementById("enemiesDB")) {
201     $.getScript(mw.util.getUrl("MediaWiki:Update_DB.js") + "?action=raw&ctype=text/javascript");
202     console.log("DB Updater Loaded");
203 }
204 
205 if (document.getElementById("xpCalculator")) {
206     $.getScript(mw.util.getUrl("MediaWiki:XpCalculator.js") + "?action=raw&ctype=text/javascript");
207     console.log("XP Calculator Loaded");
208 }
209 
210 // --- Hit chance calculator ---
211 function hitChance(accuracy, defence) {
212     accuracy = parseInt(accuracy);
213     defence = parseInt(defence);
214     
215     if (accuracy >= defence) {
216         return 100;
217     }
218     
219     const diff = defence - accuracy;
220     const hc = Math.max(100 - (diff * 10), 5);
221     
222     return hc.toFixed(2);
223 }
224 
225 if (document.querySelector('.hitChance')) {
226     document.querySelectorAll(".hitChance").forEach(function(el) {
227         const parts = el.innerText.split(",");
228         let acc = parts[0];
229         let def = parts[1];
230         el.innerText = "";
231         const accSpan = document.createElement("span");
232         const defSpan = document.createElement("span");
233         const hitSpan = document.createElement("span");
234         const accInput = document.createElement("input");
235         const defInput = document.createElement("input");
236         const hitValueSpan = document.createElement("span");
237 
238         accSpan.innerText = "Accuracy:";
239         defSpan.innerText = "Defence:";
240         hitSpan.innerText = "Hit Chance:";
241 
242         accInput.type = "number";
243         defInput.type = "number";
244         accInput.min = 0;
245         defInput.min = 0;
246         if (acc !== "0") {
247             accInput.disabled = true;
248         }
249         if (def !== "0") {
250             defInput.disabled = true;
251         }
252         def = def < 0 ? 0 : def;
253         acc = acc < 0 ? 0 : acc;
254         accInput.defaultValue = acc;
255         defInput.defaultValue = def;
256         accInput.onchange = function() {
257             hitValueSpan.innerText = hitChance(accInput.value, defInput.value) + "%";
258         };
259         defInput.onchange = function() {
260             hitValueSpan.innerText = hitChance(accInput.value, defInput.value) + "%";
261         };
262         el.append(accSpan, accInput, defSpan, defInput, hitSpan, hitValueSpan);
263         hitValueSpan.innerText = hitChance(accInput.value, defInput.value) + "%";
264     });
265 }
266 
267 // --- Page Preview Popup ---
268 (function() {
269     let popup = null;
270     let hideTimer = null;
271     let showTimer = null;
272     const previewCache = {};
273     const isMobile = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0);
274 
275     function createPopup() {
276         popup = document.createElement('div');
277         popup.className = 'flatmmo-preview';
278         document.body.appendChild(popup);
279 
280         popup.addEventListener('mouseenter', function() {
281             clearTimeout(hideTimer);
282         });
283         popup.addEventListener('mouseleave', function() {
284             hideTimer = setTimeout(function() {
285                 popup.style.display = 'none';
286             }, 200);
287         });
288     }
289 
290     function positionPopup(e) {
291         // On mobile, let CSS handle positioning (fixed bottom sheet)
292         if (isMobile) {
293             popup.style.left = '';
294             popup.style.top = '';
295             return;
296         }
297         let x = e.pageX + 15;
298         let y = e.pageY + 15;
299         const popupWidth = 340;
300         const popupHeight = 200;
301 
302         if (x + popupWidth > document.documentElement.scrollLeft + window.innerWidth) {
303             x = e.pageX - popupWidth - 15;
304         }
305         if (y + popupHeight > document.documentElement.scrollTop + window.innerHeight) {
306             y = e.pageY - popupHeight - 15;
307         }
308 
309         popup.style.left = x + 'px';
310         popup.style.top = y + 'px';
311     }
312 
313     function fetchPreview(title, callback) {
314         if (previewCache[title]) {
315             callback(previewCache[title]);
316             return;
317         }
318 
319         const apiUrl = mw.util.wikiScript('api') + '?action=parse&page=' + encodeURIComponent(title) + '&prop=text&format=json&redirects=1';
320 
321         $.ajax({
322             url: apiUrl,
323             dataType: 'json',
324             success: function(data) {
325                 if (data.parse && data.parse.text) {
326                     const html = data.parse.text['*'];
327                     const temp = document.createElement('div');
328                     temp.innerHTML = html;
329 
330                     let img = null;
331                     const infoboxImg = temp.querySelector('.flatmmo-infobox-image img, .flatmmo-infobox img');
332                     if (infoboxImg) {
333                         img = infoboxImg.src;
334                     } else {
335                         const firstImg = temp.querySelector('img');
336                         if (firstImg && firstImg.width > 20) {
337                             img = firstImg.src;
338                         }
339                     }
340 
341                     let text = '';
342                     const paragraphs = temp.querySelectorAll('p');
343                     for (let i = 0; i < paragraphs.length; i++) {
344                         const t = paragraphs[i].textContent.trim();
345                         if (t.length > 10) {
346                             text = t;
347                             break;
348                         }
349                     }
350 
351                     if (text.length > 250) {
352                         text = text.substring(0, 247) + '...';
353                     }
354 
355                     const result = { image: img, text: text, title: title };
356                     previewCache[title] = result;
357                     callback(result);
358                 }
359             },
360             error: function() {
361                 console.log('Preview request failed for:', title);
362             }
363         });
364     }
365 
366     function buildPopupHTML(data) {
367         let html = '';
368         html += '<div class="flatmmo-preview-title">' + data.title + '</div>';
369         if (data.image) {
370             html += '<div class="flatmmo-preview-image"><img src="' + data.image + '"></div>';
371         }
372         if (data.text) {
373             html += '<div class="flatmmo-preview-text">' + data.text + '</div>';
374         }
375         if (!data.image && !data.text) {
376             html += '<div class="flatmmo-preview-text" style="color:#999;">No preview available.</div>';
377         }
378         return html;
379     }
380 
381     function showPreview(link, e) {
382         if (!popup) createPopup();
383 
384         const href = link.getAttribute('href');
385         if (!href) return;
386 
387         const match = href.match(/\/index\.php\/(.+)/);
388         if (!match) return;
389 
390         const title = decodeURIComponent(match[1]).replace(/_/g, ' ');
391 
392         if (title.match(/^Special:|^Talk:|^User:|^Category:|^File:|^Template:|^MediaWiki:/i)) return;
393         if (href.indexOf('action=edit') !== -1) return;
394         if (link.classList.contains('new')) return;
395 
396         positionPopup(e);
397         popup.innerHTML = '<div class="flatmmo-preview-title">' + title + '</div><div class="flatmmo-preview-loading">Loading...</div>';
398         popup.style.display = 'block';
399 
400         fetchPreview(title, function(data) {
401             popup.innerHTML = buildPopupHTML(data);
402         });
403     }
404 
405     const content = document.getElementById('mw-content-text') || document.getElementById('bodyContent') || document.body;
406 
407     // Strip all native tooltips from content links
408     $(content).find('[title]').removeAttr('title');
409 
410     if (isMobile) {
411         // Mobile: first tap shows preview, second tap follows link
412         $(content).on('click', 'a:not(.new)', function(e) {
413             const link = this;
414             if ($(link).closest('.flatmmo-preview').length) return;
415 
416             const href = link.getAttribute('href');
417             if (!href || !href.match(/\/index\.php\//)) return;
418             if (href.indexOf('action=edit') !== -1) return;
419 
420             if (link.getAttribute('data-preview-shown') === 'true') {
421                 link.removeAttribute('data-preview-shown');
422                 return;
423             }
424 
425             e.preventDefault();
426             link.setAttribute('data-preview-shown', 'true');
427             showPreview(link, e);
428 
429             setTimeout(function() {
430                 link.removeAttribute('data-preview-shown');
431             }, 3000);
432         });
433 
434         // Tap outside closes popup
435         $(document).on('click', function(e) {
436             if (popup && !$(e.target).closest('.flatmmo-preview').length && !$(e.target).closest('#mw-content-text a').length) {
437                 popup.style.display = 'none';
438             }
439         });
440     } else {
441         // Desktop: hover behavior
442         $(content).on('mouseover', 'a', function(e) {
443             const link = this;
444             if ($(link).closest('.flatmmo-preview').length) return;
445 
446             clearTimeout(hideTimer);
447             clearTimeout(showTimer);
448 
449             if (link.getAttribute('title')) {
450                 link.removeAttribute('title');
451             }
452 
453             showTimer = setTimeout(function() {
454                 showPreview(link, e);
455             }, 300);
456         });
457 
458         $(content).on('mouseout', 'a', function() {
459             clearTimeout(showTimer);
460             hideTimer = setTimeout(function() {
461                 if (popup) popup.style.display = 'none';
462             }, 200);
463         });
464     }
465 
466     console.log('Page preview popup initialized');
467 })();
468 
469 // --- Mobile table scroll fix ---
470 (function() {
471     if (window.innerWidth > 768) return;
472     const tables = document.querySelectorAll('#mw-content-text table');
473     for (let i = 0; i < tables.length; i++) {
474         const table = tables[i];
475         if (table.parentElement.classList.contains('table-scroll-wrap')) continue;
476         table.style.overflow = 'visible';
477         const wrapper = document.createElement('div');
478         wrapper.className = 'table-scroll-wrap';
479         table.parentNode.insertBefore(wrapper, table);
480         wrapper.appendChild(table);
481     }
482 })();
483 
484 }); // end $(document).ready