MediaWiki:Common.js: Difference between revisions

From AlphaX Wiki
Jump to navigation Jump to search
Add Life Planning hero CSS; move all 8 cards to ID-based grid fixer
Add geo dashboard script: Users by Country with day/week/month periods, activity chart, world map
 
(13 intermediate revisions by the same user not shown)
Line 1: Line 1:
// Hero logo image styling - white + orange glow
(function() {
var s = document.createElement('style');
s.textContent = '#ax-hero-logo img { filter: hue-rotate(200deg) saturate(8) brightness(1.6) drop-shadow(0 0 20px rgba(255,120,0,0.9)) drop-shadow(0 0 50px rgba(255,80,0,0.5)); max-width:520px; width:80%; height:auto; display:inline-block; }';
document.head.appendChild(s);
})();
// Knowledge Areas grid layout fixer
// Knowledge Areas grid layout fixer
(function() {
(function() {
Line 45: Line 52:
(function() {
(function() {
   var style = document.createElement('style');
   var style = document.createElement('style');
   style.textContent = '#ax-sexual-health-hero { background-image: url("https://alphax.wiki/images/4/43/Sexual_Health_Hero.jpg"); background-size: cover; background-position: center center; } #ax-dating-hero { background-image: url("https://alphax.wiki/images/0/0b/Dating_Sex_Relationships_Hero.png"); background-size: cover; background-position: center center; } #ax-kink-hero { background-image: url("https://alphax.wiki/images/e/e5/Kink_BDSM_Hero.png"); background-size: cover; background-position: center center; } #ax-culture-hero { background-image: url("https://alphax.wiki/images/2/25/Culture_History_Politics_Hero.png"); background-size: cover; background-position: center center; } #ax-fashion-hero { background-image: url("https://alphax.wiki/images/4/4b/Fashion_Visual_Signaling_Hero.png"); background-size: cover; background-position: center top; } #ax-community-hero { background-image: url("https://alphax.wiki/images/e/ed/Community_Identity_Hero.png"); background-size: cover; background-position: center center; } #ax-drugs-hero { background-image: url("https://alphax.wiki/images/c/c7/Drugs_Party_Culture_Hero.jpg"); background-size: cover; background-position: center center; } #ax-life-hero { background-image: url("https://alphax.wiki/images/7/74/Life_Planning_Hero.jpg"); background-size: cover; background-position: center top; }';
   style.textContent = '#ax-sexual-health-hero { background-image: url("https://alphax.wiki/images/c/c2/Sexual_health.png"); background-size: cover; background-position: center center; } #ax-dating-hero { background-image: url("https://alphax.wiki/images/6/6c/Dating_and_relationships.png"); background-size: cover; background-position: center center; } #ax-kink-hero { background-image: url("https://alphax.wiki/images/e/e5/Kink_BDSM_Hero.png"); background-size: cover; background-position: center center; } #ax-culture-hero { background-image: url("https://alphax.wiki/images/2/25/Culture_History_Politics_Hero.png"); background-size: cover; background-position: center center; } #ax-fashion-hero { background-image: url("https://alphax.wiki/images/4/4b/Fashion_Visual_Signaling_Hero.png"); background-size: cover; background-position: center top; } #ax-community-hero { background-image: url("https://alphax.wiki/images/e/ed/Community_Identity_Hero.png"); background-size: cover; background-position: center center; } #ax-drugs-hero { background-image: url("https://alphax.wiki/images/c/c7/Drugs_Party_Culture_Hero.jpg"); background-size: cover; background-position: center center; } #ax-life-hero { background-image: url("https://alphax.wiki/images/7/74/Life_Planning_Hero.jpg"); background-size: cover; background-position: center top; }';
   document.head.appendChild(style);
   document.head.appendChild(style);
})();
})();
Line 641: Line 648:
   } else {
   } else {
     applyCatImages();
     applyCatImages();
  }
})();
// Add Impressum link to footer places row
(function() {
  function addImpressumToFooter() {
    // Find the footer-places list
    var footerPlaces = document.getElementById('footer-places');
    if (!footerPlaces) return;
    // Check if Impressum link already exists
    if (document.getElementById('footer-places-impressum')) return;
    // Create the new list item
    var li = document.createElement('li');
    li.id = 'footer-places-impressum';
    var a = document.createElement('a');
    a.href = '/Impressum';
    a.textContent = 'Impressum';
    li.appendChild(a);
    footerPlaces.appendChild(li);
  }
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', addImpressumToFooter);
  } else {
    addImpressumToFooter();
  }
})();
// ===== Permanent Favicon =====
(function() {
  document.querySelectorAll('link[rel="icon"], link[rel="shortcut icon"]').forEach(function(el) {
    el.parentNode.removeChild(el);
  });
  var link = document.createElement('link');
  link.rel = 'icon';
  link.type = 'image/png';
  link.href = 'https://alphax.wiki/images/2/26/Favicon.png';
  document.head.appendChild(link);
})();
// =====================================================
// Users by Country Counter - Admin Page Widget
// =====================================================
(function() {
  'use strict';
  if (mw.config.get('wgPageName') !== 'User:Admin') return;
  var langToCountry = {
    'en': '🇺🇸 English-speaking', 'de': '🇩🇪 Germany / German-speaking',
    'fr': '🇫🇷 France / French-speaking', 'es': '🇪🇸 Spain / Spanish-speaking',
    'pt': '🇵🇹 Portugal / Portuguese-speaking', 'it': '🇮🇹 Italy / Italian-speaking',
    'nl': '🇳🇱 Netherlands / Dutch-speaking', 'pl': '🇵🇱 Poland / Polish-speaking',
    'ru': '🇷🇺 Russia / Russian-speaking', 'ja': '🇯🇵 Japan',
    'zh': '🇨🇳 China / Chinese-speaking', 'ar': '🇸🇦 Arabic-speaking',
    'tr': '🇹🇷 Turkey / Turkish-speaking', 'sv': '🇸🇪 Sweden / Swedish-speaking',
    'da': '🇩🇰 Denmark / Danish-speaking', 'fi': '🇫🇮 Finland / Finnish-speaking',
    'nb': '🇳🇴 Norway / Norwegian-speaking', 'cs': '🇨🇿 Czech Republic',
    'hu': '🇭🇺 Hungary', 'ro': '🇷🇴 Romania', 'el': '🇬🇷 Greece / Greek-speaking',
    'ko': '🇰🇷 Korea / Korean-speaking', 'uk': '🇺🇦 Ukraine / Ukrainian-speaking',
    'he': '🇮🇱 Israel / Hebrew-speaking', 'id': '🇮🇩 Indonesia',
    'vi': '🇻🇳 Vietnam', 'th': '🇹🇭 Thailand', 'hi': '🇮🇳 India / Hindi-speaking',
    'bn': '🇧🇩 Bangladesh / Bengali-speaking', 'fa': '🇮🇷 Iran / Persian-speaking'
  };
  function renderResults(langCounts, totalUsers) {
    var container = document.getElementById('country-user-counter');
    if (!container) return;
    var sorted = Object.keys(langCounts)
      .filter(function(l) { return langCounts[l] > 0; })
      .sort(function(a, b) { return langCounts[b] - langCounts[a]; });
    var html = '<style>';
    html += '#cuc-wrap{font-family:sans-serif}';
    html += '#cuc-stats{display:flex;gap:20px;margin-bottom:14px;flex-wrap:wrap}';
    html += '.cuc-stat-card{background:#222;border:1px solid #444;border-radius:6px;padding:10px 18px;min-width:120px;text-align:center}';
    html += '.cuc-stat-num{font-size:1.8em;font-weight:bold;color:#e07000}';
    html += '.cuc-stat-lbl{font-size:0.8em;color:#aaa;margin-top:2px}';
    html += '#cuc-table{width:100%;border-collapse:collapse;margin-top:4px}';
    html += '#cuc-table th{background:#2a2a2a;color:#e07000;padding:8px 12px;text-align:left;border:1px solid #444;font-size:0.9em}';
    html += '#cuc-table td{padding:7px 12px;border:1px solid #2e2e2e;color:#ddd;font-size:0.9em}';
    html += '#cuc-table tr:nth-child(even) td{background:#1c1c1c}';
    html += '#cuc-table tr:hover td{background:#252525}';
    html += '.cuc-bar-wrap{background:#111;border-radius:3px;height:14px;overflow:hidden}';
    html += '.cuc-bar{height:14px;background:linear-gradient(90deg,#e07000,#ff9a00);border-radius:3px;transition:width 0.4s ease}';
    html += '.cuc-no-data{background:#1a1a1a;border:1px dashed #444;border-radius:6px;padding:16px;color:#aaa;line-height:1.6}';
    html += '.cuc-no-data code{background:#222;padding:2px 6px;border-radius:3px;color:#e07000;font-size:0.9em}';
    html += '#cuc-timestamp{color:#555;font-size:0.8em;margin-top:10px}';
    html += '</style>';
    html += '<div id="cuc-wrap">';
    // Stats cards
    html += '<div id="cuc-stats">';
    html += '<div class="cuc-stat-card"><div class="cuc-stat-num">' + totalUsers + '</div><div class="cuc-stat-lbl">Total Users</div></div>';
    html += '<div class="cuc-stat-card"><div class="cuc-stat-num">' + sorted.length + '</div><div class="cuc-stat-lbl">Countries / Regions</div></div>';
    var locatedUsers = sorted.reduce(function(sum, l) { return sum + langCounts[l]; }, 0);
    html += '<div class="cuc-stat-card"><div class="cuc-stat-num">' + locatedUsers + '</div><div class="cuc-stat-lbl">Users with Location</div></div>';
    html += '</div>';
    if (sorted.length === 0) {
      html += '<div class="cuc-no-data">';
      html += '<strong>📍 No country data available yet.</strong><br>';
      html += 'This counter tracks users by country using the <strong>Babel extension</strong> language categories.<br>';
      html += 'Users can declare their language/country by adding babel tags to their user page:<br><br>';
      html += '<code>{{#babel:en|de|fr}}</code><br><br>';
      html += 'Once users add babel tags, this counter will automatically display their country distribution.<br>';
      html += 'Currently tracking <strong>' + totalUsers + ' registered users</strong> on this wiki.';
      html += '</div>';
    } else {
      var maxCount = langCounts[sorted[0]] || 1;
      html += '<table id="cuc-table"><thead><tr>';
      html += '<th style="width:40px">#</th>';
      html += '<th>Country / Region</th>';
      html += '<th style="width:70px;text-align:center">Users</th>';
      html += '<th style="width:200px">Distribution</th>';
      html += '</tr></thead><tbody>';
      sorted.forEach(function(lang, idx) {
        var count = langCounts[lang];
        var label = langToCountry[lang] || ('🌐 ' + lang.toUpperCase());
        var pct = Math.round((count / locatedUsers) * 100);
        var barWidth = Math.round((count / maxCount) * 180);
        html += '<tr>';
        html += '<td style="color:#666;text-align:center">' + (idx + 1) + '</td>';
        html += '<td>' + label + '</td>';
        html += '<td style="text-align:center;font-weight:bold;color:#e07000">' + count + '</td>';
        html += '<td><div class="cuc-bar-wrap"><div class="cuc-bar" style="width:' + barWidth + 'px"></div></div></td>';
        html += '</tr>';
      });
      html += '</tbody></table>';
    }
    html += '<div id="cuc-timestamp">Last updated: ' + new Date().toLocaleString() + '</div>';
    html += '</div>';
    container.innerHTML = html;
  }
  function buildCounter() {
    var container = document.getElementById('country-user-counter');
    if (!container) return;
    container.innerHTML = '<p style="color:#aaa;font-style:italic">⏳ Loading user statistics by country...</p>';
    var allUsers = [];
    var langCounts = {};
    var langs = Object.keys(langToCountry);
    function fetchUsers(aufrom) {
      var params = { action: 'query', list: 'allusers', aulimit: 500, format: 'json' };
      if (aufrom) params.aufrom = aufrom;
      return $.getJSON(mw.util.wikiScript('api'), params).then(function(data) {
        var users = (data.query && data.query.allusers) || [];
        allUsers = allUsers.concat(users);
        if (data['continue'] && data['continue'].aufrom) return fetchUsers(data['continue'].aufrom);
      });
    }
    function fetchLangCat(lang) {
      return $.getJSON(mw.util.wikiScript('api'), {
        action: 'query', list: 'categorymembers',
        cmtitle: 'Category:User_' + lang,
        cmtype: 'page', cmnamespace: 2, cmlimit: 500, format: 'json'
      }).then(function(data) {
        var m = (data.query && data.query.categorymembers) || [];
        if (m.length > 0) langCounts[lang] = (langCounts[lang] || 0) + m.length;
      }).catch(function() {});
    }
    fetchUsers().then(function() {
      var d = $.Deferred().resolve().promise();
      langs.forEach(function(lang) { d = d.then(function() { return fetchLangCat(lang); }); });
      return d;
    }).then(function() {
      renderResults(langCounts, allUsers.length);
    }).catch(function(e) {
      container.innerHTML = '<p style="color:#f66">⚠️ Error loading data: ' + String(e) + '</p>';
    });
  }
  $(document).ready(function() { buildCounter(); });
})();
// =====================================================
// End: Users by Country Counter
// =====================================================
// ===== Article Overview Page - Category/Subcategory/Title/Words/Focus Table =====
(function() {
  if (mw.config.get('wgPageName') !== 'Article_Overview') return;
  var apiBase = mw.util.wikiScript('api');
  var allRows = [];
  var tableBody = null;
  var statusSpan = null;
  $(document).ready(function() {
    var wrapper = document.getElementById('article-overview-wrapper');
    if (!wrapper) return;
    // Build controls row with button + status
    var controls = document.createElement('div');
    controls.style.marginBottom = '14px';
    var btn = document.createElement('button');
    btn.textContent = 'Download CSV';
    btn.style.cssText = 'background-color:#e07b00;color:#fff;padding:9px 20px;border:none;border-radius:5px;font-size:14px;font-weight:bold;cursor:pointer;letter-spacing:0.3px;';
    btn.addEventListener('click', downloadCSV);
    controls.appendChild(btn);
    statusSpan = document.createElement('span');
    statusSpan.style.cssText = 'margin-left:14px;font-size:13px;color:#aaa;vertical-align:middle;';
    statusSpan.textContent = 'Loading…';
    controls.appendChild(statusSpan);
    wrapper.appendChild(controls);
    // Build table
    var table = document.createElement('table');
    table.style.cssText = 'width:100%;border-collapse:collapse;font-size:13px;';
    var thead = document.createElement('thead');
    var headerRow = document.createElement('tr');
    headerRow.style.cssText = 'background-color:#2a2a2a;color:#f0a500;text-align:left;';
    var cols = ['Category', 'Subcategory', 'Title', 'Words', 'Focus'];
    cols.forEach(function(colName, i) {
      var th = document.createElement('th');
      th.textContent = colName;
      th.style.cssText = 'padding:9px 12px;border:1px solid #444;' + (colName === 'Words' ? 'text-align:right;' : '');
      headerRow.appendChild(th);
    });
    thead.appendChild(headerRow);
    table.appendChild(thead);
    tableBody = document.createElement('tbody');
    tableBody.innerHTML = '<tr><td colspan="5" style="padding:12px;text-align:center;color:#aaa;font-style:italic;">Loading articles… please wait.</td></tr>';
    table.appendChild(tableBody);
    wrapper.appendChild(table);
    buildTable();
  });
  function getCategoryMembers(catName, cmtype, continueParam, accumulated, callback) {
    var params = {
      action: 'query', list: 'categorymembers',
      cmtitle: 'Category:' + catName,
      cmlimit: 500, cmtype: cmtype, format: 'json'
    };
    if (continueParam) params.cmcontinue = continueParam;
    $.getJSON(apiBase, params).done(function(data) {
      var members = (data.query && data.query.categorymembers) ? data.query.categorymembers : [];
      members.forEach(function(m){ accumulated.push(m); });
      if (data.continue && data.continue.cmcontinue) {
        getCategoryMembers(catName, cmtype, data.continue.cmcontinue, accumulated, callback);
      } else {
        callback(accumulated);
      }
    }).fail(function(){ callback(accumulated); });
  }
  function escHtml(str) {
    return String(str).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
  }
  function renderTable(rows) {
    if (!tableBody) return;
    if (!rows || rows.length === 0) {
      tableBody.innerHTML = '<tr><td colspan="5" style="padding:10px;text-align:center;color:#aaa;">No articles found.</td></tr>';
      return;
    }
    var html = '';
    rows.forEach(function(row, i) {
      var bg = i % 2 === 0 ? 'rgba(255,255,255,0.03)' : 'rgba(255,255,255,0.07)';
      html += '<tr style="background:' + bg + ';">';
      html += '<td style="padding:6px 10px;border:1px solid #444;">' + escHtml(row.category) + '</td>';
      html += '<td style="padding:6px 10px;border:1px solid #444;">' + escHtml(row.sub) + '</td>';
      html += '<td style="padding:6px 10px;border:1px solid #444;"><a href="' + mw.util.getUrl(row.title) + '">' + escHtml(row.title) + '</a></td>';
      html += '<td style="padding:6px 10px;border:1px solid #444;text-align:right;">' + escHtml(String(row.words)) + '</td>';
      html += '<td style="padding:6px 10px;border:1px solid #444;">' + escHtml(row.focus) + '</td>';
      html += '</tr>';
    });
    tableBody.innerHTML = html;
  }
  function buildTable() {
    if (statusSpan) statusSpan.textContent = 'Fetching categories…';
    $.getJSON(apiBase, { action:'query', list:'allcategories', aclimit:500, format:'json' })
      .done(function(data) {
        var topCats = (data.query && data.query.allcategories) ? data.query.allcategories.map(function(c){ return c['*']; }) : [];
        var rows = [];
        var totalCats = topCats.length;
        var processed = 0;
        if (totalCats === 0) {
          if (tableBody) tableBody.innerHTML = '<tr><td colspan="5" style="padding:10px;text-align:center;color:#aaa;">No categories found.</td></tr>';
          if (statusSpan) statusSpan.textContent = '';
          return;
        }
        function checkDone() {
          processed++;
          if (statusSpan) statusSpan.textContent = 'Loading… (' + processed + '/' + totalCats + ' categories)';
          if (processed === totalCats) {
            rows.sort(function(a,b){
              var ca = a.category.toLowerCase(), cb = b.category.toLowerCase();
              if (ca < cb) return -1; if (ca > cb) return 1;
              var sa = a.sub.toLowerCase(), sb = b.sub.toLowerCase();
              if (sa < sb) return -1; if (sa > sb) return 1;
              return a.title.localeCompare(b.title);
            });
            allRows = rows;
            renderTable(rows);
            if (statusSpan) statusSpan.textContent = rows.length + ' article entries loaded.';
          }
        }
        topCats.forEach(function(catName) {
          getCategoryMembers(catName, 'page|subcat', null, [], function(members) {
            var pages = members.filter(function(m){ return m.ns === 0; });
            var subcats = members.filter(function(m){ return m.ns === 14; });
            if (pages.length === 0 && subcats.length === 0) {
              checkDone(); return;
            }
            var subTotal = pages.length + subcats.length;
            var subProcessed = 0;
            function subDone() {
              subProcessed++;
              if (subProcessed >= subTotal) checkDone();
            }
            pages.forEach(function(page) {
              rows.push({ category: catName, sub: '—', title: page.title, words: 'n/a', focus: catName });
              subDone();
            });
            subcats.forEach(function(subcat) {
              var subName = subcat.title.replace('Category:','');
              getCategoryMembers(subName, 'page', null, [], function(subPages) {
                subPages.filter(function(m){ return m.ns === 0; }).forEach(function(page) {
                  rows.push({ category: catName, sub: subName, title: page.title, words: 'n/a', focus: catName });
                });
                subDone();
              });
            });
          });
        });
      })
      .fail(function() {
        if (tableBody) tableBody.innerHTML = '<tr><td colspan="5" style="padding:10px;text-align:center;color:#f55;">Error loading data. Check API access.</td></tr>';
      });
  }
  function downloadCSV() {
    if (!allRows || allRows.length === 0) {
      alert('No data to download yet. Please wait for the table to finish loading.');
      return;
    }
    var csv = 'Category,Subcategory,Title,Words,Focus\n';
    allRows.forEach(function(row) {
      csv += ['category','sub','title','words','focus'].map(function(k){
        return '"' + String(row[k]).replace(/"/g,'""') + '"';
      }).join(',') + '\n';
    });
    var blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
    var url = URL.createObjectURL(blob);
    var a = document.createElement('a');
    a.href = url; a.download = 'article-overview.csv';
    document.body.appendChild(a); a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
  }
})();
// ═══════════════════════════════════════════════════════════
// GEO DASHBOARD – Users by Country (Day / Week / Month)
// ═══════════════════════════════════════════════════════════
(function () {
  if (!document.getElementById('geo-dashboard')) return;
  var API = '/api.php';
  function isoDate(d) { return d.toISOString().slice(0, 10); }
  function startOf(period) {
    var now = new Date();
    if (period === 'day') return new Date(now.getFullYear(), now.getMonth(), now.getDate());
    if (period === 'week') { var d = new Date(now); d.setDate(d.getDate() - d.getDay()); d.setHours(0,0,0,0); return d; }
    if (period === 'month') return new Date(now.getFullYear(), now.getMonth(), 1);
    return new Date(0);
  }
  var COUNTRY_MAP = {
    'en':'🇺🇸 English / US','de':'🇩🇪 German','fr':'🇫🇷 French','es':'🇪🇸 Spanish',
    'it':'🇮🇹 Italian','pt':'🇧🇷 Portuguese','ru':'🇷🇺 Russian','ja':'🇯🇵 Japanese',
    'zh':'🇨🇳 Chinese','ko':'🇰🇷 Korean','ar':'🇸🇦 Arabic','nl':'🇳🇱 Dutch',
    'pl':'🇵🇱 Polish','sv':'🇸🇪 Swedish','no':'🇳🇴 Norwegian','da':'🇩🇰 Danish',
    'fi':'🇫🇮 Finnish','tr':'🇹🇷 Turkish','cs':'🇨🇿 Czech','hu':'🇭🇺 Hungarian',
    'ro':'🇷🇴 Romanian','uk':'🇺🇦 Ukrainian','vi':'🇻🇳 Vietnamese','th':'🇹🇭 Thai',
    'id':'🇮🇩 Indonesian','ms':'🇲🇾 Malay','he':'🇮🇱 Hebrew','fa':'🇮🇷 Persian',
    'hi':'🇮🇳 Hindi','bn':'🇧🇩 Bengali'
  };
  function fetchUsers() {
    return fetch(API + '?action=query&list=allusers&aulimit=500&format=json')
      .then(function(r) { return r.json(); })
      .then(function(d) { return d.query.allusers || []; });
  }
  function fetchChanges() {
    return fetch(API + '?action=query&list=recentchanges&rclimit=500&rcprop=user%7Ctimestamp&format=json')
      .then(function(r) { return r.json(); })
      .then(function(d) { return d.query.recentchanges || []; });
  }
  function fetchBabel(username) {
    return fetch(API + '?action=query&titles=User:' + encodeURIComponent(username) + '&prop=categories&format=json')
      .then(function(r) { return r.json(); })
      .then(function(d) {
        var pages = (d.query && d.query.pages) ? d.query.pages : {};
        var langs = [];
        Object.values(pages).forEach(function(p) {
          (p.categories || []).forEach(function(c) {
            var m = c.title.match(/Category:User ([a-z]{2,3})/i);
            if (m) langs.push(m[1].toLowerCase());
          });
        });
        return langs;
      });
  }
  function drawMap(activeLangs) {
    var svg = document.getElementById('geo-world-map');
    if (!svg) return;
    svg.innerHTML = '';
    var bg = document.createElementNS('http://www.w3.org/2000/svg','rect');
    bg.setAttribute('width','900'); bg.setAttribute('height','440'); bg.setAttribute('fill','#0d1f33');
    svg.appendChild(bg);
    var continents = [
      'M80,100 L240,95 L255,200 L220,230 L170,235 L110,220 L75,170 Z',
      'M155,240 L220,235 L235,360 L195,385 L155,370 L138,320 Z',
      'M385,90 L480,85 L490,160 L450,175 L400,170 L378,140 Z',
      'M390,175 L460,170 L470,320 L430,345 L388,335 L375,280 Z',
      'M490,80 L750,75 L760,230 L700,250 L520,240 L485,180 Z',
      'M640,280 L740,275 L748,355 L700,368 L638,355 Z',
      'M230,50 L290,45 L295,90 L255,95 L228,82 Z'
    ];
    continents.forEach(function(d) {
      var path = document.createElementNS('http://www.w3.org/2000/svg','path');
      path.setAttribute('d', d); path.setAttribute('fill','#2a3a2a');
      path.setAttribute('stroke','#1a2a1a'); path.setAttribute('stroke-width','1');
      svg.appendChild(path);
    });
    var shapes = {
      'de':'M440,145 L455,140 L465,148 L462,165 L448,170 L438,162 Z',
      'fr':'M415,152 L430,148 L438,162 L430,175 L415,172 L408,162 Z',
      'es':'M390,170 L415,168 L415,185 L395,190 L385,182 Z',
      'it':'M450,168 L462,165 L468,180 L460,195 L450,188 L445,178 Z',
      'gb':'M418,135 L428,132 L430,145 L420,148 L414,142 Z',
      'ru':'M460,120 L560,110 L580,135 L540,145 L465,148 Z',
      'us':'M120,150 L220,148 L225,190 L200,200 L115,195 Z',
      'cn':'M620,150 L680,145 L685,175 L650,185 L615,178 Z',
      'jp':'M695,155 L708,152 L710,165 L700,168 L693,163 Z',
      'br':'M195,230 L240,225 L245,275 L215,282 L188,270 Z',
      'in':'M580,175 L615,170 L618,210 L595,218 L575,205 Z',
      'au':'M660,280 L720,275 L725,320 L690,328 L655,315 Z'
    };
    Object.keys(shapes).forEach(function(cc) {
      var isActive = activeLangs.some(function(l) { return l.startsWith(cc); });
      var path = document.createElementNS('http://www.w3.org/2000/svg','path');
      path.setAttribute('d', shapes[cc]);
      path.setAttribute('fill', isActive ? '#ff6600' : '#3a4a3a');
      path.setAttribute('stroke','#555'); path.setAttribute('stroke-width','1');
      path.setAttribute('opacity', isActive ? '0.9' : '0.5');
      svg.appendChild(path);
    });
    var labels = [{x:160,y:175,t:'North America'},{x:450,y:130,t:'Europe'},{x:620,y:165,t:'Asia'},
                  {x:430,y:265,t:'Africa'},{x:192,y:305,t:'South America'},{x:693,y:325,t:'Oceania'}];
    labels.forEach(function(loc) {
      var text = document.createElementNS('http://www.w3.org/2000/svg','text');
      text.setAttribute('x',loc.x); text.setAttribute('y',loc.y);
      text.setAttribute('fill','#444'); text.setAttribute('font-size','10');
      text.setAttribute('font-family','sans-serif'); text.setAttribute('text-anchor','middle');
      text.textContent = loc.t; svg.appendChild(text);
    });
  }
  function renderActivityBars(daily) {
    var wrap = document.getElementById('geo-activity-bars');
    var labelWrap = document.getElementById('geo-activity-labels');
    if (!wrap) return;
    var days = [];
    for (var i = 29; i >= 0; i--) {
      var d = new Date(); d.setDate(d.getDate() - i); days.push(isoDate(d));
    }
    var max = Math.max.apply(null, days.map(function(d){ return daily[d]||0; }).concat([1]));
    var bHtml = '', lHtml = '';
    days.forEach(function(day, idx) {
      var val = daily[day] || 0;
      var h = Math.max(3, Math.round((val/max)*70));
      var isToday = idx === 29;
      var col = isToday ? '#ff6600' : (val > 0 ? '#cc5500' : '#222');
      bHtml += '<div title="' + day + ': ' + val + ' edits" style="flex:1;background:' + col + ';height:' + h + 'px;border-radius:2px 2px 0 0;min-width:4px;"></div>';
      lHtml += '<div style="flex:1;text-align:center;white-space:nowrap;">' + (idx%5===0||isToday ? day.slice(5) : '') + '</div>';
    });
    wrap.innerHTML = bHtml; labelWrap.innerHTML = lHtml;
  }
  function renderTable(rows, total, period) {
    var tbody = document.getElementById('geo-table-body');
    var titleEl = document.getElementById('geo-table-title');
    if (!tbody) return;
    var labels = {day:'Today', week:'This Week', month:'This Month', all:'All Time'};
    if (titleEl) titleEl.textContent = 'Users by Country – ' + (labels[period]||'All Time');
    if (!rows.length) {
      tbody.innerHTML = '<tr><td colspan="5" style="padding:20px;text-align:center;color:#555;">No activity for this period.</td></tr>';
      return;
    }
    var html = '';
    rows.forEach(function(row, i) {
      var bar = '<div style="display:inline-block;background:#ff6600;height:8px;border-radius:2px;width:' + Math.round(row.pct) + '%;min-width:2px;vertical-align:middle;"></div><span style="color:#666;font-size:0.8em;margin-left:6px;">' + row.pct.toFixed(1) + '%</span>';
      html += '<tr style="background:' + (i%2===0?'#1a1a1a':'#1e1e1e') + ';border-top:1px solid #222;">' +
        '<td style="padding:10px 16px;color:#666;">' + (i+1) + '</td>' +
        '<td style="padding:10px 16px;">' + (COUNTRY_MAP[row.lang]||('🌐 '+row.lang)) + '</td>' +
        '<td style="padding:10px 16px;text-align:right;font-weight:bold;color:#ff6600;">' + row.users + '</td>' +
        '<td style="padding:10px 16px;">' + bar + '</td>' +
        '<td style="padding:10px 16px;text-align:right;color:#aaa;">' + row.edits + '</td></tr>';
    });
    tbody.innerHTML = html;
  }
  var cachedUsers = null, cachedChanges = null, cachedBabel = {}, currentPeriod = 'day';
  function showPeriod(period) {
    currentPeriod = period;
    ['day','week','month','all'].forEach(function(p) {
      var btn = document.getElementById('geo-btn-'+p);
      if (!btn) return;
      if (p === period) {
        btn.style.background='#ff6600'; btn.style.color='#fff';
        btn.style.borderColor='#ff6600'; btn.style.fontWeight='bold';
      } else {
        btn.style.background='#333'; btn.style.color='#eee';
        btn.style.borderColor='#555'; btn.style.fontWeight='normal';
      }
    });
    if (!cachedChanges || !cachedUsers) return;
    var cutoff = period === 'all' ? '1970-01-01' : startOf(period).toISOString();
    var userEdits = {};
    cachedChanges.forEach(function(c) {
      if (c.timestamp >= cutoff) userEdits[c.user] = (userEdits[c.user]||0)+1;
    });
    var langUsers = {}, langEdits = {};
    cachedUsers.forEach(function(u) {
      var langs = cachedBabel[u.name] || [];
      if (!langs.length) langs = ['unknown'];
      var edits = userEdits[u.name] || 0;
      var lang = langs[0];
      langUsers[lang] = (langUsers[lang]||0)+1;
      langEdits[lang] = (langEdits[lang]||0)+edits;
    });
    var total = cachedUsers.length;
    var cEl = document.getElementById('geo-countries');
    if (cEl) cEl.textContent = Object.keys(langUsers).filter(function(l){return l!=='unknown';}).length;
    var rows = Object.keys(langUsers).map(function(lang) {
      return {lang:lang, users:langUsers[lang], edits:langEdits[lang]||0, pct:total>0?(langUsers[lang]/total*100):0};
    }).sort(function(a,b) {
      if (a.lang==='unknown') return 1; if (b.lang==='unknown') return -1; return b.users-a.users;
    });
    renderTable(rows, total, period);
    drawMap(rows.filter(function(r){return r.lang!=='unknown';}).map(function(r){return r.lang;}));
  }
  window.geoShowPeriod = showPeriod;
  function boot() {
    var el = document.getElementById('geo-dashboard');
    if (!el) return;
    Promise.all([fetchUsers(), fetchChanges()]).then(function(results) {
      cachedUsers = results[0]; cachedChanges = results[1];
      var now = new Date();
      var tEl = document.getElementById('geo-total');
      if (tEl) tEl.textContent = cachedUsers.length;
      var updEl = document.getElementById('geo-updated');
      if (updEl) updEl.textContent = 'Updated: ' + now.toLocaleTimeString();
      var dStart = startOf('day').toISOString();
      var wStart = startOf('week').toISOString();
      var mStart = startOf('month').toISOString();
      var aDay=new Set(), aWeek=new Set(), aMonth=new Set();
      var daily = {};
      cachedChanges.forEach(function(c) {
        if (c.timestamp >= dStart) aDay.add(c.user);
        if (c.timestamp >= wStart) aWeek.add(c.user);
        if (c.timestamp >= mStart) aMonth.add(c.user);
        var day = c.timestamp.slice(0,10);
        daily[day] = (daily[day]||0)+1;
      });
      var adEl = document.getElementById('geo-active-day');
      var awEl = document.getElementById('geo-active-week');
      var amEl = document.getElementById('geo-active-month');
      if (adEl) adEl.textContent = aDay.size;
      if (awEl) awEl.textContent = aWeek.size;
      if (amEl) amEl.textContent = aMonth.size;
      renderActivityBars(daily);
      var babelPromises = cachedUsers.map(function(u) {
        return fetchBabel(u.name).then(function(langs) { cachedBabel[u.name] = langs; });
      });
      Promise.all(babelPromises).then(function() { showPeriod('day'); });
      // wire up buttons
      ['day','week','month','all'].forEach(function(p) {
        var btn = document.getElementById('geo-btn-'+p);
        if (btn) btn.addEventListener('click', function() { showPeriod(p); });
      });
    }).catch(function(e) { console.error('GEO dashboard error', e); });
  }
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', boot);
  } else {
    setTimeout(boot, 100);
   }
   }
})();
})();

