MediaWiki:Common.js: відмінності між версіями
Admin (обговорення | внесок) Немає опису редагування Мітка: Скасовано |
Admin (обговорення | внесок) Немає опису редагування |
||
| (Не показано 19 проміжних версій цього користувача) | |||
| Рядок 2: | Рядок 2: | ||
// MediaWiki:Common.js — Mafia Closed Circle — ФІНАЛЬНА ВЕРСІЯ | // MediaWiki:Common.js — Mafia Closed Circle — ФІНАЛЬНА ВЕРСІЯ | ||
// ============================================================ | // ============================================================ | ||
// ============================================================ | |||
// FULL-WIDTH PAGE DETECTION | |||
// ============================================================ | |||
var MCC_FULLWIDTH_PAGES = [ | |||
'page-Ігри', 'page-Статистика', 'page-Перша_статистика', | |||
'page-Фіналіст', 'page-Сезони', 'page-Гравці', | |||
'page-Фундація', 'page-Призовий_фонд', 'page-Призові', | |||
'page-Титули', 'page-Період' | |||
]; | |||
function isMccFullwidth() { | |||
var bodyClasses = document.body.className; | |||
for (var i = 0; i < MCC_FULLWIDTH_PAGES.length; i++) { | |||
if (bodyClasses.indexOf(MCC_FULLWIDTH_PAGES[i]) !== -1) return true; | |||
} | |||
return false; | |||
} | |||
// ============================================================ | // ============================================================ | ||
| Рядок 66: | Рядок 85: | ||
$(this).addClass('active'); | $(this).addClass('active'); | ||
// | // If games tab is active — switch to profile first, then scroll | ||
var $gamesTab = $('.player-tab[data-tab="games"]'); | var $profileTab = $('.player-tab[data-tab="profile"], .player-tab[data-tab="info"]'); | ||
var $gamesTab = $('.player-tab[data-tab="games"]'); | |||
var gamesActive = $gamesTab.length && $gamesTab.hasClass('active'); | var gamesActive = $gamesTab.length && $gamesTab.hasClass('active'); | ||
function doScroll() { | |||
if (target === 'top') { | if (target === 'top') { | ||
$('html, body').animate({ scrollTop: 0 }, 280); | $('html, body').animate({ scrollTop: 0 }, 280); | ||
| Рядок 86: | Рядок 102: | ||
} | } | ||
} | } | ||
}, | } | ||
if (gamesActive) { | |||
// Click the profile tab to switch | |||
$profileTab.trigger('click'); | |||
// Wait for tab switch + DOM | |||
setTimeout(doScroll, 120); | |||
} else { | |||
doScroll(); | |||
} | |||
}); | }); | ||
| Рядок 288: | Рядок 313: | ||
$content.data('loaded', true); | $content.data('loaded', true); | ||
var $table = $content.find('table. | setTimeout(function () { | ||
var $table = $content.find('table').first(); | |||
if ($table.length) { | |||
$table.addClass('wikitable sortable'); | |||
try { $table.tablesorter(); } catch(e) {} | |||
} | |||
applyWinrateColors($content); | |||
applyRolePills($content); | |||
wrapWideTables($content); | |||
// Inject filters directly at top of content | |||
if (!$content.find('.mcc-games-filters').length) { | |||
buildGamesFilters($content, $table); | |||
} | |||
$(document).trigger('mcc:content-loaded'); | |||
}, 100); | |||
}, | }, | ||
error: function () { | error: function () { | ||
| Рядок 315: | Рядок 349: | ||
// ============================================================ | // ============================================================ | ||
// 7. ФІЛЬТРИ НАД ТАБЛИЦЕЮ ІГОР | // 7. ФІЛЬТРИ НАД ТАБЛИЦЕЮ ІГОР | ||
// ============================================================ | |||
// 7b. BUILD GAMES FILTERS | |||
// ============================================================ | |||
function buildGamesFilters($container, $table) { | |||
// Already added? | |||
if ($container.find('.mcc-games-filters').length) return; | |||
// Find table if not valid | |||
if (!$table || !$table.length) { | |||
$table = $container.find('table').first(); | |||
} | |||
if (!$table.length) return; | |||
// Use ALL tr rows (with or without tbody) | |||
var $rows = $table.find('tr').filter(function () { | |||
return $(this).find('td').length > 0; // only data rows | |||
}); | |||
if (!$rows.length) return; | |||
// Collect unique events from first td | |||
var events = []; | |||
$rows.each(function () { | |||
var ev = $(this).find('td').first().text().trim(); | |||
if (ev && events.indexOf(ev) === -1) events.push(ev); | |||
}); | |||
// Build event options | |||
var opts = '<option value="">Всі події</option>'; | |||
events.forEach(function (e) { | |||
opts += '<option value="' + e + '">' + e + '</option>'; | |||
}); | |||
// Build the filter bar HTML | |||
var barHTML = '<div class="mcc-games-filters">' | |||
+ '<div class="mcc-filter-group">' | |||
+ '<span class="mcc-filter-label">Подія</span>' | |||
+ '<select class="mcc-filter-select mcc-fe">' + opts + '</select>' | |||
+ '</div>' | |||
+ '<div class="mcc-filter-group">' | |||
+ '<span class="mcc-filter-label">Роль</span>' | |||
+ '<select class="mcc-filter-select mcc-fr">' | |||
+ '<option value="">Всі ролі</option>' | |||
+ '<option value="Мир">Мирний</option>' | |||
+ '<option value="Шер">Шериф</option>' | |||
+ '<option value="Маф">Мафія</option>' | |||
+ '<option value="Дон">Дон</option>' | |||
+ '</select>' | |||
+ '</div>' | |||
+ '<div class="mcc-filter-group">' | |||
+ '<span class="mcc-filter-label">Результат</span>' | |||
+ '<select class="mcc-filter-select mcc-frr">' | |||
+ '<option value="">Всі</option>' | |||
+ '<option value="В">Перемога</option>' | |||
+ '<option value="П">Поразка</option>' | |||
+ '</select>' | |||
+ '</div>' | |||
+ '<div class="mcc-filter-group mcc-duration-group">' | |||
+ '<span class="mcc-filter-label">Макс. тривалість</span>' | |||
+ '<div class="mcc-slider-wrap">' | |||
+ '<input type="range" class="mcc-duration-range mcc-dur-rng" min="20" max="75" value="75" step="1">' | |||
+ '<span class="mcc-slider-val mcc-dur-v">75:00</span>' | |||
+ '</div>' | |||
+ '</div>' | |||
+ '<span class="mcc-filter-count mcc-fcnt"></span>' | |||
+ '</div>'; | |||
// Insert at the very top of the container | |||
$container.prepend(barHTML); | |||
// Get reference to the just-added bar | |||
var $bar = $container.find('.mcc-games-filters').first(); | |||
function toMins(s) { | |||
s = (s || '').trim(); | |||
var m1 = s.match(/^(\d+):(\d+)$/); | |||
if (m1) return parseInt(m1[1]) + parseInt(m1[2]) / 60; | |||
var m2 = s.match(/(\d+)\s*[хh]\s*[вv]\s*(\d+)/); | |||
if (m2) return parseInt(m2[1]) + parseInt(m2[2]) / 60; | |||
return 999; | |||
} | |||
function doFilter() { | |||
var ev = $bar.find('.mcc-fe').val() || ''; | |||
var rol = $bar.find('.mcc-fr').val() || ''; | |||
var res = $bar.find('.mcc-frr').val() || ''; | |||
var dur = parseFloat($bar.find('.mcc-dur-rng').val()) || 75; | |||
var n = 0; | |||
$rows.each(function () { | |||
var $tds = $(this).find('td'); | |||
var evVal = $tds.eq(0).text().trim(); | |||
var rolVal = $tds.eq(1).text().trim(); | |||
var timeVal = $tds.eq(2).text().trim(); | |||
var resVal = $tds.eq(3).text().trim(); | |||
var show = (ev === '' || evVal.indexOf(ev) !== -1) | |||
&& (rol === '' || rolVal.indexOf(rol) !== -1) | |||
&& (res === '' || resVal.indexOf(res) !== -1) | |||
&& toMins(timeVal) <= dur; | |||
$(this).toggle(show); | |||
if (show) n++; | |||
}); | |||
$bar.find('.mcc-fcnt').text(n + '\u00a0ігор'); | |||
} | |||
$bar.find('.mcc-dur-rng').on('input', function () { | |||
var v = parseFloat($(this).val()); | |||
$bar.find('.mcc-dur-v').text(v + ':00'); | |||
$(this).css('background-size', ((v - 20) / 55 * 100) + '% 100%'); | |||
doFilter(); | |||
}); | |||
$bar.find('select').on('change', doFilter); | |||
$bar.find('.mcc-fcnt').text($rows.length + '\u00a0ігор'); | |||
$bar.find('.mcc-dur-rng').css('background-size', '100% 100%'); | |||
} | |||
// ============================================================ | // ============================================================ | ||
function injectGamesFilters($container, $table) { | function injectGamesFilters($container, $table) { | ||
if (!$table.length) return; | // Robust table search | ||
if ($container.find('.mcc-games-filters').length) return; | if (!$table || !$table.length) { | ||
$table = $container.find('table').first(); | |||
} | |||
if (!$table.length) { return; } | |||
// Don't add twice | |||
if ($container.find('.mcc-games-filters').length) { return; } | |||
// Collect unique events from column 0 | |||
var events = []; | var events = []; | ||
$table.find('tbody tr').each(function () { | $table.find('tbody tr').each(function () { | ||
| Рядок 327: | Рядок 488: | ||
var opts = '<option value="">Всі події</option>'; | var opts = '<option value="">Всі події</option>'; | ||
events.forEach(function (e) { opts += '<option value="' + e + '">' + e + '</option>'; }); | events.slice(0, 60).forEach(function (e) { | ||
opts += '<option value="' + e + '">' + e + '</option>'; | |||
}); | |||
$ | var $filters = $('<div class="mcc-games-filters">' | ||
+ '<div class="mcc-filter-group"><span class="mcc-filter-label">Подія</span>' | + '<div class="mcc-filter-group"><span class="mcc-filter-label">Подія</span>' | ||
+ '<select class="mcc-filter-select | + '<select class="mcc-filter-select mcc-fe">' + opts + '</select></div>' | ||
+ '<div class="mcc-filter-group"><span class="mcc-filter-label">Роль</span>' | + '<div class="mcc-filter-group"><span class="mcc-filter-label">Роль</span>' | ||
+ '<select class="mcc-filter-select | + '<select class="mcc-filter-select mcc-fr">' | ||
+ '<option value="">Всі ролі</option><option value="Мир">Мирний</option>' | + '<option value="">Всі ролі</option><option value="Мир">Мирний</option>' | ||
+ '<option value="Шер">Шериф</option><option value="Маф">Мафія</option>' | + '<option value="Шер">Шериф</option><option value="Маф">Мафія</option>' | ||
+ '<option value="Дон">Дон</option></select></div>' | + '<option value="Дон">Дон</option></select></div>' | ||
+ '<div class="mcc-filter-group"><span class="mcc-filter-label">Результат</span>' | + '<div class="mcc-filter-group"><span class="mcc-filter-label">Результат</span>' | ||
+ '<select class="mcc-filter-select | + '<select class="mcc-filter-select mcc-frr">' | ||
+ '<option value="">Всі</option><option value="В">Перемога</option>' | + '<option value="">Всі</option>' | ||
+ '<option value="В">Перемога</option>' | |||
+ '<option value="П">Поразка</option></select></div>' | + '<option value="П">Поразка</option></select></div>' | ||
+ '<div class="mcc-filter-group mcc-duration-group">' | + '<div class="mcc-filter-group mcc-duration-group">' | ||
+ '<span class="mcc-filter-label">Макс. тривалість</span>' | + '<span class="mcc-filter-label">Макс. тривалість</span>' | ||
+ '<div class="mcc-slider-wrap">' | + '<div class="mcc-slider-wrap">' | ||
+ '<input type="range" class="mcc-duration-range | + '<input type="range" class="mcc-duration-range mcc-dur-range" min="20" max="75" value="75" step="1">' | ||
+ '<span class="mcc-slider-val | + '<span class="mcc-slider-val mcc-dur-val">75:00</span>' | ||
+ '</div></div>' | + '</div></div>' | ||
+ '<span class="mcc-filter-count | + '<span class="mcc-filter-count mcc-fcount"></span>' | ||
+ '</div>' | + '</div>'); | ||
); | |||
// Insert: before scroll-outer wrapper if exists, else before table | |||
var $wrap = $table.closest('.mcc-scroll-outer'); | |||
($wrap.length ? $wrap : $table).before($filters); | |||
function toMins(s) { | function toMins(s) { | ||
s = (s || '').trim(); | s = (s || '').trim(); | ||
var m1 = s.match(/^(\d+):(\d+)$/); | var m1 = s.match(/^(\d+):(\d+)$/); if (m1) return +m1[1] + +m1[2]/60; | ||
var m2 = s.match(/(\d+)\s*хв\s*(\d+)/); if (m2) return +m2[1] + +m2[2]/60; | |||
var m2 = s.match(/(\d+)\s*хв\s*(\d+)/); | |||
return 999; | return 999; | ||
} | } | ||
function | function doFilter() { | ||
var ev = $(' | var ev = $filters.find('.mcc-fe').val(); | ||
var rol = $(' | var rol = $filters.find('.mcc-fr').val(); | ||
var res = $(' | var res = $filters.find('.mcc-frr').val(); | ||
var dur = +$(' | var dur = +$filters.find('.mcc-dur-range').val(); | ||
var n = 0; | var n = 0; | ||
$table.find('tbody tr').each(function () { | $table.find('tbody tr').each(function () { | ||
var td = $(this).find('td'); | var td = $(this).find('td'); | ||
var show = (!ev || td.eq(0).text().trim().indexOf(ev) !== -1) | var show = (!ev || td.eq(0).text().trim().indexOf(ev) !== -1) | ||
&& (!rol || td.eq(1).text().trim().indexOf(rol) !== -1) | && (!rol || td.eq(1).text().trim().indexOf(rol) !== -1) | ||
| Рядок 376: | Рядок 540: | ||
if (show) n++; | if (show) n++; | ||
}); | }); | ||
$(' | $filters.find('.mcc-fcount').text(n + '\u00a0ігор'); | ||
} | } | ||
$(' | $filters.find('.mcc-dur-range').on('input', function () { | ||
var v = +$(this).val(); | var v = +$(this).val(); | ||
$(' | $filters.find('.mcc-dur-val').text(v + ':00'); | ||
$(this).css('background-size', ((v - 20) / | $(this).css('background-size', ((v - 20) / 55 * 100) + '% 100%'); | ||
doFilter(); | |||
}); | }); | ||
$ | $filters.find('select').on('change', doFilter); | ||
$filters.find('.mcc-fcount').text($table.find('tbody tr').length + '\u00a0ігор'); | |||
$filters.find('.mcc-dur-range').css('background-size', '100% 100%'); | |||
$(' | |||
} | } | ||
| Рядок 399: | Рядок 562: | ||
var $root = $context || $(document); | var $root = $context || $(document); | ||
var roleMap = { | var roleMap = { | ||
'Мир': { bg:'rgba( | // Мирний — light red (city team color) | ||
'Шер': { bg:'rgba( | 'Мир': { bg:'rgba(200,70,70,0.10)', color:'#e89090', border:'rgba(200,70,70,0.20)' }, | ||
'Маф': { bg:'rgba( | // Шериф — gold/amber | ||
'Дон': { bg:'rgba( | 'Шер': { bg:'rgba(210,168,60,0.12)', color:'#d4a843', border:'rgba(210,168,60,0.25)' }, | ||
// Мафія — medium grey | |||
'Маф': { bg:'rgba(100,100,115,0.18)', color:'#aaa8b8', border:'rgba(120,120,140,0.30)'}, | |||
// Дон — lighter grey | |||
'Дон': { bg:'rgba(80,80,95,0.14)', color:'#9896a8', border:'rgba(100,100,120,0.24)'} | |||
}; | }; | ||
| Рядок 473: | Рядок 640: | ||
} | } | ||
$(function () { wrapWideTables(); }); | $(function () { | ||
// Full-width stat pages — don't wrap tables, let them fill naturally | |||
if (!isMccFullwidth()) { | |||
wrapWideTables(); | |||
} else { | |||
// On full-width pages: CSS handles width and scroll, no JS needed | |||
} | |||
}); | |||
// ============================================================ | // ============================================================ | ||
| Рядок 520: | Рядок 694: | ||
applySeasonColors(); | applySeasonColors(); | ||
$(document).on('mcc:content-loaded', function () { applySeasonColors(); }); | $(document).on('mcc:content-loaded', function () { applySeasonColors(); }); | ||
}); | |||
// ============================================================ | |||
// 13b. DYNAMIC R-BOX TOP — відступ під реальну висоту header | |||
// ============================================================ | |||
$(function () { | |||
function setRboxTop() { | |||
var $hdr = $('.header-container.header-chrome'); | |||
var h = $hdr.length ? ($hdr.outerHeight() + 4) : 66; | |||
$('.r-box, .tournament-box, .series-box').css('top', h + 'px'); | |||
} | |||
setRboxTop(); | |||
setTimeout(setRboxTop, 300); | |||
$(window).on('resize', setRboxTop); | |||
}); | }); | ||
| Рядок 622: | Рядок 810: | ||
history.replaceState(null, null, window.location.pathname); | history.replaceState(null, null, window.location.pathname); | ||
}); | }); | ||
// ============================================================ | // ============================================================ | ||
// | // FILTERS FOR SEASON "Запис ігор" WIDE TABLE | ||
// Only runs on season/tournament pages (not full-width stat pages) | |||
// ============================================================ | // ============================================================ | ||
$(function () { | $(function () { | ||
var $ | // Skip on full-width stat pages | ||
if (!$ | if (isMccFullwidth()) return; | ||
var $table = $('.wikitable.wide-table').first(); | |||
if (!$table.length) $table = $('.wikitable.full-width').first(); | |||
if (!$table.length) return; | |||
var $rows = $table.find('tr').filter(function () { | |||
return $(this).find('td').length > 0; | |||
}); | |||
if (!$rows.length) return; | |||
var results = []; | |||
$rows.each(function () { | |||
var tds = $(this).find('td'); | |||
var r = tds.eq(tds.length - 2).text().trim(); | |||
if (r && results.indexOf(r) === -1) results.push(r); | |||
}); | |||
var resOpts = '<option value="">Всі результати</option>'; | |||
results.forEach(function (r) { | |||
resOpts += '<option value="' + r + '">' + r + '</option>'; | |||
}); | |||
var $bar = $( | |||
'<div class="mcc-games-filters">' + | |||
'<div class="mcc-filter-group">' + | |||
'<span class="mcc-filter-label">Результат</span>' + | |||
'<select class="mcc-filter-select sg-res">' + resOpts + '</select>' + | |||
'</div>' + | |||
'<div class="mcc-filter-group mcc-duration-group">' + | |||
'<span class="mcc-filter-label">Макс. тривалість</span>' + | |||
'<div class="mcc-slider-wrap">' + | |||
'<input type="range" class="mcc-duration-range sg-dur" min="30" max="65" value="65" step="1">' + | |||
'<span class="mcc-slider-val sg-durv">65:00</span>' + | |||
'</div>' + | |||
'</div>' + | |||
'<span class="mcc-filter-count sg-cnt"></span>' + | |||
'</div>' | |||
); | |||
var $ | var $target = $table.closest('.mcc-scroll-outer, .mcc-scroll-inner'); | ||
($target.length ? $target : $table).before($bar); | |||
function toMins(s) { | |||
s = (s || '').trim(); | |||
var m = s.match(/(\d+)\s*хв\s*(\d+)/); | |||
if (m) return parseInt(m[1]) + parseInt(m[2]) / 60; | |||
m = s.match(/^(\d+):(\d+)$/); | |||
if (m) return parseInt(m[1]) + parseInt(m[2]) / 60; | |||
return 999; | |||
} | |||
function | function doFilter() { | ||
var | var res = $bar.find('.sg-res').val(); | ||
var dur = parseInt($bar.find('.sg-dur').val()); | |||
var n = 0; | |||
$rows.each(function () { | |||
var tds = $(this).find('td'); | |||
var timeVal = tds.eq(0).text().trim(); | |||
var resVal = tds.eq(tds.length - 2).text().trim(); | |||
var show = (!res || resVal === res) && toMins(timeVal) <= dur; | |||
$(this).toggle(show); | |||
if (show) n++; | |||
}); | |||
$bar.find('.sg-cnt').text(n + '\u00a0ігор'); | |||
} | } | ||
$ | |||
$ | $bar.find('.sg-dur').on('input', function () { | ||
var v = parseInt($(this).val()); | |||
$bar.find('.sg-durv').text(v + ':00'); | |||
$(this).css('background-size', ((v - 30) / 35 * 100) + '% 100%'); | |||
doFilter(); | |||
}); | |||
$bar.find('.sg-res').on('change', doFilter); | |||
$bar.find('.sg-cnt').text($rows.length + '\u00a0ігор'); | |||
$bar.find('.sg-dur').css('background-size', '100% 100%'); | |||
}); | }); | ||
// | // ============================================================ | ||
// | // 17. BANNER SEARCH | ||
// | |||
// ============================================================ | |||
$(function () { | $(function () { | ||
var | |||
$( | // ── MEDALS helper — adds gold/silver/bronze circles to first-column cells ── | ||
var $ | function addMedals($t) { | ||
$t.find('tbody tr, tr').filter(function () { | |||
$ | return $(this).find('td').length >= 2; | ||
var | }).each(function () { | ||
$ | var $tds = $(this).find('td'); | ||
var raw = $tds.eq(0).text().trim(); | |||
var n = parseInt(raw); | |||
$ | if (isNaN(n) || n < 1 || n > 3) return; | ||
if ($tds.eq(0).find('[class^="mcc-rank"]').length) return; | |||
$tds.eq(0).html('<span class="mcc-rank-' + n + '">' + n + '</span>'); | |||
}); | |||
} | |||
// ── Tables that need medals: closedcups, rank, getnames, wide rank (Період/Фіналіст) ── | |||
$('.content .mw-parser-output .wikitable').each(function () { | |||
var $t = $(this); | |||
if ($t.hasClass('mcc-rating-table')) return; | |||
var $ths = $t.find('tr').first().find('th'); | |||
if (!$ths.length) return; | |||
var h0 = $ths.eq(0).text().trim(); | |||
// Wide rank tables: № + more than 5 columns (Період, Фіналіст) | |||
if (h0.indexOf('№') !== -1 && $ths.length > 5) { | |||
addMedals($t); | |||
return; | |||
} | |||
// Explicit class-based | |||
if ($t.hasClass('closedcups') || $t.hasClass('rank')) { | |||
addMedals($t); | |||
} | |||
}); | |||
// ── Get Names tables — auto-number ── | |||
$('.content .mw-parser-output .wikitable.getnames').each(function () { | |||
var $t = $(this); | |||
var $headerRow = $t.find('tr').first(); | |||
if ($headerRow.find('th').first().text().trim() === '№') return; | |||
$headerRow.prepend('<th style="width:38px;text-align:center">№</th>'); | |||
var n = 1; | |||
$t.find('tbody tr, tr').filter(function () { | |||
return $(this).find('td').length > 0; | |||
}).each(function () { | |||
var badge = n <= 3 | |||
? '<span class="mcc-rank-' + n + '">' + n + '</span>' | |||
: ('<span style="display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;font-size:12px;font-weight:600">' + n + '</span>'); | |||
$(this).prepend('<td style="text-align:center;padding:10px 8px">' + badge + '</td>'); | |||
n++; | |||
}); | |||
}); | |||
// ── Season final table — auto-number (Пан/Пані | І | П | ДБ | Дія | Σ) ── | |||
$('.content .mw-parser-output .wikitable').each(function () { | |||
var $t = $(this); | |||
if ($t.hasClass('mcc-rating-table') || $t.hasClass('mcc-final-table')) return; | |||
var $ths = $t.find('tr').first().find('th'); | |||
if (!$ths.length) return; | |||
var h0 = $ths.eq(0).text().trim(); | |||
var hLast = $ths.last().text().trim(); | |||
var isFinal = (h0 === 'Пан/Пані' || h0 === 'Пан' || h0 === 'Гравець') | |||
&& hLast === 'Σ'; | |||
if (!isFinal) return; | |||
$t.addClass('mcc-final-table'); | |||
$t.find('tr').first().prepend('<th style="width:36px;text-align:center">#</th>'); | |||
var n = 1; | |||
$t.find('tbody tr').each(function () { | |||
var $tds = $(this).find('td'); | |||
if (!$tds.length) return; | |||
var badge = n <= 3 | |||
? '<span class="mcc-rank-' + n + '">' + n + '</span>' | |||
: n; | |||
$(this).prepend('<td style="text-align:center;padding:10px 8px">' + badge + '</td>'); | |||
n++; | |||
}); | }); | ||
}); | }); | ||
}); | }); | ||
// | // ============================================================ | ||
// | // SEASON RATING TABLE — medals + color extremes for №|Пан/Пані|Σ|І|% | ||
// ============================================================ | |||
// | |||
$(function () { | $(function () { | ||
$('. | if (isMccFullwidth()) return; | ||
var $ | |||
function processRatingTable($t) { | |||
var headers = []; | |||
$t.find('tr').first().find('th').each(function () { | |||
headers.push($(this).text().trim()); | |||
}); | |||
if (headers[0] !== '№') return; | |||
if (headers.length < 4) return; | |||
var hasSigma = (headers[2] && headers[2].indexOf('Σ') !== -1); | |||
if (!hasSigma) return; | |||
if (headers.length > 8) return; // wide tables handled elsewhere | |||
var iGames = 3; | |||
var iPct = 4; | |||
var hasI = headers[iGames] === 'І'; | |||
var hasPct = headers[iPct] === '%'; | |||
$t.addClass('mcc-rating-table'); | |||
var $rows = $t.find('tbody tr').filter(function () { | |||
return $(this).find('td').length >= 4; | |||
}); | |||
if (!$rows.length) return; | |||
// Medals top 3 | |||
$rows.each(function () { | |||
var $tds = $(this).find('td'); | |||
var n = parseInt($tds.eq(0).text().trim()); | |||
if (n >= 1 && n <= 3) { | |||
$tds.eq(0).html('<span class="mcc-rank-' + n + '">' + n + '</span>'); | |||
} | |||
}); | |||
// Σ white | |||
$rows.each(function () { | |||
$(this).find('td').eq(2).css({ color: '#ffffff', fontWeight: '700' }); | |||
}); | |||
// Games extremes | |||
if (hasI) { | |||
var gVals = []; | |||
$rows.each(function () { | |||
var v = parseInt($(this).find('td').eq(iGames).text().trim()); | |||
if (!isNaN(v) && v > 0) gVals.push(v); | |||
}); | |||
if (gVals.length > 1) { | |||
var gMax = Math.max.apply(null, gVals); | |||
var gMin = Math.min.apply(null, gVals); | |||
$rows.each(function () { | |||
var $td = $(this).find('td').eq(iGames); | |||
var v = parseInt($td.text().trim()); | |||
if (isNaN(v)) return; | |||
if (v === gMax) $td.addClass('mcc-games-hi'); | |||
else if (v === gMin) $td.addClass('mcc-games-lo'); | |||
}); | |||
} | |||
} | |||
// | // WR extremes | ||
if (hasPct) { | |||
var pVals = []; | |||
$rows.each(function () { | |||
var v = parseFloat($(this).find('td').eq(iPct).text().trim().replace('%','')); | |||
if (!isNaN(v)) pVals.push(v); | |||
}); | |||
if (pVals.length > 1) { | |||
var pMax = Math.max.apply(null, pVals); | |||
var pMin = Math.min.apply(null, pVals); | |||
$rows.each(function () { | |||
var $td = $(this).find('td').eq(iPct); | |||
var v = parseFloat($td.text().trim().replace('%','')); | |||
if (isNaN(v)) return; | |||
if (v === pMax) $td.addClass('mcc-wr-hi'); | |||
else if (v === pMin) $td.addClass('mcc-wr-lo'); | |||
}); | |||
} | |||
} | } | ||
} | |||
// | $('.content .mw-parser-output .wikitable').each(function () { | ||
processRatingTable($(this)); | |||
}); | |||
}); | |||
// ============================================================ | |||
// PLAYER GAMES FILTERS — pre-rendered tab (server-side FetchData) | |||
// ============================================================ | |||
$(function () { | |||
var $content = $('#tab-games'); | |||
if (!$content.length) return; | |||
var $table = $content.find('table').first(); | |||
if (!$table.length) return; | |||
applyWinrateColors($content); | |||
applyRolePills($content); | |||
buildGamesFilters($content, $table); | |||
}); | |||
// ============================================================ | |||
// 17. BANNER SEARCH | |||
// ============================================================ | |||
$(function () { | |||
var $searchInput = $('#searchInput'); | |||
if (!$searchInput.length) return; | |||
$searchInput.on('keydown', function (e) { | |||
if (e.key === 'Enter') { | |||
var q = $(this).val().trim(); | |||
if (q) window.location.href = '/index.php?search=' + encodeURIComponent(q); | |||
} | } | ||
}); | }); | ||
}); | |||
// ============================================================ | |||
// HOME PAGE — collapsible panels + clickable rows | |||
// ============================================================ | |||
$(function () { | |||
// Expand/collapse | |||
var GRID_PANELS = ['seasons-panel', 'gn-panel', 'closed-panel']; | |||
$('[data-toggle]').on('click', function () { | |||
var $hd = $(this); | |||
var targetId = $hd.data('toggle'); | |||
var $bd = $('#' + targetId); | |||
if (!$bd.length) return; | |||
var isGridPanel = GRID_PANELS.indexOf(targetId) !== -1; | |||
var isDesktop = $(window).width() >= 1000; | |||
if (isGridPanel && isDesktop) { | |||
// Collapse/expand all 3 grid panels together | |||
var collapsed = $bd.hasClass('collapsed'); | |||
GRID_PANELS.forEach(function(id) { | |||
$('#' + id).toggleClass('collapsed', !collapsed); | |||
$('[data-toggle="' + id + '"]').toggleClass('collapsed', !collapsed); | |||
}); | |||
} else { | |||
var collapsed = $bd.hasClass('collapsed'); | |||
$bd.toggleClass('collapsed', !collapsed); | |||
$hd.toggleClass('collapsed', !collapsed); | |||
} | |||
}); | |||
// Clickable rows / cards — navigate on click, ignore inner link clicks | |||
$(document).on('click', '.mcc-nav', function (e) { | |||
if ($(e.target).closest('a').length) return; // let wiki links work normally | |||
var href = $(this).data('href'); | |||
if (href) window.location.href = href; | |||
}); | |||
// Cursor pointer for nav elements | |||
$('.mcc-nav').css('cursor', 'pointer'); | |||
}); | }); | ||
Поточна версія на 02:34, 11 квітня 2026
// ============================================================
// MediaWiki:Common.js — Mafia Closed Circle — ФІНАЛЬНА ВЕРСІЯ
// ============================================================
// ============================================================
// FULL-WIDTH PAGE DETECTION
// ============================================================
var MCC_FULLWIDTH_PAGES = [
'page-Ігри', 'page-Статистика', 'page-Перша_статистика',
'page-Фіналіст', 'page-Сезони', 'page-Гравці',
'page-Фундація', 'page-Призовий_фонд', 'page-Призові',
'page-Титули', 'page-Період'
];
function isMccFullwidth() {
var bodyClasses = document.body.className;
for (var i = 0; i < MCC_FULLWIDTH_PAGES.length; i++) {
if (bodyClasses.indexOf(MCC_FULLWIDTH_PAGES[i]) !== -1) return true;
}
return false;
}
// ============================================================
// 1. RANDOM ARTICLES
// ============================================================
$(document).ready(function () {
var apiUrl = mw.config.get('wgScriptPath') + '/api.php';
$.getJSON(apiUrl, {
action: 'query', format: 'json', list: 'random',
rnnamespace: '0', rnlimit: '5'
}, function (data) {
var html = '';
$.each(data.query.random, function (i, article) {
html += '<div class="random-article-preview"><h2><a href="/wiki/'
+ encodeURIComponent(article.title) + '">' + article.title + '</a></h2></div>';
});
$('#random-articles-container').html(html);
});
});
// ============================================================
// 2. L-BOX NAVIGATION
// document.getElementById — коректно обробляє кирилицю
// ============================================================
$(function () {
var $lbox = $('.l-box');
if ($lbox.length === 0) return;
var $items = $lbox.find('.l-box-item');
function buildSectionMap() {
var map = [];
$items.each(function () {
var target = $(this).data('target');
if (target === 'top') {
map.push({ $item: $(this), top: 0 });
} else {
var el = document.getElementById(target);
if (el) {
var $el = $(el);
var $heading = $el.closest('h2, h3, h4').length
? $el.closest('h2, h3, h4') : $el;
map.push({ $item: $(this), top: $heading.offset().top });
}
}
});
map.sort(function (a, b) { return a.top - b.top; });
return map;
}
function updateActive(map) {
var scrollY = $(window).scrollTop() + 120;
var current = null;
for (var i = 0; i < map.length; i++) {
if (map[i].top <= scrollY) current = map[i].$item;
}
$items.removeClass('active');
if (current) current.addClass('active');
}
$items.on('click', function () {
var target = $(this).data('target');
$items.removeClass('active');
$(this).addClass('active');
// If games tab is active — switch to profile first, then scroll
var $profileTab = $('.player-tab[data-tab="profile"], .player-tab[data-tab="info"]');
var $gamesTab = $('.player-tab[data-tab="games"]');
var gamesActive = $gamesTab.length && $gamesTab.hasClass('active');
function doScroll() {
if (target === 'top') {
$('html, body').animate({ scrollTop: 0 }, 280);
} else {
var el = document.getElementById(target);
if (el) {
var $el = $(el);
var $heading = $el.closest('h2, h3, h4').length
? $el.closest('h2, h3, h4') : $el;
$('html, body').animate({ scrollTop: $heading.offset().top - 78 }, 280);
}
}
}
if (gamesActive) {
// Click the profile tab to switch
$profileTab.trigger('click');
// Wait for tab switch + DOM
setTimeout(doScroll, 120);
} else {
doScroll();
}
});
var sectionMap = buildSectionMap();
$(document).on('mcc:content-loaded', function () {
sectionMap = buildSectionMap();
updateActive(sectionMap);
});
var ticking = false;
$(window).on('scroll', function () {
if (!ticking) {
window.requestAnimationFrame(function () {
updateActive(sectionMap);
ticking = false;
});
ticking = true;
}
});
$(window).on('resize', function () {
sectionMap = buildSectionMap();
updateActive(sectionMap);
});
setTimeout(function () {
sectionMap = buildSectionMap();
updateActive(sectionMap);
}, 200);
});
// ============================================================
// 3. PROFILE ICONS — клік + inactive + розгортання <p>
// ============================================================
$(document).ready(function () {
// Розгортаємо <p> яку MediaWiki вставляє всередину grid
function fixProfileGrid() {
$('.profile-links-icons').each(function () {
var $wrap = $(this);
var $p = $wrap.find('> p');
if ($p.length) {
$p.children().appendTo($wrap);
$p.remove();
}
$wrap.css({ display: 'grid', 'grid-template-columns': 'repeat(4, 1fr)', gap: '0' });
$wrap.find('.profile-icon').css({
display: 'flex', 'align-items': 'center', 'justify-content': 'center',
padding: '13px 4px', float: 'none', width: 'auto',
'min-width': '0', overflow: 'hidden',
'border-right': '1px solid rgba(255,255,255,0.06)'
});
$wrap.find('.profile-icon:last-child').css('border-right', 'none');
$wrap.find('.profile-icon img, .profile-icon .mw-file-element').css({
width: '22px', height: '22px', 'max-width': '22px',
'object-fit': 'contain', display: 'block'
});
$wrap.find('.profile-icon > span, .profile-icon .mw-default-size').css({
display: 'flex', 'align-items': 'center',
'justify-content': 'center', 'line-height': '0'
});
});
}
fixProfileGrid();
setTimeout(fixProfileGrid, 400);
// Клік + inactive
$('.profile-icon').each(function () {
var $icon = $(this);
var url = ($icon.attr('data-url') || '').trim();
if (url) {
$icon.css('cursor', 'pointer');
$icon.on('click', function () { window.open(url, '_blank'); });
} else {
$icon.addClass('inactive');
}
});
});
// ============================================================
// 4. STATS OVERLAY НА ФОТО ГРАВЦЯ В R-BOX
// ============================================================
$(function () {
var $rbox = $('.r-box');
if ($rbox.length === 0) return;
var wins = '', losses = '', games = '';
$rbox.find('table tr').each(function () {
var label = $(this).find('th').text().trim().toLowerCase();
var val = $(this).find('td').text().trim();
if (label.indexOf('офіційних') !== -1 || label.indexOf('ігор') !== -1) {
var m = val.match(/\((\d+)[^\d]+(\d+)\)/);
if (m) { wins = m[1]; losses = m[2]; }
var total = parseInt(wins || 0) + parseInt(losses || 0);
if (total > 0) games = total;
}
});
if (games && wins && losses) {
var $figure = $rbox.find('figure');
if ($figure.length && !$figure.find('.rp-stats-overlay').length) {
$figure.append(
'<div class="rp-stats-overlay">'
+ '<div class="rp-stat-box"><span class="rp-sv">' + games + '</span><span class="rp-sl">Ігор</span></div>'
+ '<div class="rp-stat-box"><span class="rp-sv">' + wins + '</span><span class="rp-sl">Перемог</span></div>'
+ '<div class="rp-stat-box"><span class="rp-sv">' + losses + '</span><span class="rp-sl">Поразок</span></div>'
+ '</div>'
);
}
}
});
// ============================================================
// 5. ДИНАМІЧНІ ПОСИЛАННЯ НА ГОЛОВНІЙ
// ============================================================
$(document).ready(function () {
if (!$('body').hasClass('page-Головна_сторінка')) return;
var block1 = [{ title: 'Фінал Року', url: '/index.php/Фінал_Року' }];
var block2 = [
{ title: 'Перша статистика', url: '/index.php/Перша_статистика' },
{ title: 'Період', url: '/index.php/Період' },
{ title: 'Друга статистика', url: '/index.php/Статистика' }
];
var block3 = [
{ title: 'Mafia Closed Cup I', url: '/index.php/Mafia_Closed_Cup_I' },
{ title: 'Mafia Closed Cup I Online', url: '/index.php/Mafia_Closed_Cup_I_Online' },
{ title: 'My Closest Circle I', url: '/index.php/My_Closest_Circle_I' }
];
function rand(arr) { return arr[Math.floor(Math.random() * arr.length)]; }
var $b = $('.home__block-image-block p');
if ($b.length >= 3) {
[rand(block1), rand(block2), rand(block3)].forEach(function (l, i) {
$b.eq(i).html('<a href="' + l.url + '">' + l.title + '</a>');
});
}
});
// ============================================================
// 6. PLAYER TABS + LAZY LOADING
// ============================================================
// Глобальна функція перемикання табу (використовується і в l-box)
function activatePlayerTab(tabId) {
var $tabs = $('.player-tab');
var $contents = $('.player-tab-content');
var $rbox = $('.r-box');
$tabs.removeClass('active');
$contents.removeClass('active');
$tabs.filter('[data-tab="' + tabId + '"]').addClass('active');
$('#tab-' + tabId).addClass('active');
if ($(window).width() <= 768) {
if (tabId === 'games') $rbox.slideUp(200);
else $rbox.slideDown(200);
}
}
$(function () {
var $tabs = $('.player-tab');
if ($tabs.length === 0) return;
$tabs.on('click', function () {
var tabId = $(this).data('tab');
activatePlayerTab(tabId);
if (tabId === 'games') {
var $content = $('#tab-games');
if ($content.data('loaded')) return;
var playerName = $content.data('player');
if (!playerName) return;
$content.html('<div class="tab-loader">Завантаження...</div>');
$.ajax({
url: mw.config.get('wgScriptPath') + '/api.php',
data: {
action: 'expandtemplates', format: 'json',
text: '{{#invoke:FetchData|player_games|player=' + playerName + '}}',
prop: 'wikitext'
},
dataType: 'json',
success: function (response) {
if (!response.expandtemplates || !response.expandtemplates.wikitext) return;
$.ajax({
url: mw.config.get('wgScriptPath') + '/api.php',
data: {
action: 'parse', format: 'json',
text: response.expandtemplates.wikitext,
contentmodel: 'wikitext',
disablelimitreport: true
},
dataType: 'json',
success: function (r) {
if (!r.parse || !r.parse.text) return;
$content.html(r.parse.text['*']);
$content.data('loaded', true);
setTimeout(function () {
var $table = $content.find('table').first();
if ($table.length) {
$table.addClass('wikitable sortable');
try { $table.tablesorter(); } catch(e) {}
}
applyWinrateColors($content);
applyRolePills($content);
wrapWideTables($content);
// Inject filters directly at top of content
if (!$content.find('.mcc-games-filters').length) {
buildGamesFilters($content, $table);
}
$(document).trigger('mcc:content-loaded');
}, 100);
},
error: function () {
$content.html('<p style="color:#ff7777;text-align:center;padding:20px">Помилка завантаження.</p>');
}
});
},
error: function () {
$content.html('<p style="color:#ff7777;text-align:center;padding:20px">Помилка завантаження.</p>');
}
});
}
});
$(window).on('resize', function () {
if ($(window).width() > 768) $('.r-box').show();
});
});
// ============================================================
// 7. ФІЛЬТРИ НАД ТАБЛИЦЕЮ ІГОР
// ============================================================
// 7b. BUILD GAMES FILTERS
// ============================================================
function buildGamesFilters($container, $table) {
// Already added?
if ($container.find('.mcc-games-filters').length) return;
// Find table if not valid
if (!$table || !$table.length) {
$table = $container.find('table').first();
}
if (!$table.length) return;
// Use ALL tr rows (with or without tbody)
var $rows = $table.find('tr').filter(function () {
return $(this).find('td').length > 0; // only data rows
});
if (!$rows.length) return;
// Collect unique events from first td
var events = [];
$rows.each(function () {
var ev = $(this).find('td').first().text().trim();
if (ev && events.indexOf(ev) === -1) events.push(ev);
});
// Build event options
var opts = '<option value="">Всі події</option>';
events.forEach(function (e) {
opts += '<option value="' + e + '">' + e + '</option>';
});
// Build the filter bar HTML
var barHTML = '<div class="mcc-games-filters">'
+ '<div class="mcc-filter-group">'
+ '<span class="mcc-filter-label">Подія</span>'
+ '<select class="mcc-filter-select mcc-fe">' + opts + '</select>'
+ '</div>'
+ '<div class="mcc-filter-group">'
+ '<span class="mcc-filter-label">Роль</span>'
+ '<select class="mcc-filter-select mcc-fr">'
+ '<option value="">Всі ролі</option>'
+ '<option value="Мир">Мирний</option>'
+ '<option value="Шер">Шериф</option>'
+ '<option value="Маф">Мафія</option>'
+ '<option value="Дон">Дон</option>'
+ '</select>'
+ '</div>'
+ '<div class="mcc-filter-group">'
+ '<span class="mcc-filter-label">Результат</span>'
+ '<select class="mcc-filter-select mcc-frr">'
+ '<option value="">Всі</option>'
+ '<option value="В">Перемога</option>'
+ '<option value="П">Поразка</option>'
+ '</select>'
+ '</div>'
+ '<div class="mcc-filter-group mcc-duration-group">'
+ '<span class="mcc-filter-label">Макс. тривалість</span>'
+ '<div class="mcc-slider-wrap">'
+ '<input type="range" class="mcc-duration-range mcc-dur-rng" min="20" max="75" value="75" step="1">'
+ '<span class="mcc-slider-val mcc-dur-v">75:00</span>'
+ '</div>'
+ '</div>'
+ '<span class="mcc-filter-count mcc-fcnt"></span>'
+ '</div>';
// Insert at the very top of the container
$container.prepend(barHTML);
// Get reference to the just-added bar
var $bar = $container.find('.mcc-games-filters').first();
function toMins(s) {
s = (s || '').trim();
var m1 = s.match(/^(\d+):(\d+)$/);
if (m1) return parseInt(m1[1]) + parseInt(m1[2]) / 60;
var m2 = s.match(/(\d+)\s*[хh]\s*[вv]\s*(\d+)/);
if (m2) return parseInt(m2[1]) + parseInt(m2[2]) / 60;
return 999;
}
function doFilter() {
var ev = $bar.find('.mcc-fe').val() || '';
var rol = $bar.find('.mcc-fr').val() || '';
var res = $bar.find('.mcc-frr').val() || '';
var dur = parseFloat($bar.find('.mcc-dur-rng').val()) || 75;
var n = 0;
$rows.each(function () {
var $tds = $(this).find('td');
var evVal = $tds.eq(0).text().trim();
var rolVal = $tds.eq(1).text().trim();
var timeVal = $tds.eq(2).text().trim();
var resVal = $tds.eq(3).text().trim();
var show = (ev === '' || evVal.indexOf(ev) !== -1)
&& (rol === '' || rolVal.indexOf(rol) !== -1)
&& (res === '' || resVal.indexOf(res) !== -1)
&& toMins(timeVal) <= dur;
$(this).toggle(show);
if (show) n++;
});
$bar.find('.mcc-fcnt').text(n + '\u00a0ігор');
}
$bar.find('.mcc-dur-rng').on('input', function () {
var v = parseFloat($(this).val());
$bar.find('.mcc-dur-v').text(v + ':00');
$(this).css('background-size', ((v - 20) / 55 * 100) + '% 100%');
doFilter();
});
$bar.find('select').on('change', doFilter);
$bar.find('.mcc-fcnt').text($rows.length + '\u00a0ігор');
$bar.find('.mcc-dur-rng').css('background-size', '100% 100%');
}
// ============================================================
function injectGamesFilters($container, $table) {
// Robust table search
if (!$table || !$table.length) {
$table = $container.find('table').first();
}
if (!$table.length) { return; }
// Don't add twice
if ($container.find('.mcc-games-filters').length) { return; }
// Collect unique events from column 0
var events = [];
$table.find('tbody tr').each(function () {
var ev = $(this).find('td').eq(0).text().trim();
if (ev && events.indexOf(ev) === -1) events.push(ev);
});
var opts = '<option value="">Всі події</option>';
events.slice(0, 60).forEach(function (e) {
opts += '<option value="' + e + '">' + e + '</option>';
});
var $filters = $('<div class="mcc-games-filters">'
+ '<div class="mcc-filter-group"><span class="mcc-filter-label">Подія</span>'
+ '<select class="mcc-filter-select mcc-fe">' + opts + '</select></div>'
+ '<div class="mcc-filter-group"><span class="mcc-filter-label">Роль</span>'
+ '<select class="mcc-filter-select mcc-fr">'
+ '<option value="">Всі ролі</option><option value="Мир">Мирний</option>'
+ '<option value="Шер">Шериф</option><option value="Маф">Мафія</option>'
+ '<option value="Дон">Дон</option></select></div>'
+ '<div class="mcc-filter-group"><span class="mcc-filter-label">Результат</span>'
+ '<select class="mcc-filter-select mcc-frr">'
+ '<option value="">Всі</option>'
+ '<option value="В">Перемога</option>'
+ '<option value="П">Поразка</option></select></div>'
+ '<div class="mcc-filter-group mcc-duration-group">'
+ '<span class="mcc-filter-label">Макс. тривалість</span>'
+ '<div class="mcc-slider-wrap">'
+ '<input type="range" class="mcc-duration-range mcc-dur-range" min="20" max="75" value="75" step="1">'
+ '<span class="mcc-slider-val mcc-dur-val">75:00</span>'
+ '</div></div>'
+ '<span class="mcc-filter-count mcc-fcount"></span>'
+ '</div>');
// Insert: before scroll-outer wrapper if exists, else before table
var $wrap = $table.closest('.mcc-scroll-outer');
($wrap.length ? $wrap : $table).before($filters);
function toMins(s) {
s = (s || '').trim();
var m1 = s.match(/^(\d+):(\d+)$/); if (m1) return +m1[1] + +m1[2]/60;
var m2 = s.match(/(\d+)\s*хв\s*(\d+)/); if (m2) return +m2[1] + +m2[2]/60;
return 999;
}
function doFilter() {
var ev = $filters.find('.mcc-fe').val();
var rol = $filters.find('.mcc-fr').val();
var res = $filters.find('.mcc-frr').val();
var dur = +$filters.find('.mcc-dur-range').val();
var n = 0;
$table.find('tbody tr').each(function () {
var td = $(this).find('td');
var show = (!ev || td.eq(0).text().trim().indexOf(ev) !== -1)
&& (!rol || td.eq(1).text().trim().indexOf(rol) !== -1)
&& (!res || td.eq(3).text().trim().indexOf(res) !== -1)
&& toMins(td.eq(2).text()) <= dur;
$(this).toggle(show);
if (show) n++;
});
$filters.find('.mcc-fcount').text(n + '\u00a0ігор');
}
$filters.find('.mcc-dur-range').on('input', function () {
var v = +$(this).val();
$filters.find('.mcc-dur-val').text(v + ':00');
$(this).css('background-size', ((v - 20) / 55 * 100) + '% 100%');
doFilter();
});
$filters.find('select').on('change', doFilter);
$filters.find('.mcc-fcount').text($table.find('tbody tr').length + '\u00a0ігор');
$filters.find('.mcc-dur-range').css('background-size', '100% 100%');
}
// ============================================================
// 8. ROLE PILLS
// ============================================================
function applyRolePills($context) {
var $root = $context || $(document);
var roleMap = {
// Мирний — light red (city team color)
'Мир': { bg:'rgba(200,70,70,0.10)', color:'#e89090', border:'rgba(200,70,70,0.20)' },
// Шериф — gold/amber
'Шер': { bg:'rgba(210,168,60,0.12)', color:'#d4a843', border:'rgba(210,168,60,0.25)' },
// Мафія — medium grey
'Маф': { bg:'rgba(100,100,115,0.18)', color:'#aaa8b8', border:'rgba(120,120,140,0.30)'},
// Дон — lighter grey
'Дон': { bg:'rgba(80,80,95,0.14)', color:'#9896a8', border:'rgba(100,100,120,0.24)'}
};
$root.find('.wikitable tbody td').each(function () {
var $cell = $(this);
var $span = $cell.find('span').filter(function () {
return ($(this).attr('style') || '').indexOf('color') !== -1;
});
if (!$span.length) return;
var text = $span.text().trim();
var style = roleMap[text];
if (!style || $span.hasClass('mcc-pill-done')) return;
$span.addClass('mcc-pill-done').css({
display:'inline-block', padding:'2px 10px', borderRadius:'4px',
fontSize:'12.5px', fontWeight:'600', fontFamily:"'Manrope',sans-serif",
background: style.bg, color: style.color, border:'1px solid '+style.border
});
});
}
$(function () { applyRolePills(); });
$(document).on('mcc:content-loaded', function () { applyRolePills(); });
// ============================================================
// 9. АНІМАЦІЯ РЯДКІВ ПРИ СОРТУВАННІ
// ============================================================
$(function () {
$(document).on('sortEnd', 'table.wikitable', function () {
var $t = $(this);
$t.addClass('mcc-sorting').removeClass('mcc-sorted');
setTimeout(function () {
$t.removeClass('mcc-sorting').addClass('mcc-sorted');
setTimeout(function () { $t.removeClass('mcc-sorted'); }, 600);
}, 20);
});
});
// ============================================================
// 10. ГОРИЗОНТАЛЬНИЙ СКРОЛ ДЛЯ ШИРОКИХ ТАБЛИЦЬ
// ============================================================
function wrapWideTables($context) {
var $root = $context || $(document);
function wrap($table) {
if ($table.closest('.mcc-scroll-inner').length) return;
$table.wrap('<div class="mcc-scroll-inner"></div>');
$table.closest('.mcc-scroll-inner').wrap('<div class="mcc-scroll-outer"></div>');
var $inner = $table.closest('.mcc-scroll-inner');
var $outer = $inner.closest('.mcc-scroll-outer');
function check() {
$outer.toggleClass('mcc-no-fade',
$inner[0].scrollLeft + $inner[0].clientWidth >= $inner[0].scrollWidth - 4);
}
$inner.on('scroll', check);
check();
}
$root.find('.wikitable.wide-table, .wikitable.mcc-wide, .wikitable.full-width').each(function () {
wrap($(this));
});
$root.find('.wikitable').each(function () {
var $t = $(this);
if ($t.closest('.mcc-scroll-inner, .r-box, .tournament-box').length) return;
if ($t.find('thead tr:first th').length > 8) {
$t.addClass('mcc-wide-table');
wrap($t);
}
});
}
$(function () {
// Full-width stat pages — don't wrap tables, let them fill naturally
if (!isMccFullwidth()) {
wrapWideTables();
} else {
// On full-width pages: CSS handles width and scroll, no JS needed
}
});
// ============================================================
// 11. ВІНРЕЙТ — кольорові класи
// ============================================================
function applyWinrateColors($context) {
var $root = $context || $(document);
$root.find('.wikitable tbody td').each(function () {
var $c = $(this);
if ($c.hasClass('wr-hi') || $c.hasClass('wr-lo')) return;
var m = $c.text().trim().match(/^(\d+(?:\.\d+)?)%$/);
if (!m) return;
var v = parseFloat(m[1]);
if (v >= 55) $c.addClass('wr-hi');
else if (v <= 33) $c.addClass('wr-lo');
});
}
$(function () { applyWinrateColors(); });
// ============================================================
// 12. PLAYER TABS — "(42)" → стилізований badge
// ============================================================
$(function () {
$('.player-tab').each(function () {
var $tab = $(this);
var m = $tab.text().match(/\((\d+)\)/);
if (!m) return;
$tab.text($tab.text().replace(/\s*\(\d+\)/, '').trim());
$tab.append('<span class="mcc-tab-cnt">' + m[1] + '</span>');
});
});
// ============================================================
// 13. СЕЗОНИ — "Чемпіон" синього кольору
// ============================================================
$(function () {
function applySeasonColors($ctx) {
($ctx || $(document)).find('.wikitable tbody td').each(function () {
var t = $(this).text().trim();
if (t === 'Чемпіон' || t === '♔ Чемпіон') {
$(this).css({ color: '#7ab8ff', fontWeight: '700' });
}
});
}
applySeasonColors();
$(document).on('mcc:content-loaded', function () { applySeasonColors(); });
});
// ============================================================
// 13b. DYNAMIC R-BOX TOP — відступ під реальну висоту header
// ============================================================
$(function () {
function setRboxTop() {
var $hdr = $('.header-container.header-chrome');
var h = $hdr.length ? ($hdr.outerHeight() + 4) : 66;
$('.r-box, .tournament-box, .series-box').css('top', h + 'px');
}
setRboxTop();
setTimeout(setRboxTop, 300);
$(window).on('resize', setRboxTop);
});
// ============================================================
// 14. NAV <pre> — розгортаємо pre всередині навігації
// ============================================================
$(function () {
var sel = '.tournament-nav, .series-nav, .player-nav, .championship-nav';
$(sel).each(function () {
var $nav = $(this);
var $pre = $nav.find('pre');
if (!$pre.length) return;
$pre.contents().each(function () { $nav.append($(this).clone()); });
$pre.remove();
});
});
// ============================================================
// 15. МОБІЛЬНИЙ ПОШУК
// ============================================================
$(function () {
if ($(window).width() > 768) return;
if ($('.mobile-search-btn').length) return;
var $btn = $('<div class="mobile-search-btn">'
+ '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2">'
+ '<circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg></div>');
var $overlay = $('<div class="mobile-search-overlay">'
+ '<input type="text" placeholder="Пошук..." autocomplete="off">'
+ '<div class="mobile-search-close">×</div></div>');
$('.minerva-header .branding-box').after($btn);
$('body').append($overlay);
var $input = $overlay.find('input');
var $close = $overlay.find('.mobile-search-close');
$btn.on('click', function (e) {
e.preventDefault(); e.stopPropagation();
$overlay.addClass('active');
setTimeout(function () { $input.focus(); }, 100);
});
$close.on('click', function () { $overlay.removeClass('active'); $input.val(''); });
$input.on('keydown', function (e) {
if (e.keyCode === 13) {
var q = $input.val().trim();
if (q) window.location.href = '/index.php?title=Спеціальна:Пошук&search=' + encodeURIComponent(q);
}
if (e.keyCode === 27) { $overlay.removeClass('active'); $input.val(''); }
});
$overlay.on('click', function (e) {
if (e.target === this) { $overlay.removeClass('active'); $input.val(''); }
});
});
// ============================================================
// 16. БЛОКУВАННЯ MINERVA SEARCH OVERLAY (ДЕСКТОП)
// ============================================================
$(function () {
if ($(window).width() <= 768) return;
var $searchInput = $('#searchInput');
if ($searchInput.length === 0) return;
setTimeout(function () {
$searchInput.attr('placeholder', 'Пошук...');
var $new = $searchInput.clone(false);
$searchInput.replaceWith($new);
$searchInput = $new;
$searchInput.prop('readonly', false).removeAttr('readonly')
.removeClass('skin-minerva-search-trigger');
$searchInput.on('focus click', function (e) {
e.stopPropagation();
$('body').removeClass('overlay-enabled search-enabled');
$('.overlay, .search-overlay').hide();
if (window.location.hash === '#/search')
history.replaceState(null, null, window.location.pathname);
});
$searchInput.on('keydown', function (e) {
if (e.which === 13) {
e.preventDefault();
var q = $(this).val().trim();
if (q) window.location.href = '/index.php?title=Спеціальна:Пошук&search=' + encodeURIComponent(q);
return false;
}
});
}, 100);
$(window).on('hashchange', function () {
if (window.location.hash === '#/search') {
history.replaceState(null, null, window.location.pathname);
$('body').removeClass('overlay-enabled search-enabled');
$('.overlay, .search-overlay').hide();
}
});
if (window.location.hash === '#/search')
history.replaceState(null, null, window.location.pathname);
});
// ============================================================
// FILTERS FOR SEASON "Запис ігор" WIDE TABLE
// Only runs on season/tournament pages (not full-width stat pages)
// ============================================================
$(function () {
// Skip on full-width stat pages
if (isMccFullwidth()) return;
var $table = $('.wikitable.wide-table').first();
if (!$table.length) $table = $('.wikitable.full-width').first();
if (!$table.length) return;
var $rows = $table.find('tr').filter(function () {
return $(this).find('td').length > 0;
});
if (!$rows.length) return;
var results = [];
$rows.each(function () {
var tds = $(this).find('td');
var r = tds.eq(tds.length - 2).text().trim();
if (r && results.indexOf(r) === -1) results.push(r);
});
var resOpts = '<option value="">Всі результати</option>';
results.forEach(function (r) {
resOpts += '<option value="' + r + '">' + r + '</option>';
});
var $bar = $(
'<div class="mcc-games-filters">' +
'<div class="mcc-filter-group">' +
'<span class="mcc-filter-label">Результат</span>' +
'<select class="mcc-filter-select sg-res">' + resOpts + '</select>' +
'</div>' +
'<div class="mcc-filter-group mcc-duration-group">' +
'<span class="mcc-filter-label">Макс. тривалість</span>' +
'<div class="mcc-slider-wrap">' +
'<input type="range" class="mcc-duration-range sg-dur" min="30" max="65" value="65" step="1">' +
'<span class="mcc-slider-val sg-durv">65:00</span>' +
'</div>' +
'</div>' +
'<span class="mcc-filter-count sg-cnt"></span>' +
'</div>'
);
var $target = $table.closest('.mcc-scroll-outer, .mcc-scroll-inner');
($target.length ? $target : $table).before($bar);
function toMins(s) {
s = (s || '').trim();
var m = s.match(/(\d+)\s*хв\s*(\d+)/);
if (m) return parseInt(m[1]) + parseInt(m[2]) / 60;
m = s.match(/^(\d+):(\d+)$/);
if (m) return parseInt(m[1]) + parseInt(m[2]) / 60;
return 999;
}
function doFilter() {
var res = $bar.find('.sg-res').val();
var dur = parseInt($bar.find('.sg-dur').val());
var n = 0;
$rows.each(function () {
var tds = $(this).find('td');
var timeVal = tds.eq(0).text().trim();
var resVal = tds.eq(tds.length - 2).text().trim();
var show = (!res || resVal === res) && toMins(timeVal) <= dur;
$(this).toggle(show);
if (show) n++;
});
$bar.find('.sg-cnt').text(n + '\u00a0ігор');
}
$bar.find('.sg-dur').on('input', function () {
var v = parseInt($(this).val());
$bar.find('.sg-durv').text(v + ':00');
$(this).css('background-size', ((v - 30) / 35 * 100) + '% 100%');
doFilter();
});
$bar.find('.sg-res').on('change', doFilter);
$bar.find('.sg-cnt').text($rows.length + '\u00a0ігор');
$bar.find('.sg-dur').css('background-size', '100% 100%');
});
// ============================================================
// 17. BANNER SEARCH
// ============================================================
$(function () {
// ── MEDALS helper — adds gold/silver/bronze circles to first-column cells ──
function addMedals($t) {
$t.find('tbody tr, tr').filter(function () {
return $(this).find('td').length >= 2;
}).each(function () {
var $tds = $(this).find('td');
var raw = $tds.eq(0).text().trim();
var n = parseInt(raw);
if (isNaN(n) || n < 1 || n > 3) return;
if ($tds.eq(0).find('[class^="mcc-rank"]').length) return;
$tds.eq(0).html('<span class="mcc-rank-' + n + '">' + n + '</span>');
});
}
// ── Tables that need medals: closedcups, rank, getnames, wide rank (Період/Фіналіст) ──
$('.content .mw-parser-output .wikitable').each(function () {
var $t = $(this);
if ($t.hasClass('mcc-rating-table')) return;
var $ths = $t.find('tr').first().find('th');
if (!$ths.length) return;
var h0 = $ths.eq(0).text().trim();
// Wide rank tables: № + more than 5 columns (Період, Фіналіст)
if (h0.indexOf('№') !== -1 && $ths.length > 5) {
addMedals($t);
return;
}
// Explicit class-based
if ($t.hasClass('closedcups') || $t.hasClass('rank')) {
addMedals($t);
}
});
// ── Get Names tables — auto-number ──
$('.content .mw-parser-output .wikitable.getnames').each(function () {
var $t = $(this);
var $headerRow = $t.find('tr').first();
if ($headerRow.find('th').first().text().trim() === '№') return;
$headerRow.prepend('<th style="width:38px;text-align:center">№</th>');
var n = 1;
$t.find('tbody tr, tr').filter(function () {
return $(this).find('td').length > 0;
}).each(function () {
var badge = n <= 3
? '<span class="mcc-rank-' + n + '">' + n + '</span>'
: ('<span style="display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;font-size:12px;font-weight:600">' + n + '</span>');
$(this).prepend('<td style="text-align:center;padding:10px 8px">' + badge + '</td>');
n++;
});
});
// ── Season final table — auto-number (Пан/Пані | І | П | ДБ | Дія | Σ) ──
$('.content .mw-parser-output .wikitable').each(function () {
var $t = $(this);
if ($t.hasClass('mcc-rating-table') || $t.hasClass('mcc-final-table')) return;
var $ths = $t.find('tr').first().find('th');
if (!$ths.length) return;
var h0 = $ths.eq(0).text().trim();
var hLast = $ths.last().text().trim();
var isFinal = (h0 === 'Пан/Пані' || h0 === 'Пан' || h0 === 'Гравець')
&& hLast === 'Σ';
if (!isFinal) return;
$t.addClass('mcc-final-table');
$t.find('tr').first().prepend('<th style="width:36px;text-align:center">#</th>');
var n = 1;
$t.find('tbody tr').each(function () {
var $tds = $(this).find('td');
if (!$tds.length) return;
var badge = n <= 3
? '<span class="mcc-rank-' + n + '">' + n + '</span>'
: n;
$(this).prepend('<td style="text-align:center;padding:10px 8px">' + badge + '</td>');
n++;
});
});
});
// ============================================================
// SEASON RATING TABLE — medals + color extremes for №|Пан/Пані|Σ|І|%
// ============================================================
$(function () {
if (isMccFullwidth()) return;
function processRatingTable($t) {
var headers = [];
$t.find('tr').first().find('th').each(function () {
headers.push($(this).text().trim());
});
if (headers[0] !== '№') return;
if (headers.length < 4) return;
var hasSigma = (headers[2] && headers[2].indexOf('Σ') !== -1);
if (!hasSigma) return;
if (headers.length > 8) return; // wide tables handled elsewhere
var iGames = 3;
var iPct = 4;
var hasI = headers[iGames] === 'І';
var hasPct = headers[iPct] === '%';
$t.addClass('mcc-rating-table');
var $rows = $t.find('tbody tr').filter(function () {
return $(this).find('td').length >= 4;
});
if (!$rows.length) return;
// Medals top 3
$rows.each(function () {
var $tds = $(this).find('td');
var n = parseInt($tds.eq(0).text().trim());
if (n >= 1 && n <= 3) {
$tds.eq(0).html('<span class="mcc-rank-' + n + '">' + n + '</span>');
}
});
// Σ white
$rows.each(function () {
$(this).find('td').eq(2).css({ color: '#ffffff', fontWeight: '700' });
});
// Games extremes
if (hasI) {
var gVals = [];
$rows.each(function () {
var v = parseInt($(this).find('td').eq(iGames).text().trim());
if (!isNaN(v) && v > 0) gVals.push(v);
});
if (gVals.length > 1) {
var gMax = Math.max.apply(null, gVals);
var gMin = Math.min.apply(null, gVals);
$rows.each(function () {
var $td = $(this).find('td').eq(iGames);
var v = parseInt($td.text().trim());
if (isNaN(v)) return;
if (v === gMax) $td.addClass('mcc-games-hi');
else if (v === gMin) $td.addClass('mcc-games-lo');
});
}
}
// WR extremes
if (hasPct) {
var pVals = [];
$rows.each(function () {
var v = parseFloat($(this).find('td').eq(iPct).text().trim().replace('%',''));
if (!isNaN(v)) pVals.push(v);
});
if (pVals.length > 1) {
var pMax = Math.max.apply(null, pVals);
var pMin = Math.min.apply(null, pVals);
$rows.each(function () {
var $td = $(this).find('td').eq(iPct);
var v = parseFloat($td.text().trim().replace('%',''));
if (isNaN(v)) return;
if (v === pMax) $td.addClass('mcc-wr-hi');
else if (v === pMin) $td.addClass('mcc-wr-lo');
});
}
}
}
$('.content .mw-parser-output .wikitable').each(function () {
processRatingTable($(this));
});
});
// ============================================================
// PLAYER GAMES FILTERS — pre-rendered tab (server-side FetchData)
// ============================================================
$(function () {
var $content = $('#tab-games');
if (!$content.length) return;
var $table = $content.find('table').first();
if (!$table.length) return;
applyWinrateColors($content);
applyRolePills($content);
buildGamesFilters($content, $table);
});
// ============================================================
// 17. BANNER SEARCH
// ============================================================
$(function () {
var $searchInput = $('#searchInput');
if (!$searchInput.length) return;
$searchInput.on('keydown', function (e) {
if (e.key === 'Enter') {
var q = $(this).val().trim();
if (q) window.location.href = '/index.php?search=' + encodeURIComponent(q);
}
});
});
// ============================================================
// HOME PAGE — collapsible panels + clickable rows
// ============================================================
$(function () {
// Expand/collapse
var GRID_PANELS = ['seasons-panel', 'gn-panel', 'closed-panel'];
$('[data-toggle]').on('click', function () {
var $hd = $(this);
var targetId = $hd.data('toggle');
var $bd = $('#' + targetId);
if (!$bd.length) return;
var isGridPanel = GRID_PANELS.indexOf(targetId) !== -1;
var isDesktop = $(window).width() >= 1000;
if (isGridPanel && isDesktop) {
// Collapse/expand all 3 grid panels together
var collapsed = $bd.hasClass('collapsed');
GRID_PANELS.forEach(function(id) {
$('#' + id).toggleClass('collapsed', !collapsed);
$('[data-toggle="' + id + '"]').toggleClass('collapsed', !collapsed);
});
} else {
var collapsed = $bd.hasClass('collapsed');
$bd.toggleClass('collapsed', !collapsed);
$hd.toggleClass('collapsed', !collapsed);
}
});
// Clickable rows / cards — navigate on click, ignore inner link clicks
$(document).on('click', '.mcc-nav', function (e) {
if ($(e.target).closest('a').length) return; // let wiki links work normally
var href = $(this).data('href');
if (href) window.location.href = href;
});
// Cursor pointer for nav elements
$('.mcc-nav').css('cursor', 'pointer');
});