MediaWiki:Common.js
MediaWiki interface page
More actions
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/* Any JavaScript here will be loaded for all users on every page load. */
/* ==========================================================
REMOVE DECIMALS (.0) FROM CARGO TABLES
========================================================== */
$(document).ready(function() {
// Target only cells inside the auto-rank table
$('.auto-rank td').each(function() {
var text = $(this).text();
// If the text looks like a number ending in .0 (e.g., "93.0")
if (/^\d+\.0$/.test(text)) {
// Replace it with just the integer part
$(this).text(parseInt(text));
}
});
});
/* ==========================================================
INDIAN CURRENCY FORMATTER (₹ Symbol Version)
========================================================== */
$(document).ready(function() {
$('.indian-currency').each(function() {
// 1. Get text and clean it (Remove commas AND existing ₹ symbol)
var rawNum = $(this).text().trim().replace(/,/g, '').replace(/₹/g, '');
// 2. Convert to a real number
var number = parseFloat(rawNum);
// 3. Check if it's a valid number
if (!isNaN(number)) {
// 4. Format to Indian Style (en-IN) - Automatically adds ₹
var formatted = number.toLocaleString('en-IN', {
style: 'currency',
currency: 'INR',
maximumFractionDigits: 0
});
// 5. Apply directly (This keeps the ₹ symbol)
$(this).text(formatted);
}
});
});
/* ========================================================
UNIVERSAL FILTER SYSTEM (Centralized Logic)
======================================================== */
$(function() {
// MASTER FUNCTION: updates the entire wrapper based on current button states
function refreshWrapperState($wrapper) {
// 1. Get the current settings
var activeView = $wrapper.find('.filter-btn[data-filter-type^="view_mode"].active').attr('data-value') || 'overall';
var activeGroup = ($wrapper.find('.filter-btn[data-filter-type^="group_"].active').attr('data-value') || 'all').toString().trim();
var filterType = $wrapper.find('.filter-btn[data-filter-type^="view_mode"].active').attr('data-filter-type'); // e.g., view_mode_The Grind
// 2. Hide all Views (Overall & Matches), then Show the Active One
$wrapper.find('.filterable-content[data-type^="view_mode"]').hide();
// Find the specific view (e.g., Match 2 table)
// We use the filterType to ensure we don't accidentally grab a view from a different stage
var $activeTable = $wrapper.find('.filterable-content[data-type="' + filterType + '"][data-value="' + activeView + '"]');
$activeTable.fadeIn(100);
// 3. Apply Group Filter to the rows INSIDE the active table
// We target ANY row (tr) that has a data-value attribute
var $rows = $activeTable.find('tr[data-value]');
if (activeGroup === 'all') {
$rows.show();
} else {
$rows.each(function() {
var $row = $(this);
var rowGroup = ($row.attr('data-value') || "").toString().trim();
if (rowGroup === activeGroup) {
$row.show();
} else {
$row.hide();
}
});
}
}
// CLICK HANDLER
$(document).on('click', '.filter-btn', function() {
var $btn = $(this);
var filterType = $btn.attr('data-filter-type');
var value = $btn.attr('data-value');
// Visual Update (Active State)
$btn.siblings('.filter-btn').removeClass('active');
$btn.addClass('active');
// LOGIC A: MASTER STAGE (Big Tabs like "Round 1")
if (filterType.indexOf('master_stage') !== -1) {
$('.filterable-content[data-type="' + filterType + '"]').hide();
$('.filterable-content[data-type="' + filterType + '"][data-value="' + value + '"]').fadeIn(200);
// Optional: When switching stages, trigger a refresh on that stage's wrapper
var $newStage = $('.filterable-content[data-type="' + filterType + '"][data-value="' + value + '"]');
var $innerWrapper = $newStage.find('.standings-wrapper');
if ($innerWrapper.length) refreshWrapperState($innerWrapper);
// LOGIC B: STANDINGS FILTERS (View or Group)
} else {
// Just run the master refresh on the parent wrapper
var $wrapper = $btn.closest('.standings-wrapper');
refreshWrapperState($wrapper);
}
});
// AUTO-INIT
setTimeout(function() {
if (!$('.filter-btn[data-filter-type="master_stage_selector"].active').length) {
$('.filter-btn[data-filter-type="master_stage_selector"]').first().click();
}
}, 500);
});
/* ==========================================================
SMART HORIZONTAL SCROLL (Mouse Wheel)
========================================================== */
mw.loader.using(['jquery'], function () {
$(function() {
$('.standings-wrapper').on('wheel', function(e) {
var container = this;
var delta = e.originalEvent.deltaY;
// Only act if the table is wider than the screen
if (container.scrollWidth > container.clientWidth) {
// Calculate current scroll position vs max scroll
var scrollLeft = container.scrollLeft;
var scrollWidth = container.scrollWidth;
var clientWidth = container.clientWidth;
var maxScroll = scrollWidth - clientWidth;
// Check if we are at the edges (allow 1px buffer for browsers)
var atRightEdge = (scrollLeft >= maxScroll - 1);
var atLeftEdge = (scrollLeft <= 1);
// LOGIC:
// 1. If scrolling RIGHT (delta > 0) and NOT at end -> Scroll Table
// 2. If scrolling LEFT (delta < 0) and NOT at start -> Scroll Table
// 3. Otherwise -> Do nothing (Let page scroll naturally)
if ((delta > 0 && !atRightEdge) || (delta < 0 && !atLeftEdge)) {
container.scrollLeft += delta;
e.preventDefault(); // Stop page scroll ONLY when table is moving
}
}
});
});
});
/* Active Tier Filter Highlight */
$(function() {
var urlParams = new URLSearchParams(window.location.search);
var tier = urlParams.get('tier');
var $buttons = $('#tier-filters .filter-btn');
$buttons.removeClass('active');
if (tier) {
$('#tier-filters .filter-btn[data-tier="' + tier + '"]').addClass('active');
} else {
$buttons.first().addClass('active');
}
});
/* ==========================================================
LIVE UPDATE NOTIFIER (Secure Version)
Checks every 60 seconds if the page has a newer revision.
========================================================== */
$(document).ready(function() {
// Only run on View mode
if (mw.config.get('wgAction') !== 'view') return;
var currentRevId = mw.config.get('wgCurRevisionId');
var pageTitle = mw.config.get('wgPageName');
function checkForUpdates() {
new mw.Api().get({
action: 'query',
prop: 'info',
titles: pageTitle,
indexpageids: 1
}).done(function(data) {
var page = data.query.pages[data.query.pageids[0]];
// If server has a newer ID
if (page.lastrevid > currentRevId) {
showUpdateToast();
}
});
}
function showUpdateToast() {
// Prevent duplicate buttons
if ($('#update-notification').length > 0) return;
// 1. Create the Button Safely (No raw HTML strings)
var $icon = $('<i>').addClass('fa-solid fa-rotate').css('margin-right', '8px');
var $btn = $('<div>')
.attr('id', 'update-notification')
.text(' New updates available. Click to refresh.')
.prepend($icon)
.css({
'position': 'fixed',
'bottom': '30px',
'left': '50%',
'transform': 'translateX(-50%)',
'background-color': '#00509d',
'color': 'white',
'padding': '12px 25px',
'border-radius': '50px',
'cursor': 'pointer',
'box-shadow': '0 4px 15px rgba(0,0,0,0.3)',
'z-index': '9999',
'font-family': 'sans-serif',
'font-weight': 'bold',
'display': 'flex',
'align-items': 'center'
})
.on('click', function() {
location.reload();
});
// 2. Add to page with animation
$('body').append($btn);
$btn.hide().fadeIn(500).css('bottom', '30px');
}
// Run check every 60 seconds
setInterval(checkForUpdates, 60000);
});
/* ==========================================================
CLEAN PAGE TITLES (Fixes H1, Sticky Header, AND Browser Tab)
Turns "BGMI/Teams/Orangutan" -> "Orangutan"
========================================================== */
$(function() {
var pageTitle = mw.config.get('wgTitle');
// Only run if we are deep in a structure (contains slash)
if (pageTitle.indexOf('/') !== -1) {
// Get the text after the last slash and replace underscores
var cleanTitle = pageTitle.split('/').pop().replace(/_/g, ' ');
// 1. Fix the On-Page Header (H1)
$('.firstHeading').text(cleanTitle);
// 2. Fix the Citizen Skin Sticky Header
$('.citizen-header__title').text(cleanTitle);
// 3. Fix the Browser Tab Title
var siteName = mw.config.get('wgSiteName') || 'eSportsAmaze';
document.title = cleanTitle + ' - ' + siteName;
}
});
/* ==========================================================
MANUAL AD INJECTION (Safe Mode / Firewall Bypass)
========================================================== */
$(document).ready(function() {
// 1. YOUR AD DETAILS
var clientID = "ca-pub-9380457493130804";
var bottomSlotID = "9418872470";
var topSlotID = "1308895278";
// 2. Build the Bottom/Sidebar Ad Unit (Standard Size)
var $bottomAdContainer = $('<div>')
.addClass('wiki-ad-bottom')
.css({
'margin-top': '20px',
'text-align': 'center',
'min-height': '250px'
});
var $bottomLabel = $('<div>')
.css({ 'font-size': '10px', 'color': '#999', 'margin-bottom': '5px' })
.text('ADVERTISEMENT');
var $bottomIns = $('<ins>')
.addClass('adsbygoogle')
.css('display', 'block')
.attr('data-ad-client', clientID)
.attr('data-ad-slot', bottomSlotID)
.attr('data-ad-format', 'auto')
.attr('data-full-width-responsive', 'true');
$bottomAdContainer.append($bottomLabel, $bottomIns);
// 3. Build the Top Mobile Ad Unit (Horizontal Only)
var $topAdContainer = $('<div>')
.addClass('wiki-ad-top')
.css({
'margin-bottom': '15px',
'text-align': 'center',
'height': '70px',
'max-height': '70px',
'overflow': 'hidden'
});
var $topLabel = $('<div>')
.css({ 'font-size': '10px', 'color': '#999', 'margin-bottom': '3px', 'line-height': '10px' })
.text('ADVERTISEMENT');
var $topIns = $('<ins>')
.addClass('adsbygoogle')
.css({ 'display': 'inline-block', 'width': '320px', 'height': '50px' })
.attr('data-ad-client', clientID)
.attr('data-ad-slot', topSlotID);
$topAdContainer.append($topLabel, $topIns);
// 4. Inject into the Page based strictly on device width
var $toc = $('#citizen-toc');
var $footer = $('#catlinks');
var $content = $('#mw-content-text');
if ($(window).width() > 1000) {
// === DESKTOP LOGIC (1 Ad Only) ===
if ($toc.length > 0) {
// If TOC exists, put it after the TOC
$toc.after($bottomAdContainer);
} else {
// If no TOC on desktop, put it at the bottom
if ($footer.length > 0) $footer.before($bottomAdContainer);
else $content.after($bottomAdContainer);
}
// Push AdSense once
try { (adsbygoogle = window.adsbygoogle || []).push({}); } catch (e) { console.log(e); }
} else {
// === MOBILE LOGIC (2 Ads) ===
// Inject Top Ad above content
$content.before($topAdContainer);
// Inject Bottom Ad at the end
if ($footer.length > 0) $footer.before($bottomAdContainer);
else $content.after($bottomAdContainer);
// Push AdSense twice for two ads
try { (adsbygoogle = window.adsbygoogle || []).push({}); } catch (e) { console.log(e); }
try { (adsbygoogle = window.adsbygoogle || []).push({}); } catch (e) { console.log(e); }
}
});
/* ==========================================================
AUTO TIMEZONE CONVERTER (For Calendar & Schedules)
Converts elements with <span class="local-time" data-utc="...">
========================================================== */
$(function() {
$('.local-time').each(function() {
var utcString = $(this).attr('data-utc');
if (!utcString) return;
var dateObj = new Date(utcString);
// Check if date is valid
if (isNaN(dateObj)) return;
// Format to User's Local Time (e.g., "2:30 PM")
var localTime = new Intl.DateTimeFormat('default', {
hour: 'numeric',
minute: '2-digit',
timeZoneName: 'short'
}).format(dateObj);
// Update the text
$(this).text(localTime);
});
});
/* ==========================================================
INTERACTIVE ESPORTS CALENDAR ENGINE (Stream Button)
========================================================== */
$(function() {
$('.esports-calendar-container').each(function() {
var $container = $(this);
var $dataNode = $container.find('.calendar-data');
if (!$dataNode.length) return;
var scheduleData = {};
try { scheduleData = JSON.parse($dataNode.text()); }
catch(e) { console.error("Calendar Parse Error", e); return; }
var allDates = Object.keys(scheduleData).sort();
if (allDates.length === 0) return;
var today = new Date();
var todayStr = today.getFullYear() + "-" + String(today.getMonth()+1).padStart(2,'0') + "-" + String(today.getDate()).padStart(2,'0');
var targetDate = null;
if (scheduleData[todayStr]) {
targetDate = todayStr;
} else {
var futureDates = allDates.filter(function(d) { return d > todayStr; });
if (futureDates.length > 0) {
targetDate = futureDates[0];
} else {
targetDate = allDates[allDates.length - 1];
}
}
var currentMonth = new Date(targetDate).getMonth();
var currentYear = new Date(targetDate).getFullYear();
var $datesGrid = $container.find('.calendar-grid-dates');
var $monthYearLabel = $container.find('.cal-month-year');
var $detailsContent = $container.find('.cal-details-content');
var monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
function formatLocalTime(timeStr) {
var d;
if (timeStr.indexOf("T") !== -1) { d = new Date(timeStr); }
else { d = new Date("2000-01-01T" + timeStr + ":00Z"); }
if (isNaN(d)) return timeStr;
return new Intl.DateTimeFormat('en-US', { hour: 'numeric', minute: '2-digit', hour12: true }).format(d);
}
function renderCalendar() {
$datesGrid.empty();
$monthYearLabel.text(monthNames[currentMonth] + " " + currentYear);
var firstDayRaw = new Date(currentYear, currentMonth, 1).getDay();
var firstDay = (firstDayRaw === 0) ? 6 : firstDayRaw - 1;
var daysInMonth = new Date(currentYear, currentMonth + 1, 0).getDate();
for (var i = 0; i < firstDay; i++) { $datesGrid.append('<div class="cal-day empty"></div>'); }
for (var day = 1; day <= daysInMonth; day++) {
var dateStr = currentYear + "-" + String(currentMonth + 1).padStart(2, '0') + "-" + String(day).padStart(2, '0');
var $dayDiv = $('<div class="cal-day"></div>').text(day).attr('data-date', dateStr);
if (scheduleData[dateStr]) {
$dayDiv.addClass('has-events');
var dayStages = [...new Set(scheduleData[dateStr].map(function(e){ return e.stage || "Matches"; }))];
var stageText = dayStages.length === 1 ? dayStages[0] : dayStages.length + " Stages";
$dayDiv.append('<div class="cal-day-stage">' + stageText + '</div>');
$dayDiv.on('click', function() {
$container.find('.cal-day').removeClass('active');
$(this).addClass('active');
showDetails($(this).attr('data-date'));
});
}
$datesGrid.append($dayDiv);
}
var $targetElem = $datesGrid.find('.cal-day[data-date="'+targetDate+'"]');
if ($targetElem.length) { $targetElem.addClass('active'); showDetails(targetDate); }
}
function showDetails(dateStr) {
$detailsContent.empty().show();
var dayEvents = scheduleData[dateStr];
if (!dayEvents) return;
var stages = {};
dayEvents.forEach(function(row) {
var sName = row.stage || "Matches";
if (!stages[sName]) stages[sName] = [];
stages[sName].push(row);
});
for (var stageName in stages) {
var stageRows = stages[stageName];
// MULTI-STREAM LOGIC
var streamData = stageRows[0].stream || "";
var streams = {};
try {
if (streamData.startsWith("{")) {
streams = JSON.parse(streamData);
} else if (streamData) {
streams["Watch"] = streamData; // Fallback for old data
}
} catch(e) {}
var streamBtns = "";
for (var label in streams) {
var url = streams[label];
if (!url.match(/^https?:\/\//)) url = "https://" + url;
// Auto-Detect Icon (Twitch vs YouTube)
var icon = '<i class="fa-brands fa-youtube"></i>';
if (url.indexOf("twitch.tv") !== -1) {
icon = '<i class="fa-brands fa-twitch"></i>';
}
streamBtns += '<a href="' + url + '" target="_blank" class="cal-stream-btn">' + icon + ' ' + label + '</a>';
}
var streamHtml = streamBtns ? '<div class="cal-stream-group">' + streamBtns + '</div>' : '';
$detailsContent.append('<div class="cal-stage-header-wrap"><div class="cal-stage-header">' + stageName + '</div>' + streamHtml + '</div>');
stageRows.forEach(function(row) {
row.data.forEach(function(match) {
var localTime = formatLocalTime(match.time);
var html = '<div class="cal-match-row"><div class="cal-m-time">' + localTime + '</div>';
if (row.match_type === "BR") {
var map = match.map || "TBD";
var grp = match.group ? '<span class="cal-m-group">' + match.group + '</span>' : '';
html += '<div class="cal-m-info"><span class="cal-m-map">' + map + '</span>' + grp + '</div>';
} else {
var t1 = match.t1 || "TBD", t2 = match.t2 || "TBD";
var grp = match.group ? '<span class="cal-m-group">' + match.group + '</span>' : '';
html += '<div class="cal-m-info"><span class="cal-m-teams">' + t1 + ' vs ' + t2 + '</span>' + grp + '</div>';
}
html += '</div>';
$detailsContent.append(html);
});
});
}
}
$container.find('.cal-prev').on('click', function() {
currentMonth--; if (currentMonth < 0) { currentMonth = 11; currentYear--; }
renderCalendar();
});
$container.find('.cal-next').on('click', function() {
currentMonth++; if (currentMonth > 11) { currentMonth = 0; currentYear++; }
renderCalendar();
});
renderCalendar();
});
});
/* ── COMMON.JS: dropdown toggle ── */
$(document).on('click', '.espa-dropdown-trigger', function(e) {
e.stopPropagation();
var $t = $(this);
var $menu = $t.siblings('.espa-dropdown-menu');
$t.toggleClass('open');
$menu.toggleClass('open');
});
$(document).on('click', function() {
$('.espa-dropdown-trigger').removeClass('open');
$('.espa-dropdown-menu').removeClass('open');
});
/* ==============================================================
BRACKET SYSTEM v2.0 JS — Add to MediaWiki:Common.js
============================================================== */
$(function () {
/* ── Open modal on match card click ─────────────────────── */
$(document).on('click', '.bk-match-clickable', function (e) {
e.stopPropagation();
var $m = $(this);
var team1 = $m.attr('data-team1') || 'TBD';
var team2 = $m.attr('data-team2') || 'TBD';
var score1 = $m.attr('data-score1') || '';
var score2 = $m.attr('data-score2') || '';
var bo = $m.attr('data-bo') || '';
var date = $m.attr('data-date') || '';
var casters = $m.attr('data-casters') || '';
var vod = $m.attr('data-vod') || '';
var notes = $m.attr('data-notes') || '';
var $overlay = $m.closest('.bk-root').find('.bk-modal-overlay');
if (!$overlay.length) { return; }
$overlay.find('.bk-modal-t1').text(team1);
$overlay.find('.bk-modal-t2').text(team2);
var hasScore = (score1 !== '' || score2 !== '');
$overlay.find('.bk-modal-s1').text(hasScore ? (score1 || '0') : '\u2014');
$overlay.find('.bk-modal-s2').text(hasScore ? (score2 || '0') : '\u2014');
$overlay.find('.bk-modal-dash').toggle(hasScore);
/* Best-of */
var $boRow = $overlay.find('.bk-modal-bo').empty();
if (bo) {
$boRow.append($('<b>').text('Format')).append(' Best of ' + bo);
}
/* Date — local timezone */
var $dateRow = $overlay.find('.bk-modal-date').empty();
if (date) {
var dateText = date;
try {
var clean = date.indexOf('T') !== -1 ? date : date.replace(' ', 'T');
var d = new Date(clean);
if (!isNaN(d.getTime())) {
dateText = new Intl.DateTimeFormat('en-IN', {
year: 'numeric', month: 'short', day: 'numeric',
hour: 'numeric', minute: '2-digit',
timeZoneName: 'short'
}).format(d);
}
} catch (ex) { /* keep raw string */ }
$dateRow.append($('<b>').text('Date')).append(' ' + dateText);
}
/* Casters */
var $casterRow = $overlay.find('.bk-modal-casters').empty();
if (casters) {
$casterRow.append($('<b>').text('Casters')).append(' ' + casters);
}
/* Notes */
var $notesRow = $overlay.find('.bk-modal-notes').empty();
if (notes) {
$notesRow.append($('<b>').text('Notes')).append(' ' + notes);
}
/* VOD */
var $vodRow = $overlay.find('.bk-modal-vod').empty();
if (vod) {
var $link = $('<a>').attr('href', vod).attr('target', '_blank')
.attr('rel', 'noopener noreferrer').text('Watch VOD');
$vodRow.append($('<b>').text('VOD')).append(' ').append($link);
}
/* Score win/lose highlight */
var win = $m.attr('data-winner') || '';
$overlay.find('.bk-modal-s1').removeClass('bk-modal-win bk-modal-lose');
$overlay.find('.bk-modal-s2').removeClass('bk-modal-win bk-modal-lose');
if (win === '1' || win === team1) {
$overlay.find('.bk-modal-s1').addClass('bk-modal-win');
$overlay.find('.bk-modal-s2').addClass('bk-modal-lose');
} else if (win === '2' || win === team2) {
$overlay.find('.bk-modal-s2').addClass('bk-modal-win');
$overlay.find('.bk-modal-s1').addClass('bk-modal-lose');
}
$overlay.fadeIn(150);
});
/* ── Close: X button ────────────────────────────────────── */
$(document).on('click', '.bk-modal-close', function (e) {
e.stopPropagation();
$('.bk-modal-overlay').fadeOut(150);
});
/* ── Close: click outside modal box ─────────────────────── */
$(document).on('click', '.bk-modal-overlay', function (e) {
if ($(e.target).hasClass('bk-modal-overlay')) {
$('.bk-modal-overlay').fadeOut(150);
}
});
/* ── Close: Escape key ───────────────────────────────────── */
$(document).on('keydown', function (e) {
if (e.key === 'Escape' || e.keyCode === 27) {
$('.bk-modal-overlay').fadeOut(150);
}
});
});
/* ==============================================================
BRACKET SYSTEM v2.0 JS — Add to MediaWiki:Common.js
============================================================== */
$(function () {
/* ── Open modal on match card click ─────────────────────── */
$(document).on('click', '.bk-match-clickable', function (e) {
e.stopPropagation();
var $m = $(this);
var team1 = $m.attr('data-team1') || 'TBD';
var team2 = $m.attr('data-team2') || 'TBD';
var score1 = $m.attr('data-score1') || '';
var score2 = $m.attr('data-score2') || '';
var bo = $m.attr('data-bo') || '';
var date = $m.attr('data-date') || '';
var casters = $m.attr('data-casters') || '';
var vod = $m.attr('data-vod') || '';
var notes = $m.attr('data-notes') || '';
var $overlay = $m.closest('.bk-root').find('.bk-modal-overlay');
if (!$overlay.length) { return; }
$overlay.find('.bk-modal-t1').text(team1);
$overlay.find('.bk-modal-t2').text(team2);
var hasScore = (score1 !== '' || score2 !== '');
$overlay.find('.bk-modal-s1').text(hasScore ? (score1 || '0') : '\u2014');
$overlay.find('.bk-modal-s2').text(hasScore ? (score2 || '0') : '\u2014');
$overlay.find('.bk-modal-dash').toggle(hasScore);
var $boRow = $overlay.find('.bk-modal-bo').empty();
if (bo) {
$boRow.append($('<b>').text('Format')).append(' Best of ' + bo);
}
var $dateRow = $overlay.find('.bk-modal-date').empty();
if (date) {
var dateText = date;
try {
var clean = date.indexOf('T') !== -1 ? date : date.replace(' ', 'T');
var d = new Date(clean);
if (!isNaN(d.getTime())) {
dateText = new Intl.DateTimeFormat('en-IN', {
year: 'numeric', month: 'short', day: 'numeric',
hour: 'numeric', minute: '2-digit',
timeZoneName: 'short'
}).format(d);
}
} catch (ex) {}
$dateRow.append($('<b>').text('Date')).append(' ' + dateText);
}
var $casterRow = $overlay.find('.bk-modal-casters').empty();
if (casters) {
$casterRow.append($('<b>').text('Casters')).append(' ' + casters);
}
var $notesRow = $overlay.find('.bk-modal-notes').empty();
if (notes) {
$notesRow.append($('<b>').text('Notes')).append(' ' + notes);
}
var $vodRow = $overlay.find('.bk-modal-vod').empty();
if (vod) {
var $link = $('<a>').attr('href', vod).attr('target', '_blank')
.attr('rel', 'noopener noreferrer').text('Watch VOD');
$vodRow.append($('<b>').text('VOD')).append(' ').append($link);
}
var win = $m.attr('data-winner') || '';
$overlay.find('.bk-modal-s1').removeClass('bk-modal-win bk-modal-lose');
$overlay.find('.bk-modal-s2').removeClass('bk-modal-win bk-modal-lose');
if (win === '1' || win === team1) {
$overlay.find('.bk-modal-s1').addClass('bk-modal-win');
$overlay.find('.bk-modal-s2').addClass('bk-modal-lose');
} else if (win === '2' || win === team2) {
$overlay.find('.bk-modal-s2').addClass('bk-modal-win');
$overlay.find('.bk-modal-s1').addClass('bk-modal-lose');
}
$overlay.fadeIn(150);
});
$(document).on('click', '.bk-modal-close', function (e) {
e.stopPropagation();
$('.bk-modal-overlay').fadeOut(150);
});
$(document).on('click', '.bk-modal-overlay', function (e) {
if ($(e.target).hasClass('bk-modal-overlay')) {
$('.bk-modal-overlay').fadeOut(150);
}
});
$(document).on('keydown', function (e) {
if (e.key === 'Escape' || e.keyCode === 27) {
$('.bk-modal-overlay').fadeOut(150);
}
});
});
/* ==============================================================
BRACKET SYSTEM v2.0 JS — Add to MediaWiki:Common.js
============================================================== */
$(function () {
/* ── Open modal on match card click ─────────────────────── */
$(document).on('click', '.bk-match-clickable', function (e) {
e.stopPropagation();
var $m = $(this);
var team1 = $m.attr('data-team1') || 'TBD';
var team2 = $m.attr('data-team2') || 'TBD';
var score1 = $m.attr('data-score1') || '';
var score2 = $m.attr('data-score2') || '';
var bo = $m.attr('data-bo') || '';
var date = $m.attr('data-date') || '';
var casters = $m.attr('data-casters') || '';
var vod = $m.attr('data-vod') || '';
var notes = $m.attr('data-notes') || '';
var $overlay = $m.closest('.bk-root').find('.bk-modal-overlay');
if (!$overlay.length) { return; }
$overlay.find('.bk-modal-t1').text(team1);
$overlay.find('.bk-modal-t2').text(team2);
var hasScore = (score1 !== '' || score2 !== '');
$overlay.find('.bk-modal-s1').text(hasScore ? (score1 || '0') : '\u2014');
$overlay.find('.bk-modal-s2').text(hasScore ? (score2 || '0') : '\u2014');
$overlay.find('.bk-modal-dash').toggle(hasScore);
var $boRow = $overlay.find('.bk-modal-bo').empty();
if (bo) {
$boRow.append($('<b>').text('Format')).append(' Best of ' + bo);
}
var $dateRow = $overlay.find('.bk-modal-date').empty();
if (date) {
var dateText = date;
try {
var clean = date.indexOf('T') !== -1 ? date : date.replace(' ', 'T');
var d = new Date(clean);
if (!isNaN(d.getTime())) {
dateText = new Intl.DateTimeFormat('en-IN', {
year: 'numeric', month: 'short', day: 'numeric',
hour: 'numeric', minute: '2-digit',
timeZoneName: 'short'
}).format(d);
}
} catch (ex) {}
$dateRow.append($('<b>').text('Date')).append(' ' + dateText);
}
var $casterRow = $overlay.find('.bk-modal-casters').empty();
if (casters) {
$casterRow.append($('<b>').text('Casters')).append(' ' + casters);
}
var $notesRow = $overlay.find('.bk-modal-notes').empty();
if (notes) {
$notesRow.append($('<b>').text('Notes')).append(' ' + notes);
}
var $vodRow = $overlay.find('.bk-modal-vod').empty();
if (vod) {
var $link = $('<a>').attr('href', vod).attr('target', '_blank')
.attr('rel', 'noopener noreferrer').text('Watch VOD');
$vodRow.append($('<b>').text('VOD')).append(' ').append($link);
}
var win = $m.attr('data-winner') || '';
$overlay.find('.bk-modal-s1').removeClass('bk-modal-win bk-modal-lose');
$overlay.find('.bk-modal-s2').removeClass('bk-modal-win bk-modal-lose');
if (win === '1' || win === team1) {
$overlay.find('.bk-modal-s1').addClass('bk-modal-win');
$overlay.find('.bk-modal-s2').addClass('bk-modal-lose');
} else if (win === '2' || win === team2) {
$overlay.find('.bk-modal-s2').addClass('bk-modal-win');
$overlay.find('.bk-modal-s1').addClass('bk-modal-lose');
}
$overlay.fadeIn(150);
});
$(document).on('click', '.bk-modal-close', function (e) {
e.stopPropagation();
$('.bk-modal-overlay').fadeOut(150);
});
$(document).on('click', '.bk-modal-overlay', function (e) {
if ($(e.target).hasClass('bk-modal-overlay')) {
$('.bk-modal-overlay').fadeOut(150);
}
});
$(document).on('keydown', function (e) {
if (e.key === 'Escape' || e.keyCode === 27) {
$('.bk-modal-overlay').fadeOut(150);
}
});
});
/* ================================================================
BRACKET CONNECTOR LINES
SVG drawn after layout — measures real card positions
================================================================ */
$(function () {
var LINE_COLOR = '#94a3b8';
var LINE_WIDTH = '2';
var svgNS = 'http://www.w3.org/2000/svg';
/* Helper: create an SVG line element */
function makeLine(x1, y1, x2, y2) {
var l = document.createElementNS(svgNS, 'line');
l.setAttribute('x1', Math.round(x1));
l.setAttribute('y1', Math.round(y1));
l.setAttribute('x2', Math.round(x2));
l.setAttribute('y2', Math.round(y2));
l.setAttribute('stroke', LINE_COLOR);
l.setAttribute('stroke-width', LINE_WIDTH);
return l;
}
/* Helper: get centre-right point using getBoundingClientRect for accuracy */
function rightMid($el, $parent) {
var er = $el[0].getBoundingClientRect();
var pr = $parent[0].getBoundingClientRect();
return {
x: er.right - pr.left,
y: er.top - pr.top + (er.height / 2)
};
}
/* Helper: get centre-left point using getBoundingClientRect for accuracy */
function leftMid($el, $parent) {
var er = $el[0].getBoundingClientRect();
var pr = $parent[0].getBoundingClientRect();
return {
x: er.left - pr.left,
y: er.top - pr.top + (er.height / 2)
};
}
/* Helper: inject a fresh SVG into a container */
function makeSVG($container) {
$container.find('.bk-connector-svg').remove();
var cr = $container[0].getBoundingClientRect();
var svg = document.createElementNS(svgNS, 'svg');
svg.setAttribute('class', 'bk-connector-svg');
svg.setAttribute('width', cr.width);
svg.setAttribute('height', cr.height);
svg.style.position = 'absolute';
svg.style.top = '0';
svg.style.left = '0';
svg.style.overflow = 'visible';
svg.style.pointerEvents = 'none';
svg.style.zIndex = '0';
$container.prepend(svg);
return svg;
}
/* ── GSL ─────────────────────────────────────────────────── */
function drawGSL($root) {
var $q1 = $root.find('.bk-gsl-top .bk-match').first();
var $elim = $root.find('.bk-gsl-bot .bk-match').first();
var $q2 = $root.find('.bk-gsl-mid .bk-match').first();
var $fin = $root.find('.bk-gsl-final .bk-match').first();
if (!$q1.length || !$elim.length || !$q2.length || !$fin.length) { return; }
var svg = makeSVG($root);
/* Points */
var q1R = rightMid($q1, $root);
var elimR = rightMid($elim, $root);
var q2L = leftMid($q2, $root);
var q2R = rightMid($q2, $root);
var finL = leftMid($fin, $root);
/* Midpoint X where the vertical join sits between col1 and col2 */
var joinX = q2L.x - 10;
/* Q1 horizontal → join */
svg.appendChild(makeLine(q1R.x, q1R.y, joinX, q1R.y));
/* Elim horizontal → join */
svg.appendChild(makeLine(elimR.x, elimR.y, joinX, elimR.y));
/* Vertical connecting Q1 and Elim arms */
svg.appendChild(makeLine(joinX, q1R.y, joinX, elimR.y));
/* Horizontal from vertical midpoint → Q2 left */
var vMidY = (q1R.y + elimR.y) / 2;
svg.appendChild(makeLine(joinX, vMidY, q2L.x, vMidY));
/* Q2 → Grand Final (step connector if heights differ) */
var midX = q2R.x + (finL.x - q2R.x) / 2;
svg.appendChild(makeLine(q2R.x, q2R.y, midX, q2R.y));
svg.appendChild(makeLine(midX, q2R.y, midX, finL.y));
svg.appendChild(makeLine(midX, finL.y, finL.x, finL.y));
}
/* ── Single Elim ─────────────────────────────────────────── */
function drawSingleElim($root) {
/* Group cards by column */
var cols = [];
$root.find('.bk-col').each(function () {
var cards = [];
$(this).find('.bk-match').each(function () {
cards.push($(this));
});
cols.push(cards);
});
if (cols.length < 2) { return; }
var svg = makeSVG($root);
for (var c = 0; c < cols.length - 1; c++) {
var thisCol = cols[c];
var nextCol = cols[c + 1];
/* Each pair of cards in thisCol feeds into one card in nextCol */
for (var i = 0; i < nextCol.length; i++) {
var $top = thisCol[i * 2];
var $bot = thisCol[i * 2 + 1];
var $target = nextCol[i];
if (!$top || !$bot || !$target) { continue; }
var topR = rightMid($top, $root);
var botR = rightMid($bot, $root);
var tarL = leftMid($target, $root);
var joinX = topR.x + ($root.find('.bk-col').eq(c + 1).offset().left
- $root.find('.bk-col').eq(c).offset().left) / 2;
/* Horizontal arms from each card */
svg.appendChild(makeLine(topR.x, topR.y, joinX, topR.y));
svg.appendChild(makeLine(botR.x, botR.y, joinX, botR.y));
/* Vertical join */
svg.appendChild(makeLine(joinX, topR.y, joinX, botR.y));
/* Horizontal to next card */
var midY = (topR.y + botR.y) / 2;
svg.appendChild(makeLine(joinX, midY, tarL.x, midY));
}
}
}
/* ── Main draw function ──────────────────────────────────── */
function drawAll() {
$('.bk-gsl').each(function () { drawGSL($(this)); });
$('.bk-single-elim').each(function () { drawSingleElim($(this)); });
}
/* Draw after fonts/images settle, redraw on resize */
setTimeout(drawAll, 300);
$(window).on('resize', function () {
clearTimeout(window._bkResizeTimer);
window._bkResizeTimer = setTimeout(drawAll, 200);
});
});