Files
SREBOT/web/views/leaderboard-comparison.ejs
T
FURRO404 2b399fdb81 add SREBOT, SHARED, TSSBOT contents (fixup for #1223)
PR #1223 only staged the deletions of the old paths because the new
top-level directories were still untracked when the commit was authored.
This commit adds the actual restructured tree: SREBOT/ (existing bot),
SHARED/ (vromfs, data_parser, ICONS/MAPS/FONTS, DAGOR_FILES,
update_game_files), and TSSBOT/ (skeleton).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 23:17:02 -07:00

1622 lines
70 KiB
Plaintext

<!DOCTYPE html>
<html lang="<%= lang %>">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<title><%= t('leaderboard.comparisonTitle') %> | <%= botName %></title>
<meta name="description" content="<%= t('leaderboard.comparisonSubtitle') %>">
<link rel="icon" type="image/png" href="/images/transparent_toothlessssss.png">
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
<link rel="preload" href="/Fonts/symbols_skyquake.ttf" as="font" type="font/ttf" crossorigin="anonymous">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<!-- Tailwind CSS -->
<link rel="stylesheet" href="/css/output.css">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" media="print" onload="this.media='all'">
<style>
@font-face {
font-family: 'skyquakesymbols';
src: url('/Fonts/symbols_skyquake.ttf') format('truetype');
font-display: swap;
}
body {
background: #1b1b1b;
min-height: 100vh;
}
.text-accent { color: #F5F5DC; }
.text-muted { color: #90EE90; }
.btn-primary {
background: linear-gradient(135deg, #F5F5DC 0%, #E8E8D0 100%);
box-shadow: 0 4px 20px rgba(245, 245, 220, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.4);
color: #1E1E1E;
font-weight: 700;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.btn-primary:hover {
box-shadow: 0 8px 25px rgba(245, 245, 220, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.4);
transform: translateY(-2px);
}
.leaderboard-container {
max-width: 1400px;
margin: 0 auto;
padding: 6rem 1rem 2rem;
min-height: calc(100vh - 200px);
}
.leaderboard-header {
text-align: center;
margin-bottom: 2rem;
}
.leaderboard-title {
font-size: 2.5rem;
font-weight: 700;
margin-bottom: 0.5rem;
background: linear-gradient(135deg, #F5F5DC 0%, #E8E8D0 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.leaderboard-subtitle {
font-size: 1.1rem;
color: rgba(255, 255, 255, 0.8);
margin-bottom: 1rem;
}
.disclaimer {
font-size: 0.9rem;
opacity: 0.8;
font-style: italic;
color: rgba(255, 255, 255, 0.7);
}
.leaderboard-nav {
background: rgba(30, 30, 30, 0.6);
border-radius: 1rem;
padding: 1rem;
backdrop-filter: blur(10px);
border: 1px solid rgba(144, 238, 144, 0.1);
margin-bottom: 2rem;
display: flex;
justify-content: center;
gap: 1rem;
flex-wrap: wrap;
}
.leaderboard-tab {
padding: 0.75rem 1.5rem;
background: rgba(30, 30, 30, 0.8);
border: 1px solid rgba(144, 238, 144, 0.2);
border-radius: 0.5rem;
color: #ffffff;
text-decoration: none;
font-weight: 500;
transition: all 0.3s ease;
}
.leaderboard-tab:hover {
background: rgba(144, 238, 144, 0.1);
color: #90EE90;
border-color: #90EE90;
text-shadow: 0 0 10px rgba(144, 238, 144, 0.3);
}
.leaderboard-tab.active {
background: linear-gradient(135deg, rgba(144, 238, 144, 0.15), rgba(144, 238, 144, 0.05));
color: #90EE90;
border-color: #90EE90;
text-shadow: 0 0 15px rgba(144, 238, 144, 0.6);
}
.comparison-section {
background: rgba(30, 30, 30, 0.6);
border-radius: 1rem;
padding: 2rem;
backdrop-filter: blur(10px);
border: 1px solid rgba(144, 238, 144, 0.1);
margin-bottom: 2rem;
}
.section-title {
font-size: 1.5rem;
font-weight: 600;
color: #90EE90;
margin-bottom: 1.5rem;
text-align: center;
text-shadow: 0 0 10px rgba(144, 238, 144, 0.3);
}
.comparison-type-selector {
display: flex;
justify-content: center;
gap: 1rem;
margin-bottom: 2rem;
}
.comparison-type-btn {
padding: 0.75rem 1.5rem;
background: rgba(30, 30, 30, 0.8);
border: 1px solid rgba(144, 238, 144, 0.2);
border-radius: 0.5rem;
color: #ffffff;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
}
.comparison-type-btn:hover {
background: rgba(144, 238, 144, 0.1);
color: #90EE90;
border-color: #90EE90;
}
.comparison-type-btn.active {
background: linear-gradient(135deg, rgba(144, 238, 144, 0.15), rgba(144, 238, 144, 0.05));
color: #90EE90;
border-color: #90EE90;
text-shadow: 0 0 15px rgba(144, 238, 144, 0.6);
}
.comparison-search {
margin-bottom: 2rem;
}
.search-container {
background: rgba(30, 30, 30, 0.8);
border-radius: 1rem;
padding: 1.5rem;
border: 1px solid rgba(144, 238, 144, 0.1);
}
.search-container label {
display: block;
color: #90EE90;
font-weight: 600;
font-size: 1rem;
margin-bottom: 0.5rem;
text-shadow: 0 0 10px rgba(144, 238, 144, 0.3);
}
.search-container input {
background: rgba(30, 30, 30, 0.8);
border: 1px solid rgba(144, 238, 144, 0.2);
border-radius: 0.5rem;
color: #ffffff;
padding: 0.75rem 1rem;
font-size: 0.9rem;
width: 100%;
max-width: 400px;
transition: all 0.3s ease;
}
.search-container input:focus {
outline: none;
border-color: #90EE90;
box-shadow: 0 0 10px rgba(144, 238, 144, 0.3);
}
.search-container input::placeholder {
color: rgba(255, 255, 255, 0.5);
}
/* Squadron tags + vehicle names echo skyquake glyphs (▄ ◢ ◊ ␗ etc.). */
#vehicleSearch, #squadronSearch {
font-family: 'skyquakesymbols', 'Inter', sans-serif;
}
.search-container {
position: relative;
}
.search-results {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: rgba(30, 30, 30, 0.95);
border: 1px solid rgba(144, 238, 144, 0.2);
border-top: none;
border-radius: 0 0 0.5rem 0.5rem;
max-height: 200px;
overflow-y: auto;
z-index: 1000;
display: none;
}
.search-results.show {
display: block;
}
.search-result-item {
padding: 0.75rem 1rem;
color: #ffffff;
cursor: pointer;
transition: all 0.3s ease;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.search-result-item:last-child {
border-bottom: none;
}
.search-result-item:hover,
.search-result-item.selected {
background: rgba(144, 238, 144, 0.1);
color: #90EE90;
}
.search-result-item .result-name {
font-weight: 600;
}
.search-result-item .result-stats {
font-size: 0.8rem;
color: rgba(255, 255, 255, 0.6);
margin-top: 0.2rem;
}
.selected-items {
margin-bottom: 2rem;
min-height: 60px;
}
.selected-item {
display: inline-flex;
align-items: center;
gap: 0.5rem;
background: rgba(144, 238, 144, 0.1);
border: 1px solid rgba(144, 238, 144, 0.2);
border-radius: 2rem;
padding: 0.5rem 1rem;
margin: 0.25rem;
color: #90EE90;
font-weight: 500;
transition: all 0.3s ease;
}
.selected-item:hover {
background: rgba(144, 238, 144, 0.2);
border-color: #90EE90;
}
.selected-item-remove {
background: none;
border: none;
color: #ff4757;
cursor: pointer;
padding: 0;
width: 20px;
height: 20px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.selected-item-remove:hover {
background: rgba(255, 71, 87, 0.2);
color: #ff3838;
}
.comparison-table-container {
background: rgba(30, 30, 30, 0.8);
border-radius: 1rem;
overflow: hidden;
border: 1px solid rgba(144, 238, 144, 0.1);
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.comparison-table {
width: 100%;
border-collapse: collapse;
min-width: 600px;
}
.comparison-table th {
background: linear-gradient(135deg, rgba(144, 238, 144, 0.15), rgba(144, 238, 144, 0.05));
color: #F5F5DC;
padding: 1rem 0.75rem;
text-align: left;
font-weight: 600;
border-bottom: 1px solid rgba(144, 238, 144, 0.2);
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 0.5px;
text-shadow: 0 0 10px rgba(144, 238, 144, 0.3);
white-space: nowrap;
}
.comparison-table th:first-child {
position: sticky;
left: 0;
background: linear-gradient(135deg, rgba(144, 238, 144, 0.2), rgba(144, 238, 144, 0.1));
z-index: 1;
}
.comparison-table td {
padding: 0.75rem;
border-bottom: 1px solid rgba(144, 238, 144, 0.1);
background: rgba(30, 30, 30, 0.4);
color: #ffffff;
transition: all 0.3s ease;
text-align: center;
white-space: nowrap;
}
.comparison-table td:first-child {
text-align: left;
font-weight: 600;
position: sticky;
left: 0;
background: rgba(30, 30, 30, 0.6);
z-index: 1;
}
.comparison-table tr:hover td {
background: rgba(144, 238, 144, 0.05);
}
.comparison-table tr:last-child td {
border-bottom: none;
}
.stat-best {
color: #90EE90 !important;
text-shadow: 0 0 10px rgba(144, 238, 144, 0.6);
font-weight: 700;
}
.vs-divider {
font-size: 1.5rem;
font-weight: 700;
color: #90EE90;
text-shadow: 0 0 15px rgba(144, 238, 144, 0.6);
padding: 0 1rem;
}
.comparison-result {
background: rgba(30, 30, 30, 0.8);
border-radius: 1rem;
padding: 2rem;
border: 1px solid rgba(144, 238, 144, 0.1);
}
.comparison-cards {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
align-items: start;
}
.comparison-card {
background: rgba(30, 30, 30, 0.6);
border-radius: 0.75rem;
padding: 1.5rem;
border: 1px solid rgba(144, 238, 144, 0.1);
transition: all 0.3s ease;
}
.comparison-name {
color: #90EE90;
text-align: center;
margin-bottom: 1rem;
font-size: 1.2rem;
font-weight: 600;
text-shadow: 0 0 10px rgba(144, 238, 144, 0.3);
}
.comparison-stats {
display: grid;
gap: 0.75rem;
}
.comparison-stat {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.comparison-stat:last-child {
border-bottom: none;
}
.stat-label {
color: rgba(255, 255, 255, 0.8);
font-size: 0.9rem;
}
.stat-value {
color: #90EE90;
font-weight: 600;
text-shadow: 0 0 10px rgba(144, 238, 144, 0.5);
}
.stat-value.better {
color: #90EE90;
text-shadow: 0 0 10px rgba(144, 238, 144, 0.6);
}
.comparison-divider {
display: flex;
align-items: center;
justify-content: center;
padding: 1rem 0;
}
.winner-indicator {
background: linear-gradient(135deg, #F5F5DC 0%, #E8E8D0 100%);
color: #1E1E1E;
padding: 0.75rem 1.5rem;
border-radius: 2rem;
font-weight: 700;
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 1px;
box-shadow: 0 4px 15px rgba(245, 245, 220, 0.4);
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { box-shadow: 0 4px 15px rgba(245, 245, 220, 0.4); }
50% { box-shadow: 0 6px 20px rgba(245, 245, 220, 0.6); }
100% { box-shadow: 0 4px 15px rgba(245, 245, 220, 0.4); }
}
.comparison-summary {
background: rgba(30, 30, 30, 0.4);
border-radius: 0.75rem;
padding: 1.5rem;
border: 1px solid rgba(144, 238, 144, 0.1);
text-align: center;
}
.summary-text {
color: rgba(255, 255, 255, 0.9);
font-size: 0.95rem;
line-height: 1.5;
}
.comparison-placeholder {
text-align: center;
padding: 4rem 2rem;
background: rgba(30, 30, 30, 0.8);
border-radius: 1rem;
border: 1px solid rgba(144, 238, 144, 0.1);
}
.loading-state {
text-align: center;
padding: 4rem;
color: #90EE90;
}
.loading-spinner {
border: 3px solid rgba(144, 238, 144, 0.3);
border-top: 3px solid #90EE90;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 1rem;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Footer Styles */
footer {
background: rgba(30, 30, 30, 0.8);
backdrop-filter: blur(10px);
border-top: 1px solid rgba(144, 238, 144, 0.1);
padding: 2rem 0;
margin-top: auto;
}
footer a {
color: #888;
text-decoration: none;
transition: all 0.3s ease;
}
footer a:hover {
color: #90EE90;
text-shadow: 0 0 10px rgba(144, 238, 144, 0.5);
}
</style>
</head>
<body class="text-white antialiased">
<%- include('partials/nav', { activePage: 'leaderboards' }) %>
<div class="leaderboard-container">
<div class="leaderboard-header">
<h1 class="leaderboard-title"><%= t('leaderboard.comparisonTitle') %></h1>
<p class="leaderboard-subtitle"><%= t('leaderboard.comparisonSubtitle') %></p>
<div class="disclaimer">
<i class="fas fa-info-circle" style="margin-right: 0.5rem;"></i>
<%= t('leaderboard.comparisonHint') %>
</div>
</div>
<div class="leaderboard-nav">
<a href="/leaderboard/players" class="leaderboard-tab">
<i class="fas fa-user"></i> <%= t('common.players') %>
</a>
<a href="/leaderboard/vehicles" class="leaderboard-tab">
<i class="fas fa-fighter-jet"></i> <%= t('common.vehicles') %>
</a>
<a href="/leaderboard/squadrons" class="leaderboard-tab">
<i class="fas fa-users"></i> <%= t('common.squadrons') %>
</a>
<a href="/leaderboard/stats" class="leaderboard-tab">
<i class="fas fa-chart-bar"></i> <%= t('common.statistics') %>
</a>
<a href="/leaderboard/comparison" class="leaderboard-tab active">
<i class="fas fa-balance-scale"></i> <%= t('common.comparison') %>
</a>
</div>
<div class="loading-state" id="loadingState">
<div class="loading-spinner"></div>
<p><%= t('leaderboard.loadingComparisonData') %></p>
</div>
<div id="comparisonContent" style="display: none;">
<div class="comparison-section">
<div class="comparison-type-selector">
<button class="comparison-type-btn active" onclick="switchComparisonType('players')" id="playersBtn">
<i class="fas fa-user"></i> <%= t('leaderboard.comparePlayers') %>
</button>
<button class="comparison-type-btn" onclick="switchComparisonType('squadrons')" id="squadronsBtn">
<i class="fas fa-users"></i> <%= t('leaderboard.compareSquadrons') %>
</button>
<button class="comparison-type-btn" onclick="switchComparisonType('vehicles')" id="vehiclesBtn">
<i class="fas fa-fighter-jet"></i> <%= t('leaderboard.compareVehicles') %>
</button>
<button class="comparison-type-btn" onclick="switchComparisonType('player-vehicles')" id="playerVehiclesBtn">
<i class="fas fa-user-cog"></i> <%= t('leaderboard.playersAndVehicles') %>
</button>
</div>
</div>
<div class="comparison-section" id="playerComparison">
<h2 class="section-title"><%= t('leaderboard.playerComparison') %></h2>
<div class="comparison-search">
<div class="search-container">
<label for="playerSearch"><%= t('leaderboard.addPlayersToCompare') %></label>
<input type="text" id="playerSearch" placeholder="<%= t('leaderboard.searchSelectPlayers') %>" oninput="searchPlayers()" onkeydown="handlePlayerKeydown(event)">
<div class="search-results" id="playerResults"></div>
</div>
</div>
<div class="selected-items" id="selectedPlayers">
</div>
<div class="comparison-result" id="playerComparisonResult" style="display: none;">
<div class="comparison-table-container">
<table class="comparison-table" id="playerComparisonTable">
<thead id="playerTableHead">
</thead>
<tbody id="playerTableBody">
</tbody>
</table>
</div>
</div>
<div class="comparison-placeholder" id="playerComparisonPlaceholder">
<i class="fas fa-users" style="font-size: 3rem; color: rgba(57, 255, 20, 0.3); margin-bottom: 1rem;"></i>
<p style="color: rgba(255, 255, 255, 0.6);"><%= t('leaderboard.selectPlayersToCompare') %></p>
</div>
</div>
<div class="comparison-section" id="squadronComparison" style="display: none;">
<h2 class="section-title"><%= t('leaderboard.squadronComparison') %></h2>
<div class="comparison-search">
<div class="search-container" id="squadronSearchContainer">
<label for="squadronSearch"><%= t('leaderboard.addSquadronsToCompare') %></label>
<input type="text" id="squadronSearch" placeholder="<%= t('leaderboard.typeSquadronName') %>" autocomplete="off" oninput="searchSquadronsDropdown()" onkeydown="handleSquadronKeydown(event)">
<div class="search-results" id="squadronDropdown" style="display:none; max-height:300px; overflow-y:auto;"></div>
</div>
</div>
<div class="selected-items" id="selectedSquadrons">
</div>
<div class="comparison-result" id="squadronComparisonResult" style="display: none;">
<div class="comparison-table-container">
<table class="comparison-table" id="squadronComparisonTable">
<thead id="squadronTableHead">
</thead>
<tbody id="squadronTableBody">
</tbody>
</table>
</div>
</div>
<div class="comparison-placeholder" id="squadronComparisonPlaceholder">
<i class="fas fa-users" style="font-size: 3rem; color: rgba(57, 255, 20, 0.3); margin-bottom: 1rem;"></i>
<p style="color: rgba(255, 255, 255, 0.6);"><%= t('leaderboard.selectSquadronsToCompare') %></p>
</div>
</div>
<div class="comparison-section" id="vehicleComparison" style="display: none;">
<h2 class="section-title"><%= t('leaderboard.vehicleComparison') %></h2>
<div class="comparison-search">
<div class="search-container">
<label for="vehicleSearch"><%= t('leaderboard.addVehiclesToCompare') %></label>
<input type="text" id="vehicleSearch" placeholder="<%= t('leaderboard.searchSelectVehicles') %>" oninput="searchVehicles()" onkeydown="handleVehicleKeydown(event)">
<div class="search-results" id="vehicleResults"></div>
</div>
</div>
<div class="selected-items" id="selectedVehicles">
</div>
<div class="comparison-result" id="vehicleComparisonResult" style="display: none;">
<div class="comparison-table-container">
<table class="comparison-table" id="vehicleComparisonTable">
<thead id="vehicleTableHead">
</thead>
<tbody id="vehicleTableBody">
</tbody>
</table>
</div>
</div>
<div class="comparison-placeholder" id="vehicleComparisonPlaceholder">
<i class="fas fa-fighter-jet" style="font-size: 3rem; color: rgba(57, 255, 20, 0.3); margin-bottom: 1rem;"></i>
<p style="color: rgba(255, 255, 255, 0.6);"><%= t('leaderboard.selectVehiclesToCompare') %></p>
</div>
</div>
<div class="comparison-section" id="playerVehicleComparison" style="display: none;">
<h2 class="section-title"><%= t('leaderboard.playersVehiclesComparison') %></h2>
<div class="comparison-search">
<div class="search-container">
<label for="playerVehicleComboSearch"><%= t('leaderboard.addPlayerVehicleCombos') %></label>
<input type="text" id="playerVehicleComboSearch" placeholder="<%= t('leaderboard.searchForPlayers') %>" oninput="searchPlayersForCombo()" onkeydown="handlePlayerVehicleComboKeydown(event)">
<div class="search-results" id="playerVehicleComboResults"></div>
</div>
</div>
<div class="selected-items" id="selectedPlayerVehicleCombos">
</div>
<div class="comparison-result" id="playerVehicleComparisonResult" style="display: none;">
<div class="comparison-table-container">
<table class="comparison-table" id="playerVehicleComparisonTable">
<thead id="playerVehicleTableHead">
</thead>
<tbody id="playerVehicleTableBody">
</tbody>
</table>
</div>
</div>
<div class="comparison-placeholder" id="playerVehicleComparisonPlaceholder">
<i class="fas fa-user-cog" style="font-size: 3rem; color: rgba(57, 255, 20, 0.3); margin-bottom: 1rem;"></i>
<p style="color: rgba(255, 255, 255, 0.6);"><%= t('leaderboard.selectPlayersVehiclesToCompare') %></p>
</div>
</div>
</div>
</div>
<!-- Footer -->
<%- include('partials/footer') %>
<script>
window.__lang = '<%= lang %>';
window.__i18n = <%- localeJson %>;
window.__t = function(key) {
var parts = key.split('.'), obj = window.__i18n;
for (var i = 0; i < parts.length; i++) { obj = obj && obj[parts[i]]; }
return obj !== undefined ? obj : key;
};
window.switchLanguage = function(lang) {
var next = lang || (document.documentElement.lang === 'en' ? 'ru' : 'en');
if (next === document.documentElement.lang) return;
document.cookie = 'lang=' + next + ';path=/;max-age=31536000;SameSite=Lax';
window.location.reload();
};
</script>
<script src="/js/main.js?v=3"></script>
<script src="/js/api-client.js?v=2"></script>
<script src="/js/vehicle-i18n.js"></script>
<script src="/js/seasons-filter.js"></script>
<script>
let playersData = [];
let squadronsData = [];
let currentComparisonType = 'players';
let selectedPlayers = [];
let selectedVehicles = [];
let selectedSquadrons = [];
const playerVehicleCache = new Map();
const vehicleAggregateCache = new Map();
let comparisonVehicleList = [];
async function loadComparisonData() {
const loadingState = document.getElementById('loadingState');
const comparisonContent = document.getElementById('comparisonContent');
try {
await window.ensureAPIClient();
const [playersResponse, squadronsResponse] = await Promise.all([
window.apiClient.getPlayerLeaderboard(),
window.apiClient.getSquadronLeaderboard()
]);
await ensureComparisonVehicleList();
playersData = playersResponse.players || [];
squadronsData = squadronsResponse.squadrons || [];
playersData = playersData.map(player => {
const total_kills = player.total_kills || ((player.ground_kills || 0) + (player.air_kills || 0));
const assists = player.total_assists || player.assists || 0;
const captures = player.total_captures || player.captures || 0;
const wins = player.wins || 0;
const battles = player.total_battles || 0;
const winRate = battles > 0 ? (wins / battles) * 100 : 0;
const kdr = (player.deaths || 0) > 0 ? (total_kills / player.deaths) : total_kills;
const kps = battles > 0 ? (total_kills / battles) : 0;
return {
...player,
total_kills,
assists,
captures,
wins,
battles,
winRate,
kdr,
kps
};
});
loadingState.style.display = 'none';
comparisonContent.style.display = 'block';
} catch (error) {
console.error('Error loading comparison data:', error);
loadingState.innerHTML = `
<div style="color: #ff3838;">
<i class="fas fa-exclamation-triangle" style="font-size: 3rem; margin-bottom: 1rem;"></i>
<h3>${__t('leaderboard.failedToLoadComparison')}</h3>
<p>${__t('leaderboard.pleaseRefresh')}</p>
</div>
`;
}
}
function switchComparisonType(type) {
currentComparisonType = type;
document.getElementById('playersBtn').classList.toggle('active', type === 'players');
document.getElementById('squadronsBtn').classList.toggle('active', type === 'squadrons');
document.getElementById('vehiclesBtn').classList.toggle('active', type === 'vehicles');
document.getElementById('playerVehiclesBtn').classList.toggle('active', type === 'player-vehicles');
document.getElementById('playerComparison').style.display = type === 'players' ? 'block' : 'none';
document.getElementById('squadronComparison').style.display = type === 'squadrons' ? 'block' : 'none';
document.getElementById('vehicleComparison').style.display = type === 'vehicles' ? 'block' : 'none';
document.getElementById('playerVehicleComparison').style.display = type === 'player-vehicles' ? 'block' : 'none';
document.getElementById('playerSearch').value = '';
if (document.getElementById('squadronSearch')) {
document.getElementById('squadronSearch').value = '';
}
document.getElementById('vehicleSearch').value = '';
if (document.getElementById('playerVehicleComboSearch')) {
document.getElementById('playerVehicleComboSearch').value = '';
}
hideSearchResults();
}
function hideSearchResults() {
document.getElementById('playerResults').classList.remove('show');
const sqDropdown = document.getElementById('squadronDropdown');
if (sqDropdown) sqDropdown.style.display = 'none';
document.getElementById('vehicleResults').classList.remove('show');
if (document.getElementById('playerVehicleComboResults')) {
document.getElementById('playerVehicleComboResults').classList.remove('show');
}
}
async function ensureComparisonVehicleList() {
if (comparisonVehicleList.length) return comparisonVehicleList;
const data = await window.apiClient.request('/api/analytics/vehicle-list');
comparisonVehicleList = (data && data.vehicles) || [];
return comparisonVehicleList;
}
function vehicleDisplay(internal, fallback) {
if (!internal) return fallback || '';
if (window.vehicleI18n && window.vehicleI18n.translate) {
return window.vehicleI18n.translate(internal, fallback || internal);
}
return fallback || internal;
}
async function fetchVehicleComparisonAggregate(internal) {
const key = String(internal || '').toLowerCase();
if (!key) return null;
if (vehicleAggregateCache.has(key)) return vehicleAggregateCache.get(key);
const rows = [];
let page = 1;
let totalPages = 1;
do {
const response = await window.apiClient.request(`/api/leaderboard/vehicles?vehicle_internal=${encodeURIComponent(key)}&limit=100&page=${page}`);
const batch = (response && response.vehicles) || [];
rows.push(...batch);
totalPages = Math.max(1, Number(response?.pagination?.total_pages) || 1);
page += 1;
} while (page <= totalPages);
const aggregate = rows.reduce((acc, row) => {
acc.total_battles += Number(row.battles) || 0;
acc.total_wins += Number(row.wins) || 0;
acc.ground_kills += Number(row.ground_kills) || 0;
acc.air_kills += Number(row.air_kills) || 0;
acc.total_kills += Number(row.total_kills) || 0;
acc.players += 1;
return acc;
}, {
vehicle_internal: key,
vehicle: vehicleDisplay(key, rows[0]?.vehicle || key),
total_battles: 0,
total_wins: 0,
ground_kills: 0,
air_kills: 0,
total_kills: 0,
players: 0
});
aggregate.avg_win_rate = aggregate.total_battles > 0 ? ((aggregate.total_wins / aggregate.total_battles) * 100) : 0;
aggregate.avg_kills = aggregate.players > 0 ? (aggregate.total_kills / aggregate.players) : 0;
vehicleAggregateCache.set(key, aggregate);
return aggregate;
}
let sqSearchTimeout;
function searchSquadronsDropdown() {
clearTimeout(sqSearchTimeout);
const input = document.getElementById('squadronSearch');
const dropdown = document.getElementById('squadronDropdown');
const val = input.value.trim().toLowerCase();
if (val.length < 1) {
dropdown.style.display = 'none';
return;
}
sqSearchTimeout = setTimeout(() => {
const hits = squadronsData
.filter(s => s.tag_name.toLowerCase().includes(val) || s.long_name.toLowerCase().includes(val))
.slice(0, 12)
.sort((a, b) => (b.points?.total_points || b.total_kills) - (a.points?.total_points || a.total_kills));
if (!hits.length) {
dropdown.innerHTML = '<div class="search-result-item" style="color:rgba(255,255,255,0.5);">' + __t('common.noSquadronsFound') + '</div>';
} else {
dropdown.innerHTML = hits.map(s =>
`<div class="search-result-item" onclick="addSquadron('${escapeHtml(s.tag_name)}')">
<span style="font-family:'skyquakesymbols','Inter',sans-serif; color:#90EE90; font-size:0.95rem;">${escapeHtml(s.tag_name)}</span>
<span style="color:rgba(255,255,255,0.35); font-size:0.85rem; margin-left:0.5rem;">${escapeHtml(s.long_name)}</span>
<div class="result-stats">${s.player_count} ${__t('common.membersCount')} &bull; ${s.total_kills.toLocaleString()} ${__t('js.killsSuffix')} &bull; ${s.win_rate.toFixed(1)}% ${__t('js.winRateSuffix')}</div>
</div>`
).join('');
}
dropdown.style.display = 'block';
}, 100);
}
let selectedPlayerVehicleCombos = [];
let currentPlayerForVehicleSelection = null;
function searchPlayersForCombo() {
const searchTerm = document.getElementById('playerVehicleComboSearch').value.toLowerCase().trim();
const resultsDiv = document.getElementById('playerVehicleComboResults');
if (searchTerm.length < 2) {
resultsDiv.classList.remove('show');
return;
}
const filteredPlayers = playersData
.filter(player => player.nick.toLowerCase().includes(searchTerm))
.slice(0, 10)
.sort((a, b) => b.total_kills - a.total_kills);
if (filteredPlayers.length === 0) {
resultsDiv.innerHTML = '<div class="search-result-item">' + __t('common.noPlayersFound') + '</div>';
} else {
resultsDiv.innerHTML = filteredPlayers.map(player => `
<div class="search-result-item" onclick="selectPlayerForCombo(${player.uid})">
<div class="result-name">${escapeHtml(player.nick)}</div>
<div class="result-stats">${formatNumber(player.total_kills)} ${__t('js.killsSuffix')} • ${player.winRate.toFixed(1)}% ${__t('js.winRateSuffix')}</div>
</div>
`).join('');
}
resultsDiv.classList.add('show');
}
async function selectPlayerForCombo(uid) {
const player = playersData.find(p => p.uid == uid);
if (!player) return;
currentPlayerForVehicleSelection = player;
document.getElementById('playerVehicleComboSearch').value = '';
hideSearchResults();
const resultsDiv = document.getElementById('playerVehicleComboResults');
resultsDiv.innerHTML = '<div class="search-result-item">' + __t('common.loading') + '</div>';
resultsDiv.classList.add('show');
try {
const playerVehicles = await getVehiclesForPlayer(player.uid);
if (currentPlayerForVehicleSelection?.uid != player.uid) return;
showVehicleSelectionForPlayer(player, playerVehicles);
} catch (error) {
console.error('Failed to load player vehicles for comparison:', error);
resultsDiv.innerHTML = '<div class="search-result-item">' + __t('leaderboard.noVehiclesForPlayer') + '</div>';
resultsDiv.classList.add('show');
}
}
async function getVehiclesForPlayer(uid) {
const cacheKey = String(uid);
if (playerVehicleCache.has(cacheKey)) return playerVehicleCache.get(cacheKey);
const playerResponse = await window.apiClient.request('/api/player/' + encodeURIComponent(uid));
const playerVehicles = (playerResponse.vehicles || [])
.filter(v => v && v.vehicle && v.vehicle !== 'DISCONNECTED')
.map(v => {
const stats = v.stats || {};
const groundKills = Number(stats.ground_kills) || 0;
const airKills = Number(stats.air_kills) || 0;
const battles = Number(stats.total_battles) || 0;
const wins = Number(stats.wins) || 0;
const assists = Number(stats.assists) || 0;
const deaths = Number(stats.deaths) || 0;
const captures = Number(stats.captures) || 0;
const vehicleInternal = (v.vehicle_internal || '').toLowerCase();
const displayName = vehicleDisplay(vehicleInternal, v.vehicle);
return {
vehicle_internal: vehicleInternal,
vehicle: displayName,
stats: {
ground_kills: groundKills,
air_kills: airKills,
total_battles: battles,
wins,
assists,
deaths,
captures
},
total_kills: groundKills + airKills,
battles,
wins,
ground_kills: groundKills,
air_kills: airKills,
assists,
deaths,
captures
};
})
.sort((a, b) => b.total_kills - a.total_kills);
playerVehicleCache.set(cacheKey, playerVehicles);
return playerVehicles;
}
function showVehicleSelectionForPlayer(player, playerVehicles) {
const resultsDiv = document.getElementById('playerVehicleComboResults');
if (playerVehicles.length === 0) {
resultsDiv.innerHTML = '<div class="search-result-item">' + __t('leaderboard.noVehiclesForPlayer') + '</div>';
resultsDiv.classList.add('show');
return;
}
resultsDiv.innerHTML = `
<div style="padding: 1rem; background: rgba(57, 255, 20, 0.1); border-bottom: 1px solid rgba(57, 255, 20, 0.3);">
<div style="font-weight: 600; color: #39ff14; margin-bottom: 0.75rem;">
${__t('leaderboard.selectVehicleFor')} ${escapeHtml(player.nick)}:
</div>
<select id="vehicleDropdown" onchange="handleVehicleSelection(${player.uid})" style="width: 100%; background: rgba(15, 15, 26, 0.8); border: 1px solid rgba(57, 255, 20, 0.3); border-radius: 0.5rem; color: #ffffff; padding: 0.75rem; font-size: 0.95rem;">
<option value="">${__t('leaderboard.selectAVehicle')}</option>
${playerVehicles.map(vehicleEntry => `
<option value="${escapeHtml(vehicleEntry.vehicle_internal || vehicleEntry.vehicle)}" data-stats="${escapeHtml(encodeURIComponent(JSON.stringify(vehicleEntry)))}">
${escapeHtml(vehicleEntry.vehicle)} (${vehicleEntry.total_kills} kills, ${vehicleEntry.battles} battles)
</option>
`).join('')}
</select>
</div>
`;
resultsDiv.classList.add('show');
}
function handleVehicleSelection(uid) {
const dropdown = document.getElementById('vehicleDropdown');
const selectedOption = dropdown.options[dropdown.selectedIndex];
if (!selectedOption.value) return;
const vehicleInternal = selectedOption.value;
const vehicleStats = JSON.parse(decodeURIComponent(selectedOption.getAttribute('data-stats')));
addPlayerVehicleComboWithStats(uid, vehicleInternal, vehicleStats);
}
function addPlayerVehicleComboWithStats(uid, vehicleInternal, vehicleStats) {
const player = playersData.find(p => p.uid == uid);
if (!player || !vehicleStats) return;
const comboKey = `${uid}-${vehicleInternal || vehicleStats.vehicle}`;
if (selectedPlayerVehicleCombos.some(combo => combo.key === comboKey)) return;
selectedPlayerVehicleCombos.push({
key: comboKey,
player: player,
vehicleData: vehicleStats,
displayName: `${player.nick} (${vehicleStats.vehicle || vehicleInternal})`
});
updateSelectedPlayerVehicleCombos();
updatePlayerVehicleComparison();
hideSearchResults();
}
function removePlayerVehicleCombo(comboKey) {
selectedPlayerVehicleCombos = selectedPlayerVehicleCombos.filter(combo => combo.key !== comboKey);
updateSelectedPlayerVehicleCombos();
updatePlayerVehicleComparison();
}
function updateSelectedPlayerVehicleCombos() {
const container = document.getElementById('selectedPlayerVehicleCombos');
if (selectedPlayerVehicleCombos.length === 0) {
container.innerHTML = '<p style="color: rgba(255, 255, 255, 0.6); text-align: center; padding: 1rem;">' + __t('leaderboard.noPlayerVehicleSelected') + '</p>';
return;
}
container.innerHTML = selectedPlayerVehicleCombos.map(combo => `
<span class="selected-item">
${escapeHtml(combo.displayName)}
<button class="selected-item-remove" data-combo-key="${combo.key}" data-type="player-vehicle-combo">
<i class="fas fa-times"></i>
</button>
</span>
`).join('');
}
function updatePlayerVehicleComparison() {
const resultDiv = document.getElementById('playerVehicleComparisonResult');
const placeholderDiv = document.getElementById('playerVehicleComparisonPlaceholder');
if (selectedPlayerVehicleCombos.length < 2) {
resultDiv.style.display = 'none';
placeholderDiv.style.display = 'block';
return;
}
displayPlayerVehicleComparisonTable();
resultDiv.style.display = 'block';
placeholderDiv.style.display = 'none';
}
function displayPlayerVehicleComparisonTable() {
const stats = [
{ label: __t('common.battles'), key: 'battles', format: (val) => formatNumber(val), higher: true },
{ label: __t('common.wins'), key: 'wins', format: (val) => formatNumber(val), higher: true },
{ label: __t('common.winRate'), key: 'win_rate', format: (val) => val.toFixed(1) + '%', higher: true },
{ label: __t('common.totalKills'), key: 'total_kills', format: (val) => formatNumber(val), higher: true },
{ label: __t('common.groundKills'), key: 'ground_kills', format: (val) => formatNumber(val), higher: true },
{ label: __t('common.airKills'), key: 'air_kills', format: (val) => formatNumber(val), higher: true },
{ label: __t('leaderboard.kdRatio'), key: 'kd_ratio', format: (val) => val.toFixed(2), higher: true },
{ label: __t('common.assists'), key: 'assists', format: (val) => formatNumber(val), higher: true },
{ label: __t('common.captures'), key: 'captures', format: (val) => formatNumber(val), higher: true },
{ label: __t('common.deaths'), key: 'deaths', format: (val) => formatNumber(val), higher: false }
];
const headerRow = `
<tr>
<th>${__t('leaderboard.statistic')}</th>
${selectedPlayerVehicleCombos.map(combo => `<th>${escapeHtml(combo.displayName)}</th>`).join('')}
</tr>
`;
const bodyRows = stats.map(stat => {
const values = selectedPlayerVehicleCombos.map(combo => {
const vehicleData = combo.vehicleData;
let value = vehicleData[stat.key] || 0;
if (stat.key === 'win_rate') {
value = vehicleData.battles > 0 ? ((vehicleData.wins || 0) / vehicleData.battles) * 100 : 0;
} else if (stat.key === 'kd_ratio') {
value = (vehicleData.deaths || 0) > 0 ? (vehicleData.total_kills || 0) / vehicleData.deaths : (vehicleData.total_kills || 0);
}
return value;
});
const bestValue = stat.higher ? Math.max(...values) : Math.min(...values);
const cells = selectedPlayerVehicleCombos.map((combo, index) => {
const value = values[index];
const isBest = value === bestValue && selectedPlayerVehicleCombos.length > 1;
return `<td class="${isBest ? 'stat-best' : ''}">${stat.format(value)}</td>`;
}).join('');
return `<tr><td>${stat.label}</td>${cells}</tr>`;
}).join('');
document.getElementById('playerVehicleTableHead').innerHTML = headerRow;
document.getElementById('playerVehicleTableBody').innerHTML = bodyRows;
}
function handlePlayerVehicleComboKeydown(event) {
if (event.key === 'Escape') {
hideSearchResults();
document.getElementById('playerVehicleComboSearch').blur();
}
}
function searchPlayers() {
const searchTerm = document.getElementById('playerSearch').value.toLowerCase().trim();
const resultsDiv = document.getElementById('playerResults');
if (searchTerm.length < 2) {
resultsDiv.classList.remove('show');
return;
}
const filteredPlayers = playersData
.filter(player => player.nick.toLowerCase().includes(searchTerm))
.slice(0, 10)
.sort((a, b) => b.total_kills - a.total_kills);
if (filteredPlayers.length === 0) {
resultsDiv.innerHTML = '<div class="search-result-item">' + __t('common.noPlayersFound') + '</div>';
} else {
resultsDiv.innerHTML = filteredPlayers.map(player => {
const squadronTag = player.squadron_name ? `<span class="player-squadron" style="font-family: 'skyquakesymbols', 'Inter', sans-serif !important; color: rgba(255, 255, 255, 0.4); font-size: 0.85rem; margin-right: 0.4rem;">${escapeHtml(player.squadron_name)}</span>` : '';
return `
<div class="search-result-item" onclick="addPlayer(${player.uid})">
<div class="result-name">${squadronTag}${escapeHtml(player.nick)}</div>
<div class="result-stats">${player.total_kills} ${__t('js.killsSuffix')} • ${player.winRate.toFixed(1)}% ${__t('js.winRateSuffix')}</div>
</div>
`;
}).join('');
}
resultsDiv.classList.add('show');
}
async function searchVehicles() {
const searchTerm = document.getElementById('vehicleSearch').value.toLowerCase().trim();
const resultsDiv = document.getElementById('vehicleResults');
if (searchTerm.length < 1) {
resultsDiv.classList.remove('show');
return;
}
await ensureComparisonVehicleList();
if (window.vehicleI18n && window.vehicleI18n.ensureLoaded) await window.vehicleI18n.ensureLoaded();
const filteredVehicles = [];
for (const entry of comparisonVehicleList) {
const display = vehicleDisplay(entry.vehicle_internal, entry.vehicle_internal);
if (!display.replace(/\u00A0/g, ' ').toLowerCase().includes(searchTerm)) continue;
filteredVehicles.push({
vehicle: display,
vehicle_internal: entry.vehicle_internal,
total: entry.total || 0
});
if (filteredVehicles.length >= 25) break;
}
if (filteredVehicles.length === 0) {
resultsDiv.innerHTML = '<div class="search-result-item">' + __t('common.noVehiclesFound') + '</div>';
} else {
resultsDiv.innerHTML = filteredVehicles.map(vehicle => `
<div class="search-result-item" onclick="addVehicle('${escapeHtml(vehicle.vehicle_internal)}')">
<div class="result-name" data-vehicle-internal="${escapeHtml(vehicle.vehicle_internal || '')}" style="font-family:'skyquakesymbols','Inter',sans-serif;">${escapeHtml(vehicle.vehicle)}</div>
<div class="result-stats">${vehicle.total} ${__t('analytics.compColSpawns').toLowerCase()}</div>
</div>
`).join('');
}
resultsDiv.classList.add('show');
}
function addPlayer(uid) {
const player = playersData.find(p => p.uid == uid);
if (!player || selectedPlayers.some(p => p.uid === player.uid)) return;
selectedPlayers.push(player);
updateSelectedPlayers();
updatePlayerComparison();
document.getElementById('playerSearch').value = '';
hideSearchResults();
}
async function addVehicle(vehicleInternal) {
const key = String(vehicleInternal || '').toLowerCase();
if (!key || selectedVehicles.some(v => (v.vehicle_internal || '').toLowerCase() === key)) return;
const vehicle = await fetchVehicleComparisonAggregate(key);
if (!vehicle) return;
selectedVehicles.push(vehicle);
updateSelectedVehicles();
updateVehicleComparison();
document.getElementById('vehicleSearch').value = '';
hideSearchResults();
}
function removePlayer(uid) {
selectedPlayers = selectedPlayers.filter(p => p.uid != uid); // Use loose equality to handle string/number conversion
updateSelectedPlayers();
updatePlayerComparison();
}
function removeVehicle(vehicleInternal) {
const key = String(vehicleInternal || '').toLowerCase();
selectedVehicles = selectedVehicles.filter(v => (v.vehicle_internal || '').toLowerCase() !== key);
updateSelectedVehicles();
updateVehicleComparison();
}
function addSquadron(squadronName) {
const squadron = squadronsData.find(s => s.tag_name === squadronName);
if (!squadron || selectedSquadrons.some(s => s.tag_name === squadron.tag_name)) return;
selectedSquadrons.push(squadron);
updateSelectedSquadrons();
updateSquadronComparison();
document.getElementById('squadronSearch').value = '';
document.getElementById('squadronDropdown').style.display = 'none';
}
function removeSquadron(squadronName) {
selectedSquadrons = selectedSquadrons.filter(s => s.tag_name !== squadronName);
updateSelectedSquadrons();
updateSquadronComparison();
}
function updateSelectedPlayers() {
const container = document.getElementById('selectedPlayers');
if (selectedPlayers.length === 0) {
container.innerHTML = '<p style="color: rgba(255, 255, 255, 0.6); text-align: center; padding: 1rem;">' + __t('leaderboard.noPlayersSelected') + '</p>';
return;
}
container.innerHTML = selectedPlayers.map((player, index) => `
<span class="selected-item">
${escapeHtml(player.nick)}
<button class="selected-item-remove" data-player-uid="${player.uid}" data-type="player">
<i class="fas fa-times"></i>
</button>
</span>
`).join('');
}
function updateSelectedVehicles() {
const container = document.getElementById('selectedVehicles');
if (selectedVehicles.length === 0) {
container.innerHTML = '<p style="color: rgba(255, 255, 255, 0.6); text-align: center; padding: 1rem;">' + __t('leaderboard.noVehiclesSelected') + '</p>';
return;
}
container.innerHTML = selectedVehicles.map((vehicle, index) => `
<span class="selected-item" data-vehicle-internal="${escapeHtml(vehicle.vehicle_internal || '')}" style="font-family:'skyquakesymbols','Inter',sans-serif;">
${escapeHtml(vehicle.vehicle)}
<button class="selected-item-remove" data-vehicle-internal="${escapeHtml(vehicle.vehicle_internal || '')}" data-type="vehicle">
<i class="fas fa-times"></i>
</button>
</span>
`).join('');
}
function updateSelectedSquadrons() {
const container = document.getElementById('selectedSquadrons');
if (selectedSquadrons.length === 0) {
container.innerHTML = '<p style="color: rgba(255, 255, 255, 0.6); text-align: center; padding: 1rem;">' + __t('leaderboard.noSquadronsSelected') + '</p>';
return;
}
container.innerHTML = selectedSquadrons.map((squadron, index) => `
<span class="selected-item">
<span style="font-family:'skyquakesymbols','Inter',sans-serif;">${escapeHtml(squadron.tag_name)}</span>
<button class="selected-item-remove" data-squadron-name="${escapeHtml(squadron.tag_name)}" data-type="squadron">
<i class="fas fa-times"></i>
</button>
</span>
`).join('');
}
function updatePlayerComparison() {
const resultDiv = document.getElementById('playerComparisonResult');
const placeholderDiv = document.getElementById('playerComparisonPlaceholder');
if (selectedPlayers.length < 2) {
resultDiv.style.display = 'none';
placeholderDiv.style.display = 'block';
return;
}
displayPlayerComparisonTable();
resultDiv.style.display = 'block';
placeholderDiv.style.display = 'none';
}
function displayPlayerComparisonTable() {
const stats = [
{ label: __t('common.totalKills'), key: 'total_kills', format: (val) => formatNumber(val), higher: true },
{ label: __t('leaderboard.kdRatio'), key: 'kdr', format: (val) => val.toFixed(2), higher: true },
{ label: __t('common.winRate'), key: 'winRate', format: (val) => val.toFixed(1) + '%', higher: true },
{ label: __t('leaderboard.killsPerSpawnShort'), key: 'kps', format: (val) => val.toFixed(2), higher: true },
{ label: __t('common.battles'), key: 'battles', format: (val) => formatNumber(val), higher: true },
{ label: __t('common.wins'), key: 'wins', format: (val) => formatNumber(val), higher: true },
{ label: __t('common.assists'), key: 'assists', format: (val) => formatNumber(val), higher: true },
{ label: __t('common.captures'), key: 'captures', format: (val) => formatNumber(val), higher: true },
{ label: __t('common.deaths'), key: 'deaths', format: (val) => formatNumber(val), higher: false }
];
// Create table header
const headerRow = `
<tr>
<th>${__t('leaderboard.statistic')}</th>
${selectedPlayers.map(player => {
const squadronTag = player.squadron_name ? `<span class="player-squadron" style="font-family: 'skyquakesymbols', 'Inter', sans-serif !important; color: rgba(255, 255, 255, 0.4); font-size: 0.75rem; margin-right: 0.4rem;">${escapeHtml(player.squadron_name)}</span>` : '';
return `<th>${squadronTag}${escapeHtml(player.nick)}</th>`;
}).join('')}
</tr>
`;
// Create table body
const bodyRows = stats.map(stat => {
const values = selectedPlayers.map(player => player[stat.key] || 0);
const bestValue = stat.higher ? Math.max(...values) : Math.min(...values);
const cells = selectedPlayers.map(player => {
const value = player[stat.key] || 0;
const isBest = value === bestValue && selectedPlayers.length > 1;
return `<td class="${isBest ? 'stat-best' : ''}">${stat.format(value)}</td>`;
}).join('');
return `<tr><td>${stat.label}</td>${cells}</tr>`;
}).join('');
document.getElementById('playerTableHead').innerHTML = headerRow;
document.getElementById('playerTableBody').innerHTML = bodyRows;
}
function updateVehicleComparison() {
const resultDiv = document.getElementById('vehicleComparisonResult');
const placeholderDiv = document.getElementById('vehicleComparisonPlaceholder');
if (selectedVehicles.length < 2) {
resultDiv.style.display = 'none';
placeholderDiv.style.display = 'block';
return;
}
displayVehicleComparisonTable();
resultDiv.style.display = 'block';
placeholderDiv.style.display = 'none';
}
function updateSquadronComparison() {
const resultDiv = document.getElementById('squadronComparisonResult');
const placeholderDiv = document.getElementById('squadronComparisonPlaceholder');
if (selectedSquadrons.length < 2) {
resultDiv.style.display = 'none';
placeholderDiv.style.display = 'block';
return;
}
displaySquadronComparisonTable();
resultDiv.style.display = 'block';
placeholderDiv.style.display = 'none';
}
function displayVehicleComparisonTable() {
const stats = [
{ label: __t('common.totalBattles'), key: 'total_battles', format: (val) => formatNumber(val), higher: true },
{ label: __t('leaderboard.avgWinRateShort'), key: 'avg_win_rate', format: (val) => val.toFixed(1) + '%', higher: true },
{ label: __t('common.groundKills'), key: 'ground_kills', format: (val) => formatNumber(val), higher: true },
{ label: __t('common.airKills'), key: 'air_kills', format: (val) => formatNumber(val), higher: true },
{ label: __t('common.totalKills'), key: 'total_kills', format: (val) => formatNumber(val), higher: true },
{ label: __t('common.players'), key: 'players', format: (val) => formatNumber(val), higher: true },
{ label: __t('leaderboard.avgKillsPlayerShort'), key: 'avg_kills', format: (val) => val.toFixed(1), higher: true }
];
const headerRow = `
<tr>
<th>${__t('leaderboard.statistic')}</th>
${selectedVehicles.map(vehicle => `<th data-vehicle-internal="${escapeHtml(vehicle.vehicle_internal || '')}" style="font-family:'skyquakesymbols','Inter',sans-serif;">${escapeHtml(vehicle.vehicle)}</th>`).join('')}
</tr>
`;
const bodyRows = stats.map(stat => {
const values = selectedVehicles.map(vehicle => vehicle[stat.key] || 0);
const bestValue = stat.higher ? Math.max(...values) : Math.min(...values);
const cells = selectedVehicles.map(vehicle => {
const value = vehicle[stat.key] || 0;
const isBest = value === bestValue && selectedVehicles.length > 1;
return `<td class="${isBest ? 'stat-best' : ''}">${stat.format(value)}</td>`;
}).join('');
return `<tr><td>${stat.label}</td>${cells}</tr>`;
}).join('');
document.getElementById('vehicleTableHead').innerHTML = headerRow;
document.getElementById('vehicleTableBody').innerHTML = bodyRows;
}
function displaySquadronComparisonTable() {
const stats = [
{ label: __t('common.members'), key: 'player_count', format: (val) => formatNumber(val), higher: true },
{ label: __t('common.totalBattles'), key: 'total_battles', format: (val) => formatNumber(val), higher: true },
{ label: __t('common.wins'), key: 'wins', format: (val) => formatNumber(val), higher: true },
{ label: __t('common.winRate'), key: 'win_rate', format: (val) => val.toFixed(1) + '%', higher: true },
{ label: __t('common.totalKills'), key: 'total_kills', format: (val) => formatNumber(val), higher: true },
{ label: __t('leaderboard.totalDeaths'), key: 'deaths', format: (val) => formatNumber(val), higher: false },
{ label: __t('common.kdr'), key: 'kdr', format: (val) => val.toFixed(2), higher: true },
{ label: __t('leaderboard.totalAssists'), key: 'assists', format: (val) => formatNumber(val), higher: true },
{ label: __t('leaderboard.totalCaptures'), key: 'captures', format: (val) => formatNumber(val), higher: true },
{ label: __t('common.points'), key: 'points_total', format: (val) => formatNumber(val), higher: true, calculate: (s) => s.points?.total_points || 0 },
{ label: __t('leaderboard.avgKillsMember'), key: 'avg_kills_per_member', format: (val) => val.toFixed(1), higher: true, calculate: (s) => s.player_count > 0 ? s.total_kills / s.player_count : 0 },
{ label: __t('leaderboard.avgBattlesMember'), key: 'avg_battles_per_member', format: (val) => val.toFixed(1), higher: true, calculate: (s) => s.player_count > 0 ? s.total_battles / s.player_count : 0 }
];
const headerRow = `
<tr>
<th>${__t('leaderboard.statistic')}</th>
${selectedSquadrons.map(squadron => `<th><span style="font-family:'skyquakesymbols','Inter',sans-serif;">${escapeHtml(squadron.tag_name)}</span></th>`).join('')}
</tr>
`;
const bodyRows = stats.map(stat => {
const values = selectedSquadrons.map(squadron => {
if (stat.calculate) {
return stat.calculate(squadron);
}
return squadron[stat.key] || 0;
});
const bestValue = stat.higher ? Math.max(...values) : Math.min(...values);
const cells = selectedSquadrons.map((squadron, index) => {
const value = values[index];
const isBest = value === bestValue && selectedSquadrons.length > 1;
return `<td class="${isBest ? 'stat-best' : ''}">${stat.format(value)}</td>`;
}).join('');
return `<tr><td>${stat.label}</td>${cells}</tr>`;
}).join('');
document.getElementById('squadronTableHead').innerHTML = headerRow;
document.getElementById('squadronTableBody').innerHTML = bodyRows;
}
function handlePlayerKeydown(event) {
if (event.key === 'Escape') {
hideSearchResults();
document.getElementById('playerSearch').blur();
}
}
async function handleVehicleKeydown(event) {
if (event.key === 'Escape') {
hideSearchResults();
document.getElementById('vehicleSearch').blur();
} else if (event.key === 'Enter') {
event.preventDefault();
const input = document.getElementById('vehicleSearch');
const exact = input.value.trim().toLowerCase();
if (!exact) return;
await ensureComparisonVehicleList();
if (window.vehicleI18n && window.vehicleI18n.ensureLoaded) await window.vehicleI18n.ensureLoaded();
const match = comparisonVehicleList.find(v => vehicleDisplay(v.vehicle_internal, v.vehicle_internal).toLowerCase() === exact);
if (match) addVehicle(match.vehicle_internal);
}
}
function handleSquadronKeydown(event) {
if (event.key === 'Escape') {
document.getElementById('squadronDropdown').style.display = 'none';
document.getElementById('squadronSearch').blur();
}
}
function formatNumber(num) {
if (num >= 1000000) {
return (num / 1000000).toFixed(1) + 'M';
} else if (num >= 1000) {
return (num / 1000).toFixed(1) + 'K';
}
return num.toLocaleString();
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function initializeComparison() {
updateSelectedPlayers();
updateSelectedVehicles();
updateSelectedSquadrons();
}
document.addEventListener('click', function(event) {
if (!event.target.closest('.search-container')) {
hideSearchResults();
}
if (event.target.closest('.selected-item-remove')) {
const button = event.target.closest('.selected-item-remove');
const type = button.getAttribute('data-type');
if (type === 'player') {
const uid = button.getAttribute('data-player-uid');
removePlayer(uid);
} else if (type === 'vehicle') {
const vehicleInternal = button.getAttribute('data-vehicle-internal');
removeVehicle(vehicleInternal);
} else if (type === 'squadron') {
const squadronName = button.getAttribute('data-squadron-name');
removeSquadron(squadronName);
} else if (type === 'player-vehicle-combo') {
const comboKey = button.getAttribute('data-combo-key');
removePlayerVehicleCombo(comboKey);
}
}
});
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => {
loadComparisonData().then(() => {
initializeComparison();
});
}, 100);
});
</script>
</body>
</html>