Latest revision as of 05:10, 12 May 2026

// Hero logo image styling - white + orange glow
(function() {
var s = document.createElement('style');
s.textContent = '#ax-hero-logo img { filter: hue-rotate(200deg) saturate(8) brightness(1.6) drop-shadow(0 0 20px rgba(255,120,0,0.9)) drop-shadow(0 0 50px rgba(255,80,0,0.5)); max-width:520px; width:80%; height:auto; display:inline-block; }';
document.head.appendChild(s);
})();

// Knowledge Areas grid layout fixer
(function() {
  function fixKnowledgeGrid() {
    var heroDiv = document.getElementById('ax-sexual-health-hero');
    if (!heroDiv) return;
    var firstCard = heroDiv.closest('.ax-card');
    if (!firstCard) return;
    var gridContainer = firstCard.parentElement;
    if (!gridContainer) return;
    gridContainer.style.display = 'grid';
    gridContainer.style.gridTemplateColumns = 'repeat(2, 1fr)';
    gridContainer.style.gap = '14px';
    // Collect cards by hero IDs
    var heroIds = ['ax-sexual-health-hero','ax-dating-hero','ax-kink-hero','ax-culture-hero','ax-fashion-hero','ax-community-hero','ax-drugs-hero','ax-life-hero'];
    var allKnowledgeCards = [];
    heroIds.forEach(function(id) {
      var h = document.getElementById(id);
      if (h) { var c = h.closest('.ax-card'); if (c) allKnowledgeCards.push(c); }
    });
    // Find remaining 3 knowledge cards by keyword match (Community, Drugs, Life Planning)
    // These have no hero images. Identify by checking they are NOT Start Learning or Featured
    var keywords = [];
    Array.from(document.querySelectorAll('.ax-card')).forEach(function(c) {
      if (allKnowledgeCards.indexOf(c) >= 0) return;
      var txt = c.innerText || '';
      keywords.forEach(function(kw) {
        if (txt.indexOf(kw) >= 0 && allKnowledgeCards.indexOf(c) < 0) {
          allKnowledgeCards.push(c);
        }
      });
    });
    // Move all knowledge cards into grid
    allKnowledgeCards.forEach(function(c) {
      if (c.parentElement !== gridContainer) gridContainer.appendChild(c);
    });
  }
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', fixKnowledgeGrid);
  } else {
    fixKnowledgeGrid();
  }
})();

// Sexual Health hero image
(function() {
  var style = document.createElement('style');
  style.textContent = '#ax-sexual-health-hero { background-image: url("https://alphax.wiki/images/c/c2/Sexual_health.png"); background-size: cover; background-position: center center; } #ax-dating-hero { background-image: url("https://alphax.wiki/images/6/6c/Dating_and_relationships.png"); background-size: cover; background-position: center center; } #ax-kink-hero { background-image: url("https://alphax.wiki/images/e/e5/Kink_BDSM_Hero.png"); background-size: cover; background-position: center center; } #ax-culture-hero { background-image: url("https://alphax.wiki/images/2/25/Culture_History_Politics_Hero.png"); background-size: cover; background-position: center center; } #ax-fashion-hero { background-image: url("https://alphax.wiki/images/4/4b/Fashion_Visual_Signaling_Hero.png"); background-size: cover; background-position: center top; } #ax-community-hero { background-image: url("https://alphax.wiki/images/e/ed/Community_Identity_Hero.png"); background-size: cover; background-position: center center; } #ax-drugs-hero { background-image: url("https://alphax.wiki/images/c/c7/Drugs_Party_Culture_Hero.jpg"); background-size: cover; background-position: center center; } #ax-life-hero { background-image: url("https://alphax.wiki/images/7/74/Life_Planning_Hero.jpg"); background-size: cover; background-position: center top; }';
  document.head.appendChild(style);
})();

(function () {

const KEY = "alphax_entry_consent";
const ANALYTICS_KEY = "alphax_analytics_consent";
const MAX_AGE = 30 * 24 * 60 * 60 * 1000;
const GA_ID = "G-XXXXXXXXXX";

const saved = localStorage.getItem(KEY);

if (saved && (Date.now() - Number(saved) < MAX_AGE)) {
    const analyticsConsent = localStorage.getItem(ANALYTICS_KEY);
    if (analyticsConsent === "granted") {
        loadGoogleAnalytics();
    }
    return;
}

const style = document.createElement("style");

style.innerHTML = `
#age-overlay{
position:fixed;
inset:0;
background:rgba(0,0,0,0.85);
backdrop-filter:blur(8px);
display:flex;
align-items:center;
justify-content:center;
z-index:999999;
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Arial;
}

#age-box{
background:#0f0f12;
border:1px solid rgba(255,255,255,0.08);
border-radius:20px;
padding:40px 34px;
max-width:520px;
width:90%;
color:white;
box-shadow:0 25px 80px rgba(0,0,0,0.6);
text-align:center;
}

#age-box h2{
margin-top:0;
font-size:32px;
margin-bottom:18px;
}

#age-text{
color:#cfcfd6;
line-height:1.6;
margin-bottom:28px;
}

#checks{
text-align:left;
margin-bottom:28px;
}

#checks label{
display:block;
margin:12px 0;
font-size:15px;
color:#e4e4ea;
cursor:pointer;
line-height:1.5;
}

#checks input{
margin-right:10px;
}

#optional-title{
margin-top:22px;
margin-bottom:8px;
font-size:13px;
font-weight:700;
letter-spacing:0.04em;
text-transform:uppercase;
color:#9ca3af;
}

#buttons{
display:flex;
gap:14px;
justify-content:center;
margin-top:10px;
}

#enter{
background:#34d27a;
color:#05110b;
border:none;
padding:14px 26px;
border-radius:10px;
font-size:16px;
font-weight:700;
cursor:pointer;
opacity:0.4;
}

#enter.active{
opacity:1;
}

#exit{
background:#e74c3c;
border:none;
color:white;
padding:14px 22px;
border-radius:10px;
font-size:16px;
cursor:pointer;
}

#links{
margin-top:22px;
font-size:13px;
opacity:0.8;
}

#links a{
color:#bbb;
margin:0 10px;
text-decoration:none;
}

#links a:hover{
color:white;
}
`;

document.head.appendChild(style);

const overlay = document.createElement("div");
overlay.id = "age-overlay";

overlay.innerHTML = `
<div id="age-box">

<h2>18+ Access</h2>

<div id="age-text">
This website contains adult educational content.<br>
You must confirm the following to enter.
</div>

<div id="checks">

<label>
<input type="checkbox" id="c1">
I confirm that I am at least 18 years old
</label>

<label>
<input type="checkbox" id="c2">
I agree to the Terms & Conditions
</label>

<label>
<input type="checkbox" id="c3">
I agree to the Privacy Policy
</label>

<div id="optional-title">Optional</div>

<label>
<input type="checkbox" id="c4">
Allow anonymous analytics to help improve the website
</label>

</div>

<div id="buttons">
<button id="enter">Enter</button>
<button id="exit">Exit</button>
</div>

<div id="links">
<a href="/index.php?title=Terms_and_Conditions">T&amp;C</a>
<a href="/index.php?title=Datenschutz">Privacy Policy</a>
</div>

</div>
`;

document.body.appendChild(overlay);

const c1 = document.getElementById("c1");
const c2 = document.getElementById("c2");
const c3 = document.getElementById("c3");
const c4 = document.getElementById("c4");
const enter = document.getElementById("enter");

function checkAll(){
    if (c1.checked && c2.checked && c3.checked) {
        enter.classList.add("active");
        enter.disabled = false;
    } else {
        enter.classList.remove("active");
        enter.disabled = true;
    }
}

c1.onchange = checkAll;
c2.onchange = checkAll;
c3.onchange = checkAll;

enter.disabled = true;

function loadGoogleAnalytics() {
    if (window.__gaLoaded) return;
    window.__gaLoaded = true;

    window.dataLayer = window.dataLayer || [];

    function gtag() {
        dataLayer.push(arguments);
    }

    window.gtag = gtag;

    gtag("consent", "default", {
        analytics_storage: "granted"
    });

    const script = document.createElement("script");
    script.async = true;
    script.src = "https://www.googletagmanager.com/gtag/js?id=" + encodeURIComponent(GA_ID);
    document.head.appendChild(script);

    gtag("js", new Date());
    gtag("config", GA_ID, {
        anonymize_ip: true
    });
}

enter.onclick = function(){
    localStorage.setItem(KEY, String(Date.now()));

    if (c4.checked) {
        localStorage.setItem(ANALYTICS_KEY, "granted");
        loadGoogleAnalytics();
    } else {
        localStorage.setItem(ANALYTICS_KEY, "denied");
    }

    overlay.remove();
};

document.getElementById("exit").onclick = function(){
    window.location.href = "https://google.com";
};

})();


/* ================================================ */
/* ADMIN CONTROL PANEL - AlphaX Wiki                */
/* ================================================ */
(function() {
  if (mw.config.get('wgPageName') !== 'AlphaX:Admin_Control_Panel') return;

  mw.loader.using(['mediawiki.api'], function() {

    var css = [
      '#axcp-root *{box-sizing:border-box}',
      '#axcp-root{font-family:system-ui,-apple-system,sans-serif;background:#0D0D0D;color:#fff;margin:-8px -20px;padding:0;border-radius:12px;overflow:hidden}',
      '.axcp-hdr{background:#1A1A1A;border-bottom:1px solid #2E2E2E;padding:24px 28px 20px}',
      '.axcp-ttl{font-size:22px;font-weight:700;color:#fff;margin-bottom:3px}',
      '.axcp-sub{font-size:11px;color:#555;text-transform:uppercase;letter-spacing:.1em}',
      '.axcp-sr{display:flex;gap:12px;margin:16px 0 0;flex-wrap:wrap}',
      '.axcp-sc{background:#0D0D0D;border:1px solid #2E2E2E;border-radius:12px;padding:14px 18px;flex:1;min-width:90px}',
      '.axcp-sv{font-size:24px;font-weight:700;color:#FF6600;line-height:1}',
      '.axcp-sl{font-size:10px;color:#555;text-transform:uppercase;letter-spacing:.08em;margin-top:4px}',
      '.axcp-ctrl{display:flex;gap:10px;padding:12px 28px;background:#111;border-bottom:1px solid #2E2E2E;flex-wrap:wrap;align-items:center}',
      '.axcp-inp{flex:1;min-width:160px;background:#1A1A1A;border:1px solid #2E2E2E;border-radius:8px;padding:8px 13px;color:#fff;font-size:13px;outline:none}',
      '.axcp-inp:focus{border-color:rgba(255,102,0,.5)}',
      '.axcp-sel{background:#1A1A1A;border:1px solid #2E2E2E;border-radius:8px;padding:8px 11px;color:#fff;font-size:12px;outline:none;cursor:pointer;max-width:200px}',
      '.axcp-sel:focus{border-color:rgba(255,102,0,.5)}',
      '.axcp-cnt{background:rgba(255,102,0,.12);border:1px solid rgba(255,102,0,.3);color:#FF6600;border-radius:6px;padding:4px 12px;font-size:12px;font-weight:600;white-space:nowrap}',
      '.axcp-rbtn{background:linear-gradient(135deg,#FF6600,#FF8533);border:none;border-radius:8px;color:#fff;padding:9px 16px;font-size:12px;font-weight:700;cursor:pointer}',
      '.axcp-rbtn:hover{opacity:.85}',
      '.axcp-tw{overflow-x:auto;padding:0 28px 32px;background:#0D0D0D}',
      'table.axcp-t{width:100%;border-collapse:collapse;margin-top:14px;font-size:13px}',
      'table.axcp-t thead th{background:#111;color:#555;text-transform:uppercase;letter-spacing:.07em;font-size:10px;font-weight:600;padding:10px 12px;border-bottom:1px solid #2E2E2E;cursor:pointer;white-space:nowrap;user-select:none}',
      'table.axcp-t thead th:hover{color:#FF6600}',
      'table.axcp-t thead th.sa::after{content:" u2191";color:#FF6600}',
      'table.axcp-t thead th.sd::after{content:" u2193";color:#FF6600}',
      'table.axcp-t td{padding:8px 12px;border-bottom:1px solid rgba(255,255,255,.03);vertical-align:middle}',
      'table.axcp-t tr:hover td{background:rgba(255,102,0,.04)}',
      'table.axcp-t tr:last-child td{border-bottom:none}',
      '.axcp-al{color:#fff;text-decoration:none;font-weight:500;font-size:13px}',
      '.axcp-al:hover{color:#FF6600}',
      '.axcp-cp{display:inline-block;padding:2px 9px;border-radius:20px;font-size:11px;font-weight:600;white-space:nowrap}',
      '.axcp-st{display:inline-block;padding:2px 9px;border-radius:20px;font-size:11px;font-weight:600}',
      '.s-stu{background:rgba(255,60,60,.15);color:#ff7070;border:1px solid rgba(255,60,60,.25)}',
      '.s-sho{background:rgba(255,166,0,.15);color:#ffb020;border:1px solid rgba(255,166,0,.25)}',
      '.s-med{background:rgba(61,220,132,.15);color:#3ddc84;border:1px solid rgba(61,220,132,.25)}',
      '.s-lon{background:rgba(100,160,255,.15);color:#78b0ff;border:1px solid rgba(100,160,255,.25)}',
      '.axcp-n{text-align:right;font-variant-numeric:tabular-nums;color:#777}',
      '.axcp-bw{background:#1A1A1A;border-radius:3px;height:5px;width:60px;display:inline-block;vertical-align:middle;margin-left:6px;overflow:hidden}',
      '.axcp-bf{height:5px;border-radius:3px;background:linear-gradient(to right,#FF6600,#FF8533)}',
      '.axcp-load{text-align:center;padding:80px 40px;color:#555;font-size:15px;background:#0D0D0D}',
      '.axcp-prog{color:#FF6600;font-size:13px;margin-top:10px}',
      '.axcp-z{color:#333}',
      '.axcp-hi{color:#FF6600;font-weight:600}',
      '.axcp-wt{color:#3ddc84;font-weight:600}'
    ].join('');

    var sEl = document.createElement('style');
    sEl.textContent = css;
    document.head.appendChild(sEl);

    var contentEl = document.querySelector('#mw-content-text .mw-parser-output') || document.getElementById('mw-content-text');
    contentEl.innerHTML = '<div id="axcp-root"><div class="axcp-load"><div style="font-size:28px;margin-bottom:14px">&#9881;</div><div>Loading article database...</div><div class="axcp-prog" id="axcp-prog">Fetching page list...</div></div></div>';

    var root = document.getElementById('axcp-root');
    var allData = [];
    var sortCol = 'title', sortDir = 1, filterText = '', filterCat = '', filterSt = '';

    var CC = {
      'Sexual Health':                       'rgba(255,100,100,.18)|#ff8080',
      'Dating, Sex & Relationships':         'rgba(255,182,60,.18)|#ffb83c',
      'Kink & BDSM':                         'rgba(180,80,255,.18)|#c864ff',
      'Culture, History & Politics':         'rgba(80,140,255,.18)|#6496ff',
      'Fashion & Visual Signaling':          'rgba(255,220,60,.18)|#ffdc3c',
      'Community & Identity':                'rgba(61,220,132,.18)|#3ddc84',
      'Drugs, Party Culture & Harm Reduction':'rgba(255,120,40,.18)|#ff8028',
      'Life Planning':                       'rgba(80,200,255,.18)|#50c8ff'
    };
    var KCATS = Object.keys(CC);

    function catSty(cat) {
      var s = CC[cat] || 'rgba(120,120,120,.18)|#888', p = s.split('|');
      return 'background:'+p[0]+';color:'+p[1]+';border:1px solid '+p[1]+';';
    }
    function wSt(w) {
      if (w < 150)  return ['stu','Stub'];
      if (w < 500)  return ['sho','Short'];
      if (w < 1500) return ['med','Medium'];
      return ['lon','Long'];
    }
    function fDate(iso) {
      if (!iso) return '<span class="axcp-z">-</span>';
      var d = new Date(iso);
      return d.toLocaleDateString('en-GB',{day:'2-digit',month:'short',year:'numeric'});
    }
    function fKb(b) {
      if (!b) return '<span class="axcp-z">-</span>';
      return (b/1024).toFixed(1)+' <span style="color:#444;font-size:10px">KB</span>';
    }
    function esc(s) {
      return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
    }
    function setP(msg) { var el = document.getElementById('axcp-prog'); if (el) el.textContent = msg; }

    function render(data) {
      var fd = data.filter(function(r) {
        var mt = !filterText || r.title.toLowerCase().indexOf(filterText)>=0 || (r.cat||'').toLowerCase().indexOf(filterText)>=0;
        var mc = !filterCat || r.cat === filterCat;
        var ms = !filterSt || wSt(r.words||0)[0] === filterSt;
        return mt && mc && ms;
      });
      fd.sort(function(a,b) {
        var av=a[sortCol]||0, bv=b[sortCol]||0;
        if (typeof av==='number'&&typeof bv==='number') return sortDir*(av-bv);
        return sortDir*String(av).localeCompare(String(bv));
      });
      var mxW = Math.max.apply(null,[1].concat(fd.map(function(r){return r.words||0;})));
      var totW = fd.reduce(function(s,r){return s+(r.words||0);},0);
      var avgW = fd.length ? Math.round(totW/fd.length) : 0;
      var totR = fd.reduce(function(s,r){return s+(r.revisions||0);},0);
      var nocat = fd.filter(function(r){return !r.cat;}).length;
      var cats={};
      data.forEach(function(r){if(r.cat)cats[r.cat]=1;});
      var catList=Object.keys(cats).sort();

      var h='';
      h+='<div class="axcp-hdr">';
      h+='<div class="axcp-ttl">&#9881; Admin Control Panel</div>';
      h+='<div class="axcp-sub">AlphaX Wiki &bull; Article Database &bull; Live Data</div>';
      h+='<div class="axcp-sr">';
      h+='<div class="axcp-sc"><div class="axcp-sv">'+fd.length+'</div><div class="axcp-sl">Articles</div></div>';
      h+='<div class="axcp-sc"><div class="axcp-sv">'+avgW.toLocaleString()+'</div><div class="axcp-sl">Avg Words</div></div>';
      h+='<div class="axcp-sc"><div class="axcp-sv">'+totW.toLocaleString()+'</div><div class="axcp-sl">Total Words</div></div>';
      h+='<div class="axcp-sc"><div class="axcp-sv">'+totR.toLocaleString()+'</div><div class="axcp-sl">Total Revisions</div></div>';
      h+='<div class="axcp-sc"><div class="axcp-sv">'+catList.length+'</div><div class="axcp-sl">Categories</div></div>';
      h+='<div class="axcp-sc"><div class="axcp-sv" style="color:'+(nocat>0?'#ff7070':'#3ddc84')+'">'+nocat+'</div><div class="axcp-sl">Uncategorised</div></div>';
      h+='</div></div>';
      h+='<div class="axcp-ctrl">';
      h+='<input class="axcp-inp" id="axcps" type="text" placeholder="Search articles or categories..." value="'+esc(filterText)+'">';
      h+='<select class="axcp-sel" id="axcpc"><option value="">All Categories</option>';
      catList.forEach(function(c){h+='<option value="'+esc(c)+'"'+(filterCat===c?' selected':'')+'>'+esc(c)+'</option>';});
      h+='</select>';
      h+='<select class="axcp-sel" id="axcpf"><option value="">All Statuses</option>';
      [['stu','Stub'],['sho','Short'],['med','Medium'],['lon','Long']].forEach(function(s){
        h+='<option value="'+s[0]+'"'+(filterSt===s[0]?' selected':'')+'>'+s[1]+'</option>';
      });
      h+='</select>';
      h+='<span class="axcp-cnt">'+fd.length+' articles</span>';
      h+='<button class="axcp-rbtn" id="axcpr">&#8635; Refresh Data</button>';
      h+='</div>';
      h+='<div class="axcp-tw"><table class="axcp-t"><thead><tr>';
      [{k:'title',l:'Article'},{k:'cat',l:'Category'},{k:'subcat',l:'Subcategory'},
       {k:'words',l:'Words'},{k:'size',l:'Size (KB)'},{k:'status',l:'Status'},
       {k:'touched',l:'Last Edited'},{k:'revisions',l:'Revisions'},
       {k:'watchers',l:'Watchers'},{k:'links',l:'Inbound Links'}
      ].forEach(function(c){
        var cl=sortCol===c.k?(sortDir===1?'sa':'sd'):'';
        h+='<th class="'+cl+'" data-col="'+c.k+'">'+c.l+'</th>';
      });
      h+='</tr></thead><tbody>';
      if (!fd.length) {
        h+='<tr><td colspan="10" style="text-align:center;padding:50px;color:#333">No articles match your filters.</td></tr>';
      }
      fd.forEach(function(r){
        var st=wSt(r.words||0);
        var bp=mxW>0?Math.round(((r.words||0)/mxW)*60):0;
        h+='<tr>';
        h+='<td><a class="axcp-al" href="/wiki/'+encodeURIComponent((r.title||'').replace(/ /g,'_'))+'" target="_blank">'+esc(r.title)+'</a></td>';
        h+='<td>'+(r.cat?'<span class="axcp-cp" style="'+catSty(r.cat)+'">'+esc(r.cat)+'</span>':'<span class="axcp-z">-</span>')+'</td>';
        h+='<td style="color:#555;font-size:12px">'+esc(r.subcat||'-')+'</td>';
        h+='<td class="axcp-n">'+(r.words||0).toLocaleString()+'<span class="axcp-bw"><span class="axcp-bf" style="width:'+bp+'px"></span></span></td>';
        h+='<td class="axcp-n">'+fKb(r.size)+'</td>';
        h+='<td><span class="axcp-st s-'+st[0]+'">'+st[1]+'</span></td>';
        h+='<td style="color:#555;white-space:nowrap;font-size:12px">'+fDate(r.touched)+'</td>';
        h+='<td class="axcp-n '+(r.revisions>20?'axcp-hi':'')+'">'+(r.revisions||0)+'</td>';
        h+='<td class="axcp-n '+(r.watchers>0?'axcp-wt':'axcp-z')+'">'+(r.watchers>0?'&#128065; '+r.watchers:'-')+'</td>';
        h+='<td class="axcp-n '+(r.links>5?'axcp-hi':'')+'">'+(r.links||0)+'</td>';
        h+='</tr>';
      });
      h+='</tbody></table></div>';
      root.innerHTML=h;

      var si=document.getElementById('axcps');
      if(si)si.addEventListener('input',function(){filterText=this.value.toLowerCase();render(allData);});
      var ci=document.getElementById('axcpc');
      if(ci)ci.addEventListener('change',function(){filterCat=this.value;render(allData);});
      var fi=document.getElementById('axcpf');
      if(fi)fi.addEventListener('change',function(){filterSt=this.value;render(allData);});
      var ri=document.getElementById('axcpr');
      if(ri)ri.addEventListener('click',function(){allData=[];loadData();});
      document.querySelectorAll('table.axcp-t thead th').forEach(function(th){
        th.addEventListener('click',function(){
          var col=this.getAttribute('data-col');
          if(sortCol===col){sortDir*=-1;}else{sortCol=col;sortDir=1;}
          render(allData);
        });
      });
    }

    function apiFetch(url) {
      return fetch(url).then(function(r){return r.json();});
    }

    function loadData() {
      root.innerHTML='<div class="axcp-load"><div style="font-size:28px;margin-bottom:14px">&#9881;</div><div>Loading article database...</div><div class="axcp-prog" id="axcp-prog">Step 1/4 -- Fetching page list...</div></div>';
      var pages=[], map={}, ids=[];

      function fetchPages(apc) {
        var u='/api.php?action=query&list=allpages&apnamespace=0&aplimit=500&format=json';
        if(apc) u+='&apcontinue='+encodeURIComponent(apc);
        return apiFetch(u).then(function(d){
          if(!d||!d.query)return;
          pages=pages.concat(d.query.allpages||[]);
          var cont=d.continue&&d.continue.apcontinue;
          if(cont)return fetchPages(cont);
        });
      }

      // Step 2: info + categories (no revision params to avoid conflicts)
      function fetchInfoChunk(i) {
        if(i>=ids.length) return Promise.resolve();
        var chunk=ids.slice(i,i+50).join('|');
        return apiFetch('/api.php?action=query&pageids='+chunk+'&prop=info|categories&inprop=watchers&cllimit=15&format=json').then(function(d){
          if(!d||!d.query||!d.query.pages){return fetchInfoChunk(i+50);}
          Object.keys(d.query.pages).forEach(function(pid){
            var pg=d.query.pages[pid]; if(!map[pid])return;
            map[pid].size=pg.length||0;
            map[pid].touched=pg.touched||'';
            map[pid].watchers=pg.watchers!=null?Number(pg.watchers):0;
            if(pg.categories){pg.categories.forEach(function(c){
              var cn=c.title.replace('Category:','');
              if(KCATS.indexOf(cn)>=0){if(!map[pid].cat)map[pid].cat=cn;}
              else if(cn.length<60&&cn.toLowerCase().indexOf('stub')<0){if(!map[pid].subcat)map[pid].subcat=cn;}
            });}
          });
          return fetchInfoChunk(i+50);
        });
      }

      // Step 3: revision counts
      function fetchRevChunk(i) {
        if(i>=ids.length) return Promise.resolve();
        var chunk=ids.slice(i,i+20).join('|');
        return apiFetch('/api.php?action=query&pageids='+chunk+'&prop=revisions&rvprop=ids&rvlimit=max&format=json').then(function(d){
          if(!d||!d.query||!d.query.pages){return fetchRevChunk(i+20);}
          Object.keys(d.query.pages).forEach(function(pid){
            var pg=d.query.pages[pid]; if(!map[pid])return;
            map[pid].revisions=(pg.revisions||[]).length;
          });
          return fetchRevChunk(i+20);
        });
      }

      // Step 4: word counts + inbound links
      function fetchContentChunk(i) {
        if(i>=ids.length) return Promise.resolve();
        var chunk=ids.slice(i,i+8).join('|');
        return apiFetch('/api.php?action=query&pageids='+chunk+'&prop=revisions|linkshere&rvprop=content&rvslots=main&lhnamespace=0&lhlimit=max&format=json').then(function(d){
          if(!d||!d.query||!d.query.pages){return fetchContentChunk(i+8);}
          Object.keys(d.query.pages).forEach(function(pid){
            var pg=d.query.pages[pid]; if(!map[pid])return;
            map[pid].links=(pg.linkshere||[]).length;
            if(!pg.revisions||!pg.revisions[0])return;
            if(!pg.revisions||!pg.revisions[0])return;
            var slot=pg.revisions[0].slots?pg.revisions[0].slots.main:pg.revisions[0];
            var raw=slot['*']||slot.content||'';
            // Remove template names but keep parameter text
            var c=raw.replace(/\{\{[A-Za-z][^|\}]*\|?/g,'').replace(/\}\}/g,' ');
            c=c.replace(/\[\[File:[^\]]+\]\]/gi,' ');
            c=c.replace(/\[\[(?:[^\]|]+\|)?([^\]]+)\]\]/g,'$1');
            c=c.replace(/<[^>]+>/g,' ').replace(/<!--[^>]*-->/g,' ');
            c=c.replace(/={2,}[^=]+=={2,}/g,' ');
            c=c.replace(/[|!=*#;:{}\/\[\]]/g,' ');
            c=c.replace(/\s+/g,' ').trim();
            map[pid].words=c?c.split(/\s+/).filter(function(w){return w.length>2;}).length:0;
          });
          return fetchContentChunk(i+8);
        });
      }

      fetchPages(null).then(function(){
        setP('Step 2/4 -- Fetching page info & categories for '+pages.length+' articles...');
        pages.forEach(function(p){map[p.pageid]={title:p.title,cat:'',subcat:'',words:0,size:0,touched:'',revisions:0,watchers:0,links:0};});
        ids=pages.map(function(p){return p.pageid;});
        return fetchInfoChunk(0);
      }).then(function(){
        setP('Step 3/4 -- Counting revisions...');
        return fetchRevChunk(0);
      }).then(function(){
        setP('Step 4/4 -- Calculating word counts & inbound links...');
        return fetchContentChunk(0);
      }).then(function(){
        allData=Object.values(map);
        render(allData);
      }).catch(function(e){
        root.innerHTML='<div class="axcp-load" style="color:#ff7070">&#9888; Error: '+e.message+'</div>';
      });
    }

    loadData();
  });
})();


// Category Grid Component - background images for ax-cat-grid
(function() {
  // Map of card ID -> background image URL and fallback gradient
  var catImages = {
    'ax-cat-img-1': { url: 'https://alphax.wiki/images/4/43/Sexual_Health_Hero.jpg', pos: 'center center' },
    'ax-cat-img-2': { url: 'https://alphax.wiki/images/e/e5/Kink_BDSM_Hero.png', pos: 'center center' },
    'ax-cat-img-3': { gradient: 'linear-gradient(135deg, #0D1B2A 0%, #1B3A4B 40%, #0D2B3E 100%)' },
    'ax-cat-img-4': { gradient: 'linear-gradient(135deg, #1A0D0D 0%, #3A1A0D 50%, #2B1A0D 100%)' },
    'ax-cat-img-5': { url: 'https://alphax.wiki/images/0/0b/Dating_Sex_Relationships_Hero.png', pos: 'center 30%' },
    'ax-cat-img-6': { gradient: 'linear-gradient(135deg, #1A0D1A 0%, #2E0D3A 50%, #1A0D2B 100%)' },
    'ax-cat-img-7': { url: 'https://alphax.wiki/images/2/25/Culture_History_Politics_Hero.png', pos: 'center center' },
    'ax-cat-img-8': { gradient: 'linear-gradient(135deg, #0D1A0D 0%, #0D2B1A 50%, #0D1A2B 100%)' }
  };

  function applyCatImages() {
    Object.keys(catImages).forEach(function(id) {
      var el = document.getElementById(id);
      if (!el) return;
      var cfg = catImages[id];
      if (cfg.url) {
        el.style.backgroundImage = 'url("' + cfg.url + '")';
        el.style.backgroundSize = 'cover';
        el.style.backgroundPosition = cfg.pos || 'center center';
      } else if (cfg.gradient) {
        el.style.backgroundImage = cfg.gradient;
      }
    });
  }

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


// Add Impressum link to footer places row
(function() {
  function addImpressumToFooter() {
    // Find the footer-places list
    var footerPlaces = document.getElementById('footer-places');
    if (!footerPlaces) return;
    // Check if Impressum link already exists
    if (document.getElementById('footer-places-impressum')) return;
    // Create the new list item
    var li = document.createElement('li');
    li.id = 'footer-places-impressum';
    var a = document.createElement('a');
    a.href = '/Impressum';
    a.textContent = 'Impressum';
    li.appendChild(a);
    footerPlaces.appendChild(li);
  }
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', addImpressumToFooter);
  } else {
    addImpressumToFooter();
  }
})();



// ===== Permanent Favicon =====
(function() {
  document.querySelectorAll('link[rel="icon"], link[rel="shortcut icon"]').forEach(function(el) {
    el.parentNode.removeChild(el);
  });
  var link = document.createElement('link');
  link.rel = 'icon';
  link.type = 'image/png';
  link.href = 'https://alphax.wiki/images/2/26/Favicon.png';
  document.head.appendChild(link);
})();


// =====================================================
// Users by Country Counter - Admin Page Widget
// =====================================================
(function() {
  'use strict';
  if (mw.config.get('wgPageName') !== 'User:Admin') return;

  var langToCountry = {
    'en': '🇺🇸 English-speaking', 'de': '🇩🇪 Germany / German-speaking',
    'fr': '🇫🇷 France / French-speaking', 'es': '🇪🇸 Spain / Spanish-speaking',
    'pt': '🇵🇹 Portugal / Portuguese-speaking', 'it': '🇮🇹 Italy / Italian-speaking',
    'nl': '🇳🇱 Netherlands / Dutch-speaking', 'pl': '🇵🇱 Poland / Polish-speaking',
    'ru': '🇷🇺 Russia / Russian-speaking', 'ja': '🇯🇵 Japan',
    'zh': '🇨🇳 China / Chinese-speaking', 'ar': '🇸🇦 Arabic-speaking',
    'tr': '🇹🇷 Turkey / Turkish-speaking', 'sv': '🇸🇪 Sweden / Swedish-speaking',
    'da': '🇩🇰 Denmark / Danish-speaking', 'fi': '🇫🇮 Finland / Finnish-speaking',
    'nb': '🇳🇴 Norway / Norwegian-speaking', 'cs': '🇨🇿 Czech Republic',
    'hu': '🇭🇺 Hungary', 'ro': '🇷🇴 Romania', 'el': '🇬🇷 Greece / Greek-speaking',
    'ko': '🇰🇷 Korea / Korean-speaking', 'uk': '🇺🇦 Ukraine / Ukrainian-speaking',
    'he': '🇮🇱 Israel / Hebrew-speaking', 'id': '🇮🇩 Indonesia',
    'vi': '🇻🇳 Vietnam', 'th': '🇹🇭 Thailand', 'hi': '🇮🇳 India / Hindi-speaking',
    'bn': '🇧🇩 Bangladesh / Bengali-speaking', 'fa': '🇮🇷 Iran / Persian-speaking'
  };

  function renderResults(langCounts, totalUsers) {
    var container = document.getElementById('country-user-counter');
    if (!container) return;

    var sorted = Object.keys(langCounts)
      .filter(function(l) { return langCounts[l] > 0; })
      .sort(function(a, b) { return langCounts[b] - langCounts[a]; });

    var html = '<style>';
    html += '#cuc-wrap{font-family:sans-serif}';
    html += '#cuc-stats{display:flex;gap:20px;margin-bottom:14px;flex-wrap:wrap}';
    html += '.cuc-stat-card{background:#222;border:1px solid #444;border-radius:6px;padding:10px 18px;min-width:120px;text-align:center}';
    html += '.cuc-stat-num{font-size:1.8em;font-weight:bold;color:#e07000}';
    html += '.cuc-stat-lbl{font-size:0.8em;color:#aaa;margin-top:2px}';
    html += '#cuc-table{width:100%;border-collapse:collapse;margin-top:4px}';
    html += '#cuc-table th{background:#2a2a2a;color:#e07000;padding:8px 12px;text-align:left;border:1px solid #444;font-size:0.9em}';
    html += '#cuc-table td{padding:7px 12px;border:1px solid #2e2e2e;color:#ddd;font-size:0.9em}';
    html += '#cuc-table tr:nth-child(even) td{background:#1c1c1c}';
    html += '#cuc-table tr:hover td{background:#252525}';
    html += '.cuc-bar-wrap{background:#111;border-radius:3px;height:14px;overflow:hidden}';
    html += '.cuc-bar{height:14px;background:linear-gradient(90deg,#e07000,#ff9a00);border-radius:3px;transition:width 0.4s ease}';
    html += '.cuc-no-data{background:#1a1a1a;border:1px dashed #444;border-radius:6px;padding:16px;color:#aaa;line-height:1.6}';
    html += '.cuc-no-data code{background:#222;padding:2px 6px;border-radius:3px;color:#e07000;font-size:0.9em}';
    html += '#cuc-timestamp{color:#555;font-size:0.8em;margin-top:10px}';
    html += '</style>';

    html += '<div id="cuc-wrap">';

    // Stats cards
    html += '<div id="cuc-stats">';
    html += '<div class="cuc-stat-card"><div class="cuc-stat-num">' + totalUsers + '</div><div class="cuc-stat-lbl">Total Users</div></div>';
    html += '<div class="cuc-stat-card"><div class="cuc-stat-num">' + sorted.length + '</div><div class="cuc-stat-lbl">Countries / Regions</div></div>';
    var locatedUsers = sorted.reduce(function(sum, l) { return sum + langCounts[l]; }, 0);
    html += '<div class="cuc-stat-card"><div class="cuc-stat-num">' + locatedUsers + '</div><div class="cuc-stat-lbl">Users with Location</div></div>';
    html += '</div>';

    if (sorted.length === 0) {
      html += '<div class="cuc-no-data">';
      html += '<strong>📍 No country data available yet.</strong><br>';
      html += 'This counter tracks users by country using the <strong>Babel extension</strong> language categories.<br>';
      html += 'Users can declare their language/country by adding babel tags to their user page:<br><br>';
      html += '<code>{{#babel:en|de|fr}}</code><br><br>';
      html += 'Once users add babel tags, this counter will automatically display their country distribution.<br>';
      html += 'Currently tracking <strong>' + totalUsers + ' registered users</strong> on this wiki.';
      html += '</div>';
    } else {
      var maxCount = langCounts[sorted[0]] || 1;
      html += '<table id="cuc-table"><thead><tr>';
      html += '<th style="width:40px">#</th>';
      html += '<th>Country / Region</th>';
      html += '<th style="width:70px;text-align:center">Users</th>';
      html += '<th style="width:200px">Distribution</th>';
      html += '</tr></thead><tbody>';
      sorted.forEach(function(lang, idx) {
        var count = langCounts[lang];
        var label = langToCountry[lang] || ('🌐 ' + lang.toUpperCase());
        var pct = Math.round((count / locatedUsers) * 100);
        var barWidth = Math.round((count / maxCount) * 180);
        html += '<tr>';
        html += '<td style="color:#666;text-align:center">' + (idx + 1) + '</td>';
        html += '<td>' + label + '</td>';
        html += '<td style="text-align:center;font-weight:bold;color:#e07000">' + count + '</td>';
        html += '<td><div class="cuc-bar-wrap"><div class="cuc-bar" style="width:' + barWidth + 'px"></div></div></td>';
        html += '</tr>';
      });
      html += '</tbody></table>';
    }

    html += '<div id="cuc-timestamp">Last updated: ' + new Date().toLocaleString() + '</div>';
    html += '</div>';

    container.innerHTML = html;
  }

  function buildCounter() {
    var container = document.getElementById('country-user-counter');
    if (!container) return;
    container.innerHTML = '<p style="color:#aaa;font-style:italic">⏳ Loading user statistics by country...</p>';

    var allUsers = [];
    var langCounts = {};
    var langs = Object.keys(langToCountry);

    function fetchUsers(aufrom) {
      var params = { action: 'query', list: 'allusers', aulimit: 500, format: 'json' };
      if (aufrom) params.aufrom = aufrom;
      return $.getJSON(mw.util.wikiScript('api'), params).then(function(data) {
        var users = (data.query && data.query.allusers) || [];
        allUsers = allUsers.concat(users);
        if (data['continue'] && data['continue'].aufrom) return fetchUsers(data['continue'].aufrom);
      });
    }

    function fetchLangCat(lang) {
      return $.getJSON(mw.util.wikiScript('api'), {
        action: 'query', list: 'categorymembers',
        cmtitle: 'Category:User_' + lang,
        cmtype: 'page', cmnamespace: 2, cmlimit: 500, format: 'json'
      }).then(function(data) {
        var m = (data.query && data.query.categorymembers) || [];
        if (m.length > 0) langCounts[lang] = (langCounts[lang] || 0) + m.length;
      }).catch(function() {});
    }

    fetchUsers().then(function() {
      var d = $.Deferred().resolve().promise();
      langs.forEach(function(lang) { d = d.then(function() { return fetchLangCat(lang); }); });
      return d;
    }).then(function() {
      renderResults(langCounts, allUsers.length);
    }).catch(function(e) {
      container.innerHTML = '<p style="color:#f66">⚠️ Error loading data: ' + String(e) + '</p>';
    });
  }

  $(document).ready(function() { buildCounter(); });
})();
// =====================================================
// End: Users by Country Counter
// =====================================================


// ===== Article Overview Page - Category/Subcategory/Title/Words/Focus Table =====
(function() {
  if (mw.config.get('wgPageName') !== 'Article_Overview') return;

  var apiBase = mw.util.wikiScript('api');
  var allRows = [];
  var tableBody = null;
  var statusSpan = null;

  $(document).ready(function() {
    var wrapper = document.getElementById('article-overview-wrapper');
    if (!wrapper) return;

    // Build controls row with button + status
    var controls = document.createElement('div');
    controls.style.marginBottom = '14px';

    var btn = document.createElement('button');
    btn.textContent = 'Download CSV';
    btn.style.cssText = 'background-color:#e07b00;color:#fff;padding:9px 20px;border:none;border-radius:5px;font-size:14px;font-weight:bold;cursor:pointer;letter-spacing:0.3px;';
    btn.addEventListener('click', downloadCSV);
    controls.appendChild(btn);

    statusSpan = document.createElement('span');
    statusSpan.style.cssText = 'margin-left:14px;font-size:13px;color:#aaa;vertical-align:middle;';
    statusSpan.textContent = 'Loading…';
    controls.appendChild(statusSpan);
    wrapper.appendChild(controls);

    // Build table
    var table = document.createElement('table');
    table.style.cssText = 'width:100%;border-collapse:collapse;font-size:13px;';

    var thead = document.createElement('thead');
    var headerRow = document.createElement('tr');
    headerRow.style.cssText = 'background-color:#2a2a2a;color:#f0a500;text-align:left;';
    var cols = ['Category', 'Subcategory', 'Title', 'Words', 'Focus'];
    cols.forEach(function(colName, i) {
      var th = document.createElement('th');
      th.textContent = colName;
      th.style.cssText = 'padding:9px 12px;border:1px solid #444;' + (colName === 'Words' ? 'text-align:right;' : '');
      headerRow.appendChild(th);
    });
    thead.appendChild(headerRow);
    table.appendChild(thead);

    tableBody = document.createElement('tbody');
    tableBody.innerHTML = '<tr><td colspan="5" style="padding:12px;text-align:center;color:#aaa;font-style:italic;">Loading articles… please wait.</td></tr>';
    table.appendChild(tableBody);
    wrapper.appendChild(table);

    buildTable();
  });

  function getCategoryMembers(catName, cmtype, continueParam, accumulated, callback) {
    var params = {
      action: 'query', list: 'categorymembers',
      cmtitle: 'Category:' + catName,
      cmlimit: 500, cmtype: cmtype, format: 'json'
    };
    if (continueParam) params.cmcontinue = continueParam;
    $.getJSON(apiBase, params).done(function(data) {
      var members = (data.query && data.query.categorymembers) ? data.query.categorymembers : [];
      members.forEach(function(m){ accumulated.push(m); });
      if (data.continue && data.continue.cmcontinue) {
        getCategoryMembers(catName, cmtype, data.continue.cmcontinue, accumulated, callback);
      } else {
        callback(accumulated);
      }
    }).fail(function(){ callback(accumulated); });
  }

  function escHtml(str) {
    return String(str).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
  }

  function renderTable(rows) {
    if (!tableBody) return;
    if (!rows || rows.length === 0) {
      tableBody.innerHTML = '<tr><td colspan="5" style="padding:10px;text-align:center;color:#aaa;">No articles found.</td></tr>';
      return;
    }
    var html = '';
    rows.forEach(function(row, i) {
      var bg = i % 2 === 0 ? 'rgba(255,255,255,0.03)' : 'rgba(255,255,255,0.07)';
      html += '<tr style="background:' + bg + ';">';
      html += '<td style="padding:6px 10px;border:1px solid #444;">' + escHtml(row.category) + '</td>';
      html += '<td style="padding:6px 10px;border:1px solid #444;">' + escHtml(row.sub) + '</td>';
      html += '<td style="padding:6px 10px;border:1px solid #444;"><a href="' + mw.util.getUrl(row.title) + '">' + escHtml(row.title) + '</a></td>';
      html += '<td style="padding:6px 10px;border:1px solid #444;text-align:right;">' + escHtml(String(row.words)) + '</td>';
      html += '<td style="padding:6px 10px;border:1px solid #444;">' + escHtml(row.focus) + '</td>';
      html += '</tr>';
    });
    tableBody.innerHTML = html;
  }

  function buildTable() {
    if (statusSpan) statusSpan.textContent = 'Fetching categories…';

    $.getJSON(apiBase, { action:'query', list:'allcategories', aclimit:500, format:'json' })
      .done(function(data) {
        var topCats = (data.query && data.query.allcategories) ? data.query.allcategories.map(function(c){ return c['*']; }) : [];
        var rows = [];
        var totalCats = topCats.length;
        var processed = 0;

        if (totalCats === 0) {
          if (tableBody) tableBody.innerHTML = '<tr><td colspan="5" style="padding:10px;text-align:center;color:#aaa;">No categories found.</td></tr>';
          if (statusSpan) statusSpan.textContent = '';
          return;
        }

        function checkDone() {
          processed++;
          if (statusSpan) statusSpan.textContent = 'Loading… (' + processed + '/' + totalCats + ' categories)';
          if (processed === totalCats) {
            rows.sort(function(a,b){
              var ca = a.category.toLowerCase(), cb = b.category.toLowerCase();
              if (ca < cb) return -1; if (ca > cb) return 1;
              var sa = a.sub.toLowerCase(), sb = b.sub.toLowerCase();
              if (sa < sb) return -1; if (sa > sb) return 1;
              return a.title.localeCompare(b.title);
            });
            allRows = rows;
            renderTable(rows);
            if (statusSpan) statusSpan.textContent = rows.length + ' article entries loaded.';
          }
        }

        topCats.forEach(function(catName) {
          getCategoryMembers(catName, 'page|subcat', null, [], function(members) {
            var pages = members.filter(function(m){ return m.ns === 0; });
            var subcats = members.filter(function(m){ return m.ns === 14; });

            if (pages.length === 0 && subcats.length === 0) {
              checkDone(); return;
            }

            var subTotal = pages.length + subcats.length;
            var subProcessed = 0;

            function subDone() {
              subProcessed++;
              if (subProcessed >= subTotal) checkDone();
            }

            pages.forEach(function(page) {
              rows.push({ category: catName, sub: '—', title: page.title, words: 'n/a', focus: catName });
              subDone();
            });

            subcats.forEach(function(subcat) {
              var subName = subcat.title.replace('Category:','');
              getCategoryMembers(subName, 'page', null, [], function(subPages) {
                subPages.filter(function(m){ return m.ns === 0; }).forEach(function(page) {
                  rows.push({ category: catName, sub: subName, title: page.title, words: 'n/a', focus: catName });
                });
                subDone();
              });
            });
          });
        });
      })
      .fail(function() {
        if (tableBody) tableBody.innerHTML = '<tr><td colspan="5" style="padding:10px;text-align:center;color:#f55;">Error loading data. Check API access.</td></tr>';
      });
  }

  function downloadCSV() {
    if (!allRows || allRows.length === 0) {
      alert('No data to download yet. Please wait for the table to finish loading.');
      return;
    }
    var csv = 'Category,Subcategory,Title,Words,Focus\n';
    allRows.forEach(function(row) {
      csv += ['category','sub','title','words','focus'].map(function(k){
        return '"' + String(row[k]).replace(/"/g,'""') + '"';
      }).join(',') + '\n';
    });
    var blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
    var url = URL.createObjectURL(blob);
    var a = document.createElement('a');
    a.href = url; a.download = 'article-overview.csv';
    document.body.appendChild(a); a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
  }

})();


// ═══════════════════════════════════════════════════════════
// GEO DASHBOARD – Users by Country (Day / Week / Month)
// ═══════════════════════════════════════════════════════════
(function () {
  if (!document.getElementById('geo-dashboard')) return;

  var API = '/api.php';

  function isoDate(d) { return d.toISOString().slice(0, 10); }

  function startOf(period) {
    var now = new Date();
    if (period === 'day') return new Date(now.getFullYear(), now.getMonth(), now.getDate());
    if (period === 'week') { var d = new Date(now); d.setDate(d.getDate() - d.getDay()); d.setHours(0,0,0,0); return d; }
    if (period === 'month') return new Date(now.getFullYear(), now.getMonth(), 1);
    return new Date(0);
  }

  var COUNTRY_MAP = {
    'en':'🇺🇸 English / US','de':'🇩🇪 German','fr':'🇫🇷 French','es':'🇪🇸 Spanish',
    'it':'🇮🇹 Italian','pt':'🇧🇷 Portuguese','ru':'🇷🇺 Russian','ja':'🇯🇵 Japanese',
    'zh':'🇨🇳 Chinese','ko':'🇰🇷 Korean','ar':'🇸🇦 Arabic','nl':'🇳🇱 Dutch',
    'pl':'🇵🇱 Polish','sv':'🇸🇪 Swedish','no':'🇳🇴 Norwegian','da':'🇩🇰 Danish',
    'fi':'🇫🇮 Finnish','tr':'🇹🇷 Turkish','cs':'🇨🇿 Czech','hu':'🇭🇺 Hungarian',
    'ro':'🇷🇴 Romanian','uk':'🇺🇦 Ukrainian','vi':'🇻🇳 Vietnamese','th':'🇹🇭 Thai',
    'id':'🇮🇩 Indonesian','ms':'🇲🇾 Malay','he':'🇮🇱 Hebrew','fa':'🇮🇷 Persian',
    'hi':'🇮🇳 Hindi','bn':'🇧🇩 Bengali'
  };

  function fetchUsers() {
    return fetch(API + '?action=query&list=allusers&aulimit=500&format=json')
      .then(function(r) { return r.json(); })
      .then(function(d) { return d.query.allusers || []; });
  }

  function fetchChanges() {
    return fetch(API + '?action=query&list=recentchanges&rclimit=500&rcprop=user%7Ctimestamp&format=json')
      .then(function(r) { return r.json(); })
      .then(function(d) { return d.query.recentchanges || []; });
  }

  function fetchBabel(username) {
    return fetch(API + '?action=query&titles=User:' + encodeURIComponent(username) + '&prop=categories&format=json')
      .then(function(r) { return r.json(); })
      .then(function(d) {
        var pages = (d.query && d.query.pages) ? d.query.pages : {};
        var langs = [];
        Object.values(pages).forEach(function(p) {
          (p.categories || []).forEach(function(c) {
            var m = c.title.match(/Category:User ([a-z]{2,3})/i);
            if (m) langs.push(m[1].toLowerCase());
          });
        });
        return langs;
      });
  }

  function drawMap(activeLangs) {
    var svg = document.getElementById('geo-world-map');
    if (!svg) return;
    svg.innerHTML = '';

    var bg = document.createElementNS('http://www.w3.org/2000/svg','rect');
    bg.setAttribute('width','900'); bg.setAttribute('height','440'); bg.setAttribute('fill','#0d1f33');
    svg.appendChild(bg);

    var continents = [
      'M80,100 L240,95 L255,200 L220,230 L170,235 L110,220 L75,170 Z',
      'M155,240 L220,235 L235,360 L195,385 L155,370 L138,320 Z',
      'M385,90 L480,85 L490,160 L450,175 L400,170 L378,140 Z',
      'M390,175 L460,170 L470,320 L430,345 L388,335 L375,280 Z',
      'M490,80 L750,75 L760,230 L700,250 L520,240 L485,180 Z',
      'M640,280 L740,275 L748,355 L700,368 L638,355 Z',
      'M230,50 L290,45 L295,90 L255,95 L228,82 Z'
    ];
    continents.forEach(function(d) {
      var path = document.createElementNS('http://www.w3.org/2000/svg','path');
      path.setAttribute('d', d); path.setAttribute('fill','#2a3a2a');
      path.setAttribute('stroke','#1a2a1a'); path.setAttribute('stroke-width','1');
      svg.appendChild(path);
    });

    var shapes = {
      'de':'M440,145 L455,140 L465,148 L462,165 L448,170 L438,162 Z',
      'fr':'M415,152 L430,148 L438,162 L430,175 L415,172 L408,162 Z',
      'es':'M390,170 L415,168 L415,185 L395,190 L385,182 Z',
      'it':'M450,168 L462,165 L468,180 L460,195 L450,188 L445,178 Z',
      'gb':'M418,135 L428,132 L430,145 L420,148 L414,142 Z',
      'ru':'M460,120 L560,110 L580,135 L540,145 L465,148 Z',
      'us':'M120,150 L220,148 L225,190 L200,200 L115,195 Z',
      'cn':'M620,150 L680,145 L685,175 L650,185 L615,178 Z',
      'jp':'M695,155 L708,152 L710,165 L700,168 L693,163 Z',
      'br':'M195,230 L240,225 L245,275 L215,282 L188,270 Z',
      'in':'M580,175 L615,170 L618,210 L595,218 L575,205 Z',
      'au':'M660,280 L720,275 L725,320 L690,328 L655,315 Z'
    };
    Object.keys(shapes).forEach(function(cc) {
      var isActive = activeLangs.some(function(l) { return l.startsWith(cc); });
      var path = document.createElementNS('http://www.w3.org/2000/svg','path');
      path.setAttribute('d', shapes[cc]);
      path.setAttribute('fill', isActive ? '#ff6600' : '#3a4a3a');
      path.setAttribute('stroke','#555'); path.setAttribute('stroke-width','1');
      path.setAttribute('opacity', isActive ? '0.9' : '0.5');
      svg.appendChild(path);
    });

    var labels = [{x:160,y:175,t:'North America'},{x:450,y:130,t:'Europe'},{x:620,y:165,t:'Asia'},
                  {x:430,y:265,t:'Africa'},{x:192,y:305,t:'South America'},{x:693,y:325,t:'Oceania'}];
    labels.forEach(function(loc) {
      var text = document.createElementNS('http://www.w3.org/2000/svg','text');
      text.setAttribute('x',loc.x); text.setAttribute('y',loc.y);
      text.setAttribute('fill','#444'); text.setAttribute('font-size','10');
      text.setAttribute('font-family','sans-serif'); text.setAttribute('text-anchor','middle');
      text.textContent = loc.t; svg.appendChild(text);
    });
  }

  function renderActivityBars(daily) {
    var wrap = document.getElementById('geo-activity-bars');
    var labelWrap = document.getElementById('geo-activity-labels');
    if (!wrap) return;
    var days = [];
    for (var i = 29; i >= 0; i--) {
      var d = new Date(); d.setDate(d.getDate() - i); days.push(isoDate(d));
    }
    var max = Math.max.apply(null, days.map(function(d){ return daily[d]||0; }).concat([1]));
    var bHtml = '', lHtml = '';
    days.forEach(function(day, idx) {
      var val = daily[day] || 0;
      var h = Math.max(3, Math.round((val/max)*70));
      var isToday = idx === 29;
      var col = isToday ? '#ff6600' : (val > 0 ? '#cc5500' : '#222');
      bHtml += '<div title="' + day + ': ' + val + ' edits" style="flex:1;background:' + col + ';height:' + h + 'px;border-radius:2px 2px 0 0;min-width:4px;"></div>';
      lHtml += '<div style="flex:1;text-align:center;white-space:nowrap;">' + (idx%5===0||isToday ? day.slice(5) : '') + '</div>';
    });
    wrap.innerHTML = bHtml; labelWrap.innerHTML = lHtml;
  }

  function renderTable(rows, total, period) {
    var tbody = document.getElementById('geo-table-body');
    var titleEl = document.getElementById('geo-table-title');
    if (!tbody) return;
    var labels = {day:'Today', week:'This Week', month:'This Month', all:'All Time'};
    if (titleEl) titleEl.textContent = 'Users by Country – ' + (labels[period]||'All Time');
    if (!rows.length) {
      tbody.innerHTML = '<tr><td colspan="5" style="padding:20px;text-align:center;color:#555;">No activity for this period.</td></tr>';
      return;
    }
    var html = '';
    rows.forEach(function(row, i) {
      var bar = '<div style="display:inline-block;background:#ff6600;height:8px;border-radius:2px;width:' + Math.round(row.pct) + '%;min-width:2px;vertical-align:middle;"></div><span style="color:#666;font-size:0.8em;margin-left:6px;">' + row.pct.toFixed(1) + '%</span>';
      html += '<tr style="background:' + (i%2===0?'#1a1a1a':'#1e1e1e') + ';border-top:1px solid #222;">' +
        '<td style="padding:10px 16px;color:#666;">' + (i+1) + '</td>' +
        '<td style="padding:10px 16px;">' + (COUNTRY_MAP[row.lang]||('🌐 '+row.lang)) + '</td>' +
        '<td style="padding:10px 16px;text-align:right;font-weight:bold;color:#ff6600;">' + row.users + '</td>' +
        '<td style="padding:10px 16px;">' + bar + '</td>' +
        '<td style="padding:10px 16px;text-align:right;color:#aaa;">' + row.edits + '</td></tr>';
    });
    tbody.innerHTML = html;
  }

  var cachedUsers = null, cachedChanges = null, cachedBabel = {}, currentPeriod = 'day';

  function showPeriod(period) {
    currentPeriod = period;
    ['day','week','month','all'].forEach(function(p) {
      var btn = document.getElementById('geo-btn-'+p);
      if (!btn) return;
      if (p === period) {
        btn.style.background='#ff6600'; btn.style.color='#fff';
        btn.style.borderColor='#ff6600'; btn.style.fontWeight='bold';
      } else {
        btn.style.background='#333'; btn.style.color='#eee';
        btn.style.borderColor='#555'; btn.style.fontWeight='normal';
      }
    });
    if (!cachedChanges || !cachedUsers) return;
    var cutoff = period === 'all' ? '1970-01-01' : startOf(period).toISOString();
    var userEdits = {};
    cachedChanges.forEach(function(c) {
      if (c.timestamp >= cutoff) userEdits[c.user] = (userEdits[c.user]||0)+1;
    });
    var langUsers = {}, langEdits = {};
    cachedUsers.forEach(function(u) {
      var langs = cachedBabel[u.name] || [];
      if (!langs.length) langs = ['unknown'];
      var edits = userEdits[u.name] || 0;
      var lang = langs[0];
      langUsers[lang] = (langUsers[lang]||0)+1;
      langEdits[lang] = (langEdits[lang]||0)+edits;
    });
    var total = cachedUsers.length;
    var cEl = document.getElementById('geo-countries');
    if (cEl) cEl.textContent = Object.keys(langUsers).filter(function(l){return l!=='unknown';}).length;
    var rows = Object.keys(langUsers).map(function(lang) {
      return {lang:lang, users:langUsers[lang], edits:langEdits[lang]||0, pct:total>0?(langUsers[lang]/total*100):0};
    }).sort(function(a,b) {
      if (a.lang==='unknown') return 1; if (b.lang==='unknown') return -1; return b.users-a.users;
    });
    renderTable(rows, total, period);
    drawMap(rows.filter(function(r){return r.lang!=='unknown';}).map(function(r){return r.lang;}));
  }

  window.geoShowPeriod = showPeriod;

  function boot() {
    var el = document.getElementById('geo-dashboard');
    if (!el) return;

    Promise.all([fetchUsers(), fetchChanges()]).then(function(results) {
      cachedUsers = results[0]; cachedChanges = results[1];
      var now = new Date();
      var tEl = document.getElementById('geo-total');
      if (tEl) tEl.textContent = cachedUsers.length;
      var updEl = document.getElementById('geo-updated');
      if (updEl) updEl.textContent = 'Updated: ' + now.toLocaleTimeString();

      var dStart = startOf('day').toISOString();
      var wStart = startOf('week').toISOString();
      var mStart = startOf('month').toISOString();
      var aDay=new Set(), aWeek=new Set(), aMonth=new Set();
      var daily = {};
      cachedChanges.forEach(function(c) {
        if (c.timestamp >= dStart) aDay.add(c.user);
        if (c.timestamp >= wStart) aWeek.add(c.user);
        if (c.timestamp >= mStart) aMonth.add(c.user);
        var day = c.timestamp.slice(0,10);
        daily[day] = (daily[day]||0)+1;
      });
      var adEl = document.getElementById('geo-active-day');
      var awEl = document.getElementById('geo-active-week');
      var amEl = document.getElementById('geo-active-month');
      if (adEl) adEl.textContent = aDay.size;
      if (awEl) awEl.textContent = aWeek.size;
      if (amEl) amEl.textContent = aMonth.size;
      renderActivityBars(daily);

      var babelPromises = cachedUsers.map(function(u) {
        return fetchBabel(u.name).then(function(langs) { cachedBabel[u.name] = langs; });
      });
      Promise.all(babelPromises).then(function() { showPeriod('day'); });

      // wire up buttons
      ['day','week','month','all'].forEach(function(p) {
        var btn = document.getElementById('geo-btn-'+p);
        if (btn) btn.addEventListener('click', function() { showPeriod(p); });
      });
    }).catch(function(e) { console.error('GEO dashboard error', e); });
  }

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