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

Coolrock (talk | contribs)
No edit summary
No edit summary
 
(9 intermediate revisions by 2 users not shown)
Line 6: Line 6:
   ============================================================ */
   ============================================================ */
(function() {
(function() {
     var saved = localStorage.getItem('flatmmo-theme') || 'dark';
     const saved = localStorage.getItem('flatmmo-theme') || 'dark';
     document.documentElement.className += ' ' + saved + '-theme';
     document.documentElement.className += ' ' + saved + '-theme';


     var searchMode = localStorage.getItem('flatmmo-search') || 'top';
     const searchMode = localStorage.getItem('flatmmo-search') || 'top';
     document.documentElement.className += ' search-' + searchMode;
     document.documentElement.className += ' search-' + searchMode;


Line 23: Line 23:
// --- Theme toggle button ---
// --- Theme toggle button ---
(function() {
(function() {
     var currentTheme = localStorage.getItem('flatmmo-theme') || 'dark';
     let currentTheme = localStorage.getItem('flatmmo-theme') || 'dark';


     document.body.classList.remove('dark-theme', 'light-theme');
     document.body.classList.remove('dark-theme', 'light-theme');
     document.body.classList.add(currentTheme + '-theme');
     document.body.classList.add(currentTheme + '-theme');


     var btn = document.createElement('button');
     const btn = document.createElement('button');
     btn.className = 'theme-toggle-btn';
     btn.className = 'theme-toggle-btn';
     btn.title = 'Toggle light/dark mode';
     btn.title = 'Toggle light/dark mode';
Line 34: Line 34:


     btn.addEventListener('click', function() {
     btn.addEventListener('click', function() {
         var isDark = document.body.classList.contains('dark-theme');
         const isDark = document.body.classList.contains('dark-theme');
         var newTheme = isDark ? 'light' : 'dark';
         const newTheme = isDark ? 'light' : 'dark';


         document.body.classList.remove('dark-theme', 'light-theme');
         document.body.classList.remove('dark-theme', 'light-theme');
Line 46: Line 46:
     });
     });


     var target = document.querySelector('.cosmos-header__wiki-buttons')
     const target = document.querySelector('.cosmos-header__wiki-buttons')
               || document.querySelector('.cosmos-actions')
               || document.querySelector('.cosmos-actions')
               || document.querySelector('.wds-button-group')
               || document.querySelector('.wds-button-group')
Line 61: Line 61:
// --- Search bar toggle + compact header search bar ---
// --- Search bar toggle + compact header search bar ---
(function() {
(function() {
     var searchMode = localStorage.getItem('flatmmo-search') || 'top';
     const searchMode = localStorage.getItem('flatmmo-search') || 'top';


     document.body.classList.remove('search-top', 'search-header');
     document.body.classList.remove('search-top', 'search-header');
Line 73: Line 73:
     }
     }


     var searchToggle = document.createElement('button');
     const searchToggle = document.createElement('button');
     searchToggle.className = 'search-toggle-btn';
     searchToggle.className = 'search-toggle-btn';
     searchToggle.title = 'Toggle search bar position';
     searchToggle.title = 'Toggle search bar position';
Line 79: Line 79:


     searchToggle.addEventListener('click', function() {
     searchToggle.addEventListener('click', function() {
         var isTop = document.body.classList.contains('search-top');
         const isTop = document.body.classList.contains('search-top');
         var newMode = isTop ? 'header' : 'top';
         const newMode = isTop ? 'header' : 'top';


         document.body.classList.remove('search-top', 'search-header');
         document.body.classList.remove('search-top', 'search-header');
Line 91: Line 91:
     });
     });


     var searchWrap = document.createElement('div');
     const searchWrap = document.createElement('div');
     searchWrap.className = 'header-search-wrap';
     searchWrap.className = 'header-search-wrap';


     var searchInput = document.createElement('input');
     const searchInput = document.createElement('input');
     searchInput.type = 'text';
     searchInput.type = 'text';
     searchInput.className = 'header-search-input';
     searchInput.className = 'header-search-input';
Line 100: Line 100:
     searchInput.setAttribute('autocomplete', 'off');
     searchInput.setAttribute('autocomplete', 'off');


     var searchBtn = document.createElement('button');
     const searchBtn = document.createElement('button');
     searchBtn.className = 'header-search-btn';
     searchBtn.className = 'header-search-btn';
     searchBtn.innerHTML = '🔍';
     searchBtn.innerHTML = '🔍';
Line 106: Line 106:


     function doSearch() {
     function doSearch() {
         var q = searchInput.value.trim();
         const q = searchInput.value.trim();
         if (q) {
         if (q) {
             window.location.href = mw.util.getUrl('Special:Search') + '?search=' + encodeURIComponent(q);
             window.location.href = mw.util.getUrl('Special:Search') + '?search=' + encodeURIComponent(q);
Line 120: Line 120:
     searchWrap.appendChild(searchBtn);
     searchWrap.appendChild(searchBtn);


     var target = document.querySelector('.cosmos-header__wiki-buttons')
     const target = document.querySelector('.cosmos-header__wiki-buttons')
               || document.querySelector('.cosmos-actions')
               || document.querySelector('.cosmos-actions')
               || document.querySelector('.wds-button-group')
               || document.querySelector('.wds-button-group')
Line 129: Line 129:
         target.appendChild(searchWrap);
         target.appendChild(searchWrap);


         var bannerUser = document.querySelector('.cosmos-userButton-label')
         const bannerUser = document.querySelector('.cosmos-userButton-label')
                       || document.querySelector('#p-personal-label');
                       || document.querySelector('#p-personal-label');
         var userName = bannerUser ? bannerUser.textContent.trim() : '';
         const userName = bannerUser ? bannerUser.textContent.trim() : '';
         if (userName) {
         if (userName) {
             var userLink = document.createElement('a');
             const userLink = document.createElement('a');
             userLink.className = 'relocated-user';
             userLink.className = 'relocated-user';
             userLink.href = mw.util.getUrl('User:' + userName);
             userLink.href = mw.util.getUrl('User:' + userName);
             userLink.title = userName;
             userLink.title = userName;


             var userIcon = document.createElement('span');
             const userIcon = document.createElement('span');
             userIcon.className = 'relocated-user-icon';
             userIcon.className = 'relocated-user-icon';
             userIcon.innerHTML = '👤';
             userIcon.innerHTML = '👤';


             var userText = document.createElement('span');
             const userText = document.createElement('span');
             userText.className = 'relocated-user-name';
             userText.className = 'relocated-user-name';
             userText.textContent = userName;
             userText.textContent = userName;
Line 201: Line 201:
     $.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");
}
}


Line 207: Line 212:
     accuracy = parseInt(accuracy);
     accuracy = parseInt(accuracy);
     defence = parseInt(defence);
     defence = parseInt(defence);
     var hc = 0;
      
     if (accuracy >= defence) {
     if (accuracy >= defence) {
         hc = 1;
         return 100;
    } else {
        hc = 1 / (1 + defence - accuracy);
     }
     }
     return (hc * 100).toFixed(2);
      
    const diff = defence - accuracy;
    const hc = Math.max(100 - (diff * 10), 5);
   
    return hc.toFixed(2);
}
}


if (document.querySelector('.hitChance')) {
if (document.querySelector('.hitChance')) {
     document.querySelectorAll(".hitChance").forEach(function(el) {
     document.querySelectorAll(".hitChance").forEach(function(el) {
         var parts = el.innerText.split(",");
         const parts = el.innerText.split(",");
         var acc = parts[0];
         let acc = parts[0];
         var def = parts[1];
         let def = parts[1];
         el.innerText = "";
         el.innerText = "";
         var accSpan = document.createElement("span");
         const accSpan = document.createElement("span");
         var defSpan = document.createElement("span");
         const defSpan = document.createElement("span");
         var hitSpan = document.createElement("span");
         const hitSpan = document.createElement("span");
         var accInput = document.createElement("input");
         const accInput = document.createElement("input");
         var defInput = document.createElement("input");
         const defInput = document.createElement("input");
         var hitValueSpan = document.createElement("span");
         const hitValueSpan = document.createElement("span");


         accSpan.innerText = "Accuracy:";
         accSpan.innerText = "Accuracy:";
Line 260: Line 267:
// --- Page Preview Popup ---
// --- Page Preview Popup ---
(function() {
(function() {
     var popup = null;
     let popup = null;
     var hideTimer = null;
     let hideTimer = null;
     var showTimer = null;
     let showTimer = null;
     var previewCache = {};
     const previewCache = {};
    const isMobile = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0);


     function createPopup() {
     function createPopup() {
Line 281: Line 289:


     function positionPopup(e) {
     function positionPopup(e) {
         var x = e.pageX + 15;
         // On mobile, let CSS handle positioning (fixed bottom sheet)
         var y = e.pageY + 15;
        if (isMobile) {
         var popupWidth = 340;
            popup.style.left = '';
         var popupHeight = 200;
            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) {
         if (x + popupWidth > document.documentElement.scrollLeft + window.innerWidth) {
Line 303: Line 317:
         }
         }


         var apiUrl = mw.util.wikiScript('api') + '?action=parse&page=' + encodeURIComponent(title) + '&prop=text&format=json&redirects=1';
         const apiUrl = mw.util.wikiScript('api') + '?action=parse&page=' + encodeURIComponent(title) + '&prop=text&format=json&redirects=1';


         $.ajax({
         $.ajax({
Line 310: Line 324:
             success: function(data) {
             success: function(data) {
                 if (data.parse && data.parse.text) {
                 if (data.parse && data.parse.text) {
                     var html = data.parse.text['*'];
                     const html = data.parse.text['*'];
                     var temp = document.createElement('div');
                     const temp = document.createElement('div');
                     temp.innerHTML = html;
                     temp.innerHTML = html;


                     var img = null;
                     let img = null;
                     var infoboxImg = temp.querySelector('.flatmmo-infobox-image img, .flatmmo-infobox img');
                     const infoboxImg = temp.querySelector('.flatmmo-infobox-image img, .flatmmo-infobox img');
                     if (infoboxImg) {
                     if (infoboxImg) {
                         img = infoboxImg.src;
                         img = infoboxImg.src;
                     } else {
                     } else {
                         var firstImg = temp.querySelector('img');
                         const firstImg = temp.querySelector('img');
                         if (firstImg && firstImg.width > 20) {
                         if (firstImg && firstImg.width > 20) {
                             img = firstImg.src;
                             img = firstImg.src;
Line 325: Line 339:
                     }
                     }


                     var text = '';
                     let text = '';
                     var paragraphs = temp.querySelectorAll('p');
                     const paragraphs = temp.querySelectorAll('p');
                     for (var i = 0; i < paragraphs.length; i++) {
                     for (let i = 0; i < paragraphs.length; i++) {
                         var t = paragraphs[i].textContent.trim();
                         const t = paragraphs[i].textContent.trim();
                         if (t.length > 10) {
                         if (t.length > 10) {
                             text = t;
                             text = t;
Line 339: Line 353:
                     }
                     }


                     var result = { image: img, text: text, title: title };
                     const result = { image: img, text: text, title: title };
                     previewCache[title] = result;
                     previewCache[title] = result;
                     callback(result);
                     callback(result);
Line 350: Line 364:
     }
     }


     function buildPopupHTML(data, includeClose) {
     function buildPopupHTML(data) {
         var html = '';
         let html = '';
        if (includeClose) {
            html += '<span class="flatmmo-preview-close">&times;</span>';
        }
         html += '<div class="flatmmo-preview-title">' + data.title + '</div>';
         html += '<div class="flatmmo-preview-title">' + data.title + '</div>';
         if (data.image) {
         if (data.image) {
Line 366: Line 377:
         }
         }
         return html;
         return html;
    }
    function attachClose() {
        var closeBtn = popup.querySelector('.flatmmo-preview-close');
        if (closeBtn) {
            closeBtn.addEventListener('click', function() {
                popup.style.display = 'none';
            });
        }
     }
     }


Line 380: Line 382:
         if (!popup) createPopup();
         if (!popup) createPopup();


         var href = link.getAttribute('href');
         const href = link.getAttribute('href');
         if (!href) return;
         if (!href) return;


         var match = href.match(/\/index\.php\/(.+)/);
         const match = href.match(/\/index\.php\/(.+)/);
         if (!match) return;
         if (!match) return;


         var title = decodeURIComponent(match[1]).replace(/_/g, ' ');
         const title = decodeURIComponent(match[1]).replace(/_/g, ' ');


         if (title.match(/^Special:|^Talk:|^User:|^Category:|^File:|^Template:|^MediaWiki:/i)) return;
         if (title.match(/^Special:|^Talk:|^User:|^Category:|^File:|^Template:|^MediaWiki:/i)) return;
         if (href.indexOf('action=edit') !== -1) return;
         if (href.indexOf('action=edit') !== -1) return;
         if (link.classList.contains('new')) return;
         if (link.classList.contains('new')) return;
        var isMobile = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0);


         positionPopup(e);
         positionPopup(e);
         popup.innerHTML = '<span class="flatmmo-preview-close">&times;</span><div class="flatmmo-preview-title">' + title + '</div><div class="flatmmo-preview-loading">Loading...</div>';
         popup.innerHTML = '<div class="flatmmo-preview-title">' + title + '</div><div class="flatmmo-preview-loading">Loading...</div>';
         popup.style.display = 'block';
         popup.style.display = 'block';
        attachClose();


         fetchPreview(title, function(data) {
         fetchPreview(title, function(data) {
             popup.innerHTML = buildPopupHTML(data, true);
             popup.innerHTML = buildPopupHTML(data);
            attachClose();
         });
         });
     }
     }


     var isMobile = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0);
     const content = document.getElementById('mw-content-text') || document.getElementById('bodyContent') || document.body;
    var content = document.getElementById('mw-content-text') || document.getElementById('bodyContent') || document.body;


     // Strip all native tooltips from content links
     // Strip all native tooltips from content links
     $(content).find('a[title]').removeAttr('title');
     $(content).find('[title]').removeAttr('title');


     if (isMobile) {
     if (isMobile) {
         // Mobile: first tap shows preview, second tap follows link
         // Mobile: first tap shows preview, second tap follows link
         $(content).on('click', 'a:not(.new)', function(e) {
         $(content).on('click', 'a:not(.new)', function(e) {
             var link = this;
             const link = this;
             if ($(link).closest('.flatmmo-preview').length) return;
             if ($(link).closest('.flatmmo-preview').length) return;


             var href = link.getAttribute('href');
             const href = link.getAttribute('href');
             if (!href || !href.match(/\/index\.php\//)) return;
             if (!href || !href.match(/\/index\.php\//)) return;
             if (href.indexOf('action=edit') !== -1) return;
             if (href.indexOf('action=edit') !== -1) return;
Line 444: Line 441:
         // Desktop: hover behavior
         // Desktop: hover behavior
         $(content).on('mouseover', 'a', function(e) {
         $(content).on('mouseover', 'a', function(e) {
             var link = this;
             const link = this;
             if ($(link).closest('.flatmmo-preview').length) return;
             if ($(link).closest('.flatmmo-preview').length) return;


Line 468: Line 465:


     console.log('Page preview popup initialized');
     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
}); // 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