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>
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="<%= lang %>">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||||
<% const __status = (typeof statusCode !== 'undefined') ? statusCode : 404; %>
|
||||
<title><%= __status %> <%= typeof error !== 'undefined' ? '- Error' : '- Page Not Found' %> | <%= typeof botName !== 'undefined' ? botName : 'Toothless SQB Bot' %></title>
|
||||
<link rel="icon" type="image/png" href="/images/transparent_toothlessssss.png">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/css/output.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
</head>
|
||||
<body class="min-h-screen text-white flex items-center justify-center p-6">
|
||||
<div class="text-center max-w-2xl">
|
||||
<div class="mb-8">
|
||||
<img src="/images/toothless_server.gif" alt="Toothless" class="w-24 h-24 mx-auto mb-8 opacity-70">
|
||||
</div>
|
||||
|
||||
<div class="text-8xl lg:text-9xl font-bold mb-6 bg-gradient-to-r from-accent to-muted bg-clip-text text-transparent">
|
||||
<%= __status %>
|
||||
</div>
|
||||
|
||||
<h1 class="text-3xl lg:text-4xl font-bold text-accent mb-4">
|
||||
<%= typeof error !== 'undefined' ? t('errors.error') : t('errors.pageNotFound') %>
|
||||
</h1>
|
||||
|
||||
<p class="text-lg text-muted mb-8 leading-relaxed">
|
||||
<%= typeof error !== 'undefined' ? error : t('errors.oopsNotFound') %>
|
||||
</p>
|
||||
|
||||
<a href="/" class="btn-primary px-8 py-4 rounded-xl text-base font-bold inline-flex items-center">
|
||||
<i class="fas fa-home mr-3"></i>
|
||||
<%= t('common.backToHome') %>
|
||||
</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,960 @@
|
||||
<!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('docs.title') %> - <%= botName %></title>
|
||||
<meta name="description" content="<%= t('docs.subtitle') %> <%= botName %>">
|
||||
<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; }
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
scroll-padding-top: 100px;
|
||||
}
|
||||
|
||||
/* Sidebar sticky positioning */
|
||||
.docs-sidebar {
|
||||
position: sticky;
|
||||
top: 100px;
|
||||
max-height: calc(100vh - 120px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Language dropdown styles */
|
||||
.languages-dropdown {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.dropdown-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 1rem;
|
||||
background: rgba(144, 238, 144, 0.1);
|
||||
border: 1px solid rgba(144, 238, 144, 0.2);
|
||||
border-radius: 0.5rem;
|
||||
color: #90EE90;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
width: 100%;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.dropdown-toggle:hover {
|
||||
background: rgba(144, 238, 144, 0.15);
|
||||
border-color: rgba(144, 238, 144, 0.3);
|
||||
}
|
||||
|
||||
.dropdown-arrow {
|
||||
margin-left: auto;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.dropdown-toggle.active .dropdown-arrow {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.languages-list {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s ease;
|
||||
}
|
||||
|
||||
.languages-list.active {
|
||||
max-height: 600px;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.languages-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
gap: 0.5rem;
|
||||
padding: 1rem;
|
||||
background: rgba(30, 30, 30, 0.6);
|
||||
border: 1px solid rgba(144, 238, 144, 0.1);
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.language-item {
|
||||
padding: 0.5rem;
|
||||
background: rgba(144, 238, 144, 0.05);
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.8125rem;
|
||||
color: #90EE90;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="text-white antialiased">
|
||||
<%- include('partials/nav', { activePage: 'docs' }) %>
|
||||
|
||||
<!-- Docs Header -->
|
||||
<section class="pt-32 pb-12 lg:pt-40 lg:pb-16">
|
||||
<div class="max-w-[1400px] mx-auto px-6 lg:px-8">
|
||||
<div class="text-center mb-8">
|
||||
<h1 class="text-4xl lg:text-5xl font-bold mb-4 text-accent"><%= t('docs.title') %></h1>
|
||||
<p class="text-lg text-muted max-w-2xl mx-auto"><%= t('docs.subtitle') %> <%= botName %></p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Docs Content -->
|
||||
<section class="pb-40">
|
||||
<div class="max-w-[1400px] mx-auto px-6 lg:px-8">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8">
|
||||
<!-- Sidebar -->
|
||||
<aside class="lg:col-span-3">
|
||||
<div class="docs-sidebar bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<h3 class="text-sm font-bold text-accent uppercase tracking-wide mb-4"><%= t('docs.quickNavigation') %></h3>
|
||||
<nav class="space-y-1">
|
||||
<a href="#getting-started" class="block px-3 py-2 text-sm text-muted hover:text-accent hover:bg-white/5 rounded-lg transition-colors"><%= t('docs.gettingStarted') %></a>
|
||||
<a href="#commands" class="block px-3 py-2 text-sm text-muted hover:text-accent hover:bg-white/5 rounded-lg transition-colors"><%= t('docs.commands') %></a>
|
||||
<a href="#setup" class="block px-3 py-2 text-sm text-muted hover:text-accent hover:bg-white/5 rounded-lg transition-colors"><%= t('docs.serverSetup') %></a>
|
||||
<a href="#premium" class="block px-3 py-2 text-sm text-muted hover:text-accent hover:bg-white/5 rounded-lg transition-colors"><%= t('nav.premium') %></a>
|
||||
<a href="#features" class="block px-3 py-2 text-sm text-muted hover:text-accent hover:bg-white/5 rounded-lg transition-colors"><%= t('docs.features') %></a>
|
||||
<a href="#examples" class="block px-3 py-2 text-sm text-muted hover:text-accent hover:bg-white/5 rounded-lg transition-colors"><%= t('docs.examples') %></a>
|
||||
<a href="#troubleshooting" class="block px-3 py-2 text-sm text-muted hover:text-accent hover:bg-white/5 rounded-lg transition-colors"><%= t('docs.troubleshooting') %></a>
|
||||
<a href="#stack-manager" class="block px-3 py-2 text-sm text-muted hover:text-accent hover:bg-white/5 rounded-lg transition-colors"><%= t('docs.stackManager') %></a>
|
||||
</nav>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="lg:col-span-9">
|
||||
<div class="space-y-20">
|
||||
|
||||
<!-- Getting Started -->
|
||||
<section id="getting-started" class="scroll-mt-24">
|
||||
<h2 class="text-3xl font-bold text-accent mb-4 flex items-center">
|
||||
<i class="fas fa-rocket mr-3"></i>
|
||||
<%= t('docs.gettingStarted') %>
|
||||
</h2>
|
||||
<p class="text-muted text-lg mb-8"><%= t('docs.welcomeMessage') %></p>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Step 1 -->
|
||||
<div class="flex gap-4 bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="w-12 h-12 rounded-full bg-accent text-[#1E1E1E] font-bold flex items-center justify-center text-xl">1</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h4 class="text-xl font-semibold text-accent mb-2"><%= t('docs.inviteTheBot') %></h4>
|
||||
<p class="text-muted leading-relaxed"><%- t('docs.inviteBotDesc') %></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 2 -->
|
||||
<div class="flex gap-4 bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="w-12 h-12 rounded-full bg-accent text-[#1E1E1E] font-bold flex items-center justify-center text-xl">2</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h4 class="text-xl font-semibold text-accent mb-2"><%= t('docs.runSetupWizard') %></h4>
|
||||
<p class="text-muted leading-relaxed mb-4"><%= t('docs.setupWizardDesc') %></p>
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent">
|
||||
/setup
|
||||
</div>
|
||||
<p class="text-muted text-sm mt-3"><%= t('docs.setupEasiest') %></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 3 -->
|
||||
<div class="flex gap-4 bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="w-12 h-12 rounded-full bg-accent text-[#1E1E1E] font-bold flex items-center justify-center text-xl">3</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h4 class="text-xl font-semibold text-accent mb-2"><%= t('docs.youreDone') %></h4>
|
||||
<p class="text-muted leading-relaxed"><%- t('docs.doneDesc') %></p>
|
||||
<p class="text-muted text-sm mt-3"><%- t('docs.premiumNote') %></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Alternative -->
|
||||
<div class="flex gap-4 bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6 opacity-75">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="w-12 h-12 rounded-full bg-[rgba(144,238,144,0.2)] text-accent font-bold flex items-center justify-center text-sm">ALT</div>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h4 class="text-xl font-semibold text-accent mb-2"><%= t('docs.manualSetup') %></h4>
|
||||
<p class="text-muted leading-relaxed mb-4"><%= t('docs.manualSetupDesc') %></p>
|
||||
<div class="space-y-2">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent">
|
||||
/set-squadron YOUR_SQUADRON_NAME
|
||||
</div>
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent">
|
||||
/quick-log YOUR_SQUADRON_NAME Both
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Commands -->
|
||||
<section id="commands" class="scroll-mt-24">
|
||||
<h2 class="text-3xl font-bold text-accent mb-4 flex items-center">
|
||||
<i class="fas fa-terminal mr-3"></i>
|
||||
<%= t('docs.commands') %>
|
||||
</h2>
|
||||
<p class="text-muted text-lg mb-8"><%- t('docs.allCommandsSlash') %></p>
|
||||
|
||||
<!-- Server Setup & Administration -->
|
||||
<div class="mb-12">
|
||||
<h3 class="text-2xl font-semibold text-accent mb-6"><%= t('docs.serverSetupAdmin') %></h3>
|
||||
<div class="space-y-4">
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/setup
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed"><%- t('docs.setupDesc') %> <span class="text-accent font-semibold"><%= t('docs.recommendedForNew') %></span></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/set-squadron [short_name]
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed mb-3"><%= t('docs.setSquadronDesc') %></p>
|
||||
<div class="text-sm">
|
||||
<span class="text-accent font-semibold"><%= t('docs.example') %>:</span>
|
||||
<code class="ml-2 px-2 py-1 bg-[rgba(0,0,0,0.4)] rounded text-accent">/set-squadron AVR</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/quick-log [squadron_name] [type]
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed mb-3"><%- t('docs.quickLogDesc') %></p>
|
||||
<p class="text-sm text-muted mb-3"><%- t('docs.quickLogPremiumNote') %></p>
|
||||
<div class="text-sm space-y-1">
|
||||
<div>
|
||||
<span class="text-accent font-semibold"><%= t('docs.example') %>:</span>
|
||||
<code class="ml-2 px-2 py-1 bg-[rgba(0,0,0,0.4)] rounded text-accent">/quick-log AVR</code>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-accent font-semibold"><%= t('docs.example') %>:</span>
|
||||
<code class="ml-2 px-2 py-1 bg-[rgba(0,0,0,0.4)] rounded text-accent">/quick-log AVR Points</code>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-accent font-semibold"><%= t('docs.example') %>:</span>
|
||||
<code class="ml-2 px-2 py-1 bg-[rgba(0,0,0,0.4)] rounded text-accent">/quick-log AVR Both</code>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-accent font-semibold"><%= t('docs.example') %>:</span>
|
||||
<code class="ml-2 px-2 py-1 bg-[rgba(0,0,0,0.4)] rounded text-accent">/quick-log AVR "Weekly BR"</code>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-accent font-semibold"><%= t('docs.example') %>:</span>
|
||||
<code class="ml-2 px-2 py-1 bg-[rgba(0,0,0,0.4)] rounded text-accent">/quick-log "" "Weekly BR"</code>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-sm text-muted mt-3">
|
||||
<strong class="text-accent">Weekly BR Report:</strong>
|
||||
Fires at the end of each BR rotation (~10 min after the BR window closes).
|
||||
With a squadron name, posts the top 15 players by ELO for that squadron.
|
||||
With an empty squadron field (or <code>*</code>), posts the top 20 squadrons
|
||||
of the week with their top 5 players each. Free for all servers.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/autolog-management
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed"><%- t('docs.autologDesc') %> <%- t('docs.autologPremiumNote') %></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/diagnose-perms
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed"><%= t('docs.diagnosePermsDesc') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Squadron Information -->
|
||||
<div class="mb-12">
|
||||
<h3 class="text-2xl font-semibold text-accent mb-6"><%= t('docs.squadronInformation') %></h3>
|
||||
<div class="space-y-4">
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/sq-info [squadron]
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed mb-3"><%= t('docs.sqInfoDesc') %></p>
|
||||
<div class="text-sm">
|
||||
<span class="text-accent font-semibold"><%= t('docs.example') %>:</span>
|
||||
<code class="ml-2 px-2 py-1 bg-[rgba(0,0,0,0.4)] rounded text-accent">/sq-info AVR</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/sq-info-graph [squadron]
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed mb-3"><%= t('docs.sqInfoGraphDesc') %></p>
|
||||
<div class="text-sm">
|
||||
<span class="text-accent font-semibold"><%= t('docs.example') %>:</span>
|
||||
<code class="ml-2 px-2 py-1 bg-[rgba(0,0,0,0.4)] rounded text-accent">/sq-info-graph AVR</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/comp [squadron_name]
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed mb-3"><%= t('docs.compDesc') %></p>
|
||||
<div class="text-sm">
|
||||
<span class="text-accent font-semibold"><%= t('docs.example') %>:</span>
|
||||
<code class="ml-2 px-2 py-1 bg-[rgba(0,0,0,0.4)] rounded text-accent">/comp AXYS</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/sq-track [squadron]
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed mb-3"><%= t('docs.trackDesc') %></p>
|
||||
<div class="text-sm">
|
||||
<span class="text-accent font-semibold"><%= t('docs.example') %>:</span>
|
||||
<code class="ml-2 px-2 py-1 bg-[rgba(0,0,0,0.4)] rounded text-accent">/sq-track 0NYX</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/top
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed"><%= t('docs.topDesc') %></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/sq-stats [squadron]
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed"><%= t('docs.sqStatsDesc') %></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/loss-calculator [player] [squadron]
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed"><%= t('docs.lossCalculatorDesc') %></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/recent [squadron]
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed"><%= t('docs.recentDesc') %></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/analytics [squadron] [view]
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed"><%= t('docs.analyticsDesc') %></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/sq-card [season] [squadron] [theme]
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed mb-3"><%= t('docs.sqCardDesc') %></p>
|
||||
<div class="text-sm">
|
||||
<span class="text-accent font-semibold"><%= t('docs.example') %>:</span>
|
||||
<code class="ml-2 px-2 py-1 bg-[rgba(0,0,0,0.4)] rounded text-accent">/sq-card 2026-II AVR</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/vs [squadron]
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed"><%= t('docs.vsDesc') %></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/leaderboard
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed"><%= t('docs.leaderboardLinkDesc') %></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/query [query]
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed"><%= t('docs.queryDesc') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Player Stats -->
|
||||
<div class="mb-12">
|
||||
<h3 class="text-2xl font-semibold text-accent mb-6"><%= t('docs.playerStats') %></h3>
|
||||
<div class="space-y-4">
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/player-stats [username]
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed mb-3"><%= t('docs.playerStatsDesc') %></p>
|
||||
<div class="text-sm">
|
||||
<span class="text-accent font-semibold"><%= t('docs.example') %>:</span>
|
||||
<code class="ml-2 px-2 py-1 bg-[rgba(0,0,0,0.4)] rounded text-accent">/player-stats Frovy</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/view-player-games [username]
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed mb-3"><%= t('docs.viewPlayerGamesDesc') %></p>
|
||||
<div class="text-sm">
|
||||
<span class="text-accent font-semibold"><%= t('docs.example') %>:</span>
|
||||
<code class="ml-2 px-2 py-1 bg-[rgba(0,0,0,0.4)] rounded text-accent">/view-player-games Frovy</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/card [season] [player] [theme]
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed mb-3"><%= t('docs.cardDesc') %></p>
|
||||
<div class="text-sm">
|
||||
<span class="text-accent font-semibold"><%= t('docs.example') %>:</span>
|
||||
<code class="ml-2 px-2 py-1 bg-[rgba(0,0,0,0.4)] rounded text-accent">/card 2026-II Frovy</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/view-match [match_id] or [player_name]
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed mb-3"><%- t('docs.viewMatchDesc') %></p>
|
||||
<div class="text-sm">
|
||||
<span class="text-accent font-semibold"><%= t('docs.examples2') %>:</span>
|
||||
<code class="ml-2 px-2 py-1 bg-[rgba(0,0,0,0.4)] rounded text-accent">/view-match match_id:abc123</code>
|
||||
<code class="ml-2 px-2 py-1 bg-[rgba(0,0,0,0.4)] rounded text-accent">/view-match player_name:Frovy</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/compare [player1] [player2] ... [player7]
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed mb-3"><%= t('docs.compareDesc') %></p>
|
||||
<div class="text-sm">
|
||||
<span class="text-accent font-semibold"><%= t('docs.example') %>:</span>
|
||||
<code class="ml-2 px-2 py-1 bg-[rgba(0,0,0,0.4)] rounded text-accent">/compare Frovy NotSoGroomless</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Meta -->
|
||||
<div class="mb-12">
|
||||
<h3 class="text-2xl font-semibold text-accent mb-6"><%= t('docs.metaData') %></h3>
|
||||
<div class="space-y-4">
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/meta-management
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed"><%= t('docs.metaManagementDesc') %></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/meta [vehicle]
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed"><%= t('docs.metaDesc') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Settings & Utilities -->
|
||||
<div class="mb-12">
|
||||
<h3 class="text-2xl font-semibold text-accent mb-6"><%= t('docs.settingsUtilities') %></h3>
|
||||
<div class="space-y-4">
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/language
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed"><%= t('docs.languageDesc') %></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/schedule
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed"><%= t('docs.scheduleDesc') %></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/website
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed"><%= t('docs.websiteDesc') %></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/credits
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed"><%= t('docs.creditsDesc') %></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.15)] rounded-xl p-6">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg px-4 py-3 font-mono text-sm text-accent flex-1">
|
||||
/unlock
|
||||
</div>
|
||||
<span class="flex-shrink-0 px-2 py-1 bg-yellow-400/10 border border-yellow-400/30 text-yellow-400 text-xs font-semibold rounded-lg"><i class="fas fa-crown mr-1"></i><%= t('docs.premiumBadge') %></span>
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed"><%- t('docs.unlockDesc') %></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/news
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed"><%= t('docs.newsDesc') %></p>
|
||||
</div>
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/donate
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed"><%= t('docs.donateDesc') %></p>
|
||||
</div>
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/bot-status
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed"><%= t('docs.botStatusDesc') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stack Manager -->
|
||||
<div class="mb-12" id="stack-manager">
|
||||
<h3 class="text-2xl font-semibold text-accent mb-6"><%= t('docs.stackManager') %></h3>
|
||||
<div class="space-y-4">
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/stack-create [vehicle]
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed mb-4"><%= t('docs.stackCreateDesc') %></p>
|
||||
<div class="space-y-2 text-sm text-muted">
|
||||
<p><%- t('docs.stackRequestToJoin') %></p>
|
||||
<p><%- t('docs.stackLeaveWithdraw') %></p>
|
||||
<p><%- t('docs.stackManagePanel') %></p>
|
||||
<ul class="list-disc list-inside ml-4 space-y-1">
|
||||
<li><%- t('docs.stackAcceptMembers') %></li>
|
||||
<li><%- t('docs.stackRemoveMembers') %></li>
|
||||
<li><%- t('docs.stackPingMembers') %></li>
|
||||
<li><%- t('docs.stackRenameStack') %></li>
|
||||
</ul>
|
||||
<p><%- t('docs.stackDisbandStack') %></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
/stack-manage
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed"><%= t('docs.stackManageDesc') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Translation -->
|
||||
<div class="mb-12">
|
||||
<h3 class="text-2xl font-semibold text-accent mb-6"><%= t('docs.translation') %></h3>
|
||||
<div class="space-y-4">
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-4 font-mono text-sm text-accent mb-4">
|
||||
<%= t('docs.translateContextMenu') %>
|
||||
</div>
|
||||
<p class="text-muted leading-relaxed mb-3"><%= t('docs.translateDesc') %></p>
|
||||
|
||||
<div class="languages-dropdown">
|
||||
<button class="dropdown-toggle" onclick="toggleLanguagesList()">
|
||||
<i class="fas fa-globe"></i> <%= t('docs.viewAllLanguages') %>
|
||||
<i class="fas fa-chevron-down dropdown-arrow"></i>
|
||||
</button>
|
||||
<div class="languages-list" id="languagesList">
|
||||
<div class="languages-grid">
|
||||
<div class="language-item">🇷🇺 Russian</div>
|
||||
<div class="language-item">🇺🇸 English (US)</div>
|
||||
<div class="language-item">🇬🇧 English (UK)</div>
|
||||
<div class="language-item">🇪🇸 Spanish</div>
|
||||
<div class="language-item">🇫🇷 French</div>
|
||||
<div class="language-item">🇩🇪 German</div>
|
||||
<div class="language-item">🇨🇳 Chinese (Simplified)</div>
|
||||
<div class="language-item">🇯🇵 Japanese</div>
|
||||
<div class="language-item">🇰🇷 Korean</div>
|
||||
<div class="language-item">🇮🇹 Italian</div>
|
||||
<div class="language-item">🇵🇹 Portuguese (Portugal)</div>
|
||||
<div class="language-item">🇧🇷 Portuguese (Brazil)</div>
|
||||
<div class="language-item">🇵🇱 Polish</div>
|
||||
<div class="language-item">🇱🇹 Lithuanian</div>
|
||||
<div class="language-item">🇱🇻 Latvian</div>
|
||||
<div class="language-item">🇪🇪 Estonian</div>
|
||||
<div class="language-item">🇩🇰 Danish</div>
|
||||
<div class="language-item">🇫🇮 Finnish</div>
|
||||
<div class="language-item">🇮🇩 Indonesian</div>
|
||||
<div class="language-item">🇳🇴 Norwegian</div>
|
||||
<div class="language-item">🇳🇱 Dutch</div>
|
||||
<div class="language-item">🇸🇪 Swedish</div>
|
||||
<div class="language-item">🇺🇦 Ukrainian</div>
|
||||
<div class="language-item">🇨🇿 Czech</div>
|
||||
<div class="language-item">🇸🇰 Slovak</div>
|
||||
<div class="language-item">🇸🇮 Slovenian</div>
|
||||
<div class="language-item">🇷🇴 Romanian</div>
|
||||
<div class="language-item">🇧🇬 Bulgarian</div>
|
||||
<div class="language-item">🇬🇷 Greek</div>
|
||||
<div class="language-item">🇭🇺 Hungarian</div>
|
||||
<div class="language-item">🇸🇦 Arabic</div>
|
||||
<div class="language-item">🇹🇷 Turkish</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Server Setup -->
|
||||
<section id="setup" class="scroll-mt-24">
|
||||
<h2 class="text-3xl font-bold text-accent mb-4 flex items-center">
|
||||
<i class="fas fa-cog mr-3"></i>
|
||||
<%= t('docs.serverSetup') %>
|
||||
</h2>
|
||||
<p class="text-muted text-lg mb-8"><%= t('docs.serverSetupSubtitle').replace('{botName}', botName) %></p>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<h4 class="text-xl font-semibold text-accent mb-4"><%= t('docs.requiredPermissions') %></h4>
|
||||
<ul class="space-y-2 text-muted list-disc list-inside">
|
||||
<li><%= t('docs.sendMessages') %></li>
|
||||
<li><%= t('docs.useSlashCommands') %></li>
|
||||
<li><%= t('docs.embedLinks') %></li>
|
||||
<li><%= t('docs.readMessageHistory') %></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<h4 class="text-xl font-semibold text-accent mb-4"><%= t('docs.recommendedChannelSetup') %></h4>
|
||||
<p class="text-muted leading-relaxed"><%- t('docs.recommendedChannelDesc') %></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<h4 class="text-xl font-semibold text-accent mb-4"><%= t('docs.roleConfiguration') %></h4>
|
||||
<p class="text-muted leading-relaxed"><%= t('docs.roleConfigurationDesc') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Premium -->
|
||||
<section id="premium" class="scroll-mt-24">
|
||||
<h2 class="text-3xl font-bold text-accent mb-4 flex items-center">
|
||||
<i class="fas fa-crown mr-3 text-yellow-400"></i>
|
||||
<%= t('nav.premium') %>
|
||||
</h2>
|
||||
<p class="text-muted text-lg mb-8"><%= t('docs.premiumSectionSubtitle') %></p>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-yellow-400/20 rounded-xl p-6">
|
||||
<h4 class="text-xl font-semibold text-accent mb-3"><%= t('docs.whatsIncluded') %></h4>
|
||||
<ul class="space-y-2 text-muted list-disc list-inside">
|
||||
<li><%= t('docs.premiumInclude1') %></li>
|
||||
<li><%= t('docs.premiumInclude2') %></li>
|
||||
<li><%= t('docs.premiumInclude3') %></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-yellow-400/20 rounded-xl p-6">
|
||||
<h4 class="text-xl font-semibold text-accent mb-3"><%= t('docs.tierOverview') %></h4>
|
||||
<p class="text-muted leading-relaxed mb-4"><%= t('docs.tierOverviewDesc') %></p>
|
||||
<ul class="space-y-2 text-muted list-disc list-inside">
|
||||
<li><strong class="text-accent">Standard</strong> — <%= t('docs.tierStandardLine') %></li>
|
||||
<li><strong class="text-accent">Pro</strong> — <%= t('docs.tierProLine') %></li>
|
||||
<li><strong class="text-accent">Max</strong> — <%= t('docs.tierMaxLine') %></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-yellow-400/20 rounded-xl p-6">
|
||||
<h4 class="text-xl font-semibold text-accent mb-3"><%= t('docs.pricingBilling') %></h4>
|
||||
<p class="text-muted leading-relaxed"><%- t('docs.pricingBillingDesc') %></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-yellow-400/20 rounded-xl p-6">
|
||||
<h4 class="text-xl font-semibold text-accent mb-3"><%= t('docs.howToSubscribe') %></h4>
|
||||
<ol class="space-y-2 text-muted list-decimal list-inside">
|
||||
<li><%- t('docs.subscribe1') %></li>
|
||||
<li><%= t('docs.subscribe2') %></li>
|
||||
<li><%= t('docs.subscribe3') %></li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-yellow-400/20 rounded-xl p-6">
|
||||
<h4 class="text-xl font-semibold text-accent mb-3"><%= t('docs.cancellation') %></h4>
|
||||
<p class="text-muted leading-relaxed"><%= t('docs.cancellationDesc') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features -->
|
||||
<section id="features" class="scroll-mt-24">
|
||||
<h2 class="text-3xl font-bold text-accent mb-8 flex items-center">
|
||||
<i class="fas fa-star mr-3"></i>
|
||||
<%= t('docs.features') %>
|
||||
</h2>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6 text-center">
|
||||
<i class="fas fa-chart-line text-accent text-4xl mb-4"></i>
|
||||
<h4 class="text-lg font-semibold text-accent mb-2"><%= t('docs.realTimeStatistics') %></h4>
|
||||
<p class="text-muted text-sm"><%= t('docs.realTimeStatisticsDesc') %></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6 text-center">
|
||||
<i class="fas fa-history text-accent text-4xl mb-4"></i>
|
||||
<h4 class="text-lg font-semibold text-accent mb-2"><%= t('docs.battleHistory') %></h4>
|
||||
<p class="text-muted text-sm"><%= t('docs.battleHistoryDesc') %></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6 text-center">
|
||||
<i class="fas fa-trophy text-accent text-4xl mb-4"></i>
|
||||
<h4 class="text-lg font-semibold text-accent mb-2"><%= t('docs.leaderboardsFeature') %></h4>
|
||||
<p class="text-muted text-sm"><%= t('docs.leaderboardsFeatureDesc') %></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6 text-center">
|
||||
<i class="fas fa-users text-accent text-4xl mb-4"></i>
|
||||
<h4 class="text-lg font-semibold text-accent mb-2"><%= t('docs.playerTracking') %></h4>
|
||||
<p class="text-muted text-sm"><%= t('docs.playerTrackingDesc') %></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6 text-center">
|
||||
<i class="fas fa-bell text-accent text-4xl mb-4"></i>
|
||||
<h4 class="text-lg font-semibold text-accent mb-2"><%= t('docs.smartAlerts') %></h4>
|
||||
<p class="text-muted text-sm"><%= t('docs.smartAlertsDesc') %></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6 text-center">
|
||||
<i class="fas fa-language text-accent text-4xl mb-4"></i>
|
||||
<h4 class="text-lg font-semibold text-accent mb-2"><%= t('docs.multiLanguageSupport') %></h4>
|
||||
<p class="text-muted text-sm"><%= t('docs.multiLanguageSupportDesc') %></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6 text-center">
|
||||
<i class="fas fa-image text-accent text-4xl mb-4"></i>
|
||||
<h4 class="text-lg font-semibold text-accent mb-2"><%= t('docs.seasonRecapCardTitle') %></h4>
|
||||
<p class="text-muted text-sm"><%= t('docs.seasonRecapCardDesc') %></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6 text-center">
|
||||
<i class="fas fa-user-astronaut text-accent text-4xl mb-4"></i>
|
||||
<h4 class="text-lg font-semibold text-accent mb-2"><%= t('docs.playerRecapCardTitle') %></h4>
|
||||
<p class="text-muted text-sm"><%= t('docs.playerRecapCardDesc') %></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6 text-center">
|
||||
<i class="fas fa-clock text-accent text-4xl mb-4"></i>
|
||||
<h4 class="text-lg font-semibold text-accent mb-2"><%= t('docs.timeCoordination') %></h4>
|
||||
<p class="text-muted text-sm"><%= t('docs.timeCoordinationDesc') %></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6 text-center">
|
||||
<i class="fas fa-search text-accent text-4xl mb-4"></i>
|
||||
<h4 class="text-lg font-semibold text-accent mb-2"><%= t('docs.advancedSearch') %></h4>
|
||||
<p class="text-muted text-sm"><%= t('docs.advancedSearchDesc') %></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6 text-center">
|
||||
<i class="fas fa-shield-alt text-accent text-4xl mb-4"></i>
|
||||
<h4 class="text-lg font-semibold text-accent mb-2"><%= t('docs.dataSecurity') %></h4>
|
||||
<p class="text-muted text-sm"><%= t('docs.dataSecurityDesc') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Examples -->
|
||||
<section id="examples" class="scroll-mt-24">
|
||||
<h2 class="text-3xl font-bold text-accent mb-8 flex items-center">
|
||||
<i class="fas fa-code mr-3"></i>
|
||||
<%= t('docs.usageExamples') %>
|
||||
</h2>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<h4 class="text-xl font-semibold text-accent mb-4"><%= t('docs.quickSetupRecommended') %></h4>
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-3 font-mono text-sm text-accent mb-2">
|
||||
/setup
|
||||
</div>
|
||||
<p class="text-muted text-sm"><%= t('docs.quickSetupDesc') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<h4 class="text-xl font-semibold text-accent mb-4"><%= t('docs.comparingPlayers') %></h4>
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-3 font-mono text-sm text-accent mb-2">
|
||||
/compare Frovy NotSoGroomless OwaArtin
|
||||
</div>
|
||||
<p class="text-muted text-sm"><%= t('docs.comparingPlayersDesc') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<h4 class="text-xl font-semibold text-accent mb-4"><%= t('docs.checkingSquadronInfo') %></h4>
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-3 font-mono text-sm text-accent mb-2">
|
||||
/sq-info AVR
|
||||
</div>
|
||||
<p class="text-muted text-sm"><%= t('docs.verifySquadronDesc') %></p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-3 font-mono text-sm text-accent mb-2">
|
||||
/recent AVR
|
||||
</div>
|
||||
<p class="text-muted text-sm"><%= t('docs.recentBattlesDesc') %></p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="bg-[rgba(0,0,0,0.4)] border border-[rgba(144,238,144,0.15)] rounded-lg p-3 font-mono text-sm text-accent mb-2">
|
||||
/vs 0NYX
|
||||
</div>
|
||||
<p class="text-muted text-sm"><%= t('docs.headToHeadDesc') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<h4 class="text-xl font-semibold text-accent mb-4"><%= t('docs.importantNote') %></h4>
|
||||
<p class="text-muted leading-relaxed mb-3"><%- t('docs.verifyFirst') %></p>
|
||||
<p class="text-muted leading-relaxed"><%- t('docs.cantFindSquadron') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Troubleshooting -->
|
||||
<section id="troubleshooting" class="scroll-mt-24 pb-12">
|
||||
<h2 class="text-3xl font-bold text-accent mb-8 flex items-center">
|
||||
<i class="fas fa-wrench mr-3"></i>
|
||||
<%= t('docs.troubleshooting') %>
|
||||
</h2>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<h4 class="text-xl font-semibold text-accent mb-4"><%= t('docs.botNotResponding') %></h4>
|
||||
<ul class="space-y-2 text-muted list-disc list-inside">
|
||||
<li><%= t('docs.checkOnline') %></li>
|
||||
<li><%= t('docs.verifyPermissions') %></li>
|
||||
<li><%= t('docs.tryDifferentChannel') %></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<h4 class="text-xl font-semibold text-accent mb-4"><%= t('docs.commandsNotWorking') %></h4>
|
||||
<ul class="space-y-2 text-muted list-disc list-inside">
|
||||
<li><%- t('docs.ensureSlash') %></li>
|
||||
<li><%= t('docs.checkRolePerms') %></li>
|
||||
<li><%= t('docs.tryRefreshing') %></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl p-6">
|
||||
<h4 class="text-xl font-semibold text-accent mb-4"><%= t('docs.dataNotSaving') %></h4>
|
||||
<ul class="space-y-2 text-muted list-disc list-inside">
|
||||
<li><%- t('docs.verifySendMessages') %></li>
|
||||
<li><%= t('docs.checkOutages') %></li>
|
||||
<li><%= t('docs.contactSupport') %></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Support Section -->
|
||||
<div class="bg-[rgba(44,44,44,0.3)] border border-[rgba(144,238,144,0.1)] rounded-xl px-12 py-12 text-center !mt-32 mb-12">
|
||||
<h3 class="text-2xl font-bold text-accent mb-6"><%= t('docs.needMoreHelp') %></h3>
|
||||
<p class="text-muted mb-10"><%= t('docs.needMoreHelpDesc') %></p>
|
||||
<div class="flex flex-wrap justify-center gap-6">
|
||||
<a href="/" class="btn-primary px-6 py-3 rounded-lg inline-flex items-center">
|
||||
<i class="fas fa-home mr-2"></i>
|
||||
<%= t('docs.backToHome') %>
|
||||
</a>
|
||||
<a href="/terms" class="px-6 py-3 rounded-lg border border-accent text-accent hover:bg-accent hover:text-[#1E1E1E] transition-all inline-flex items-center">
|
||||
<i class="fas fa-file-contract mr-2"></i>
|
||||
<%= t('docs.termsAndPrivacy') %>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 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"></script>
|
||||
<script>
|
||||
// Language dropdown toggle
|
||||
function toggleLanguagesList() {
|
||||
const list = document.getElementById('languagesList');
|
||||
const button = document.querySelector('.dropdown-toggle');
|
||||
list.classList.toggle('active');
|
||||
button.classList.toggle('active');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,612 @@
|
||||
<!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>Match History | <%= botName %></title>
|
||||
<meta name="description" content="Search and browse War Thunder squadron battle matches by player or map.">
|
||||
<link rel="icon" type="image/png" href="/images/transparent_toothlessssss.png">
|
||||
|
||||
<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">
|
||||
<link rel="stylesheet" href="/css/output.css">
|
||||
<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);
|
||||
}
|
||||
|
||||
.games-container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 8rem 1rem 2rem 1rem;
|
||||
min-height: calc(100vh - 200px);
|
||||
}
|
||||
|
||||
.games-header {
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.games-title {
|
||||
font-size: 3rem;
|
||||
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;
|
||||
}
|
||||
.games-subtitle {
|
||||
font-size: 1.2rem;
|
||||
color: #90EE90;
|
||||
}
|
||||
|
||||
/* Search form */
|
||||
.search-card {
|
||||
background: linear-gradient(135deg, rgba(62, 78, 62, 0.2) 0%, rgba(44, 44, 44, 0.2) 100%);
|
||||
border-radius: 1rem;
|
||||
padding: 1.5rem;
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(144, 238, 144, 0.15);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.search-form {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: flex-end;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.search-field {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.search-field:has(.autocomplete-list[style*="block"]) {
|
||||
z-index: 200;
|
||||
}
|
||||
.search-field label {
|
||||
display: block;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: #90EE90;
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
.search-input, .search-select {
|
||||
width: 100%;
|
||||
padding: 0.7rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid rgba(144, 238, 144, 0.2);
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
color: #F5F5DC;
|
||||
font-size: 0.95rem;
|
||||
font-family: inherit;
|
||||
outline: none;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
.search-input:focus, .search-select:focus {
|
||||
border-color: rgba(144, 238, 144, 0.5);
|
||||
}
|
||||
.search-input::placeholder { color: rgba(255, 255, 255, 0.3); }
|
||||
.search-input[type="date"]::-webkit-calendar-picker-indicator { filter: invert(0.7); cursor: pointer; }
|
||||
.search-input[type="date"] { color-scheme: dark; }
|
||||
.search-select option { background: #1E1E1E; color: #F5F5DC; }
|
||||
.search-btn {
|
||||
padding: 0.7rem 2rem;
|
||||
border-radius: 0.5rem;
|
||||
border: none;
|
||||
background: linear-gradient(135deg, #F5F5DC 0%, #E8E8D0 100%);
|
||||
color: #1E1E1E;
|
||||
font-weight: 700;
|
||||
font-size: 0.95rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.search-btn:hover { transform: translateY(-1px); box-shadow: 0 4px 15px rgba(245, 245, 220, 0.25); }
|
||||
.search-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
|
||||
|
||||
/* Autocomplete */
|
||||
.autocomplete-wrapper { position: relative; }
|
||||
.autocomplete-list {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: rgba(20, 20, 20, 0.98);
|
||||
border: 1px solid rgba(144, 238, 144, 0.15);
|
||||
border-radius: 0 0 0.5rem 0.5rem;
|
||||
max-height: 240px;
|
||||
overflow-y: auto;
|
||||
z-index: 999;
|
||||
backdrop-filter: blur(24px);
|
||||
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
|
||||
display: none;
|
||||
}
|
||||
.autocomplete-item {
|
||||
padding: 0.5rem 1rem;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
color: #F5F5DC;
|
||||
transition: background 0.1s;
|
||||
}
|
||||
.autocomplete-item:hover { background: rgba(144, 238, 144, 0.1); }
|
||||
.autocomplete-item .uid { color: rgba(255, 255, 255, 0.3); font-size: 0.75rem; margin-left: 0.5rem; }
|
||||
.autocomplete-item .sq-tag { color: rgba(144, 238, 144, 0.4); font-size: 0.8rem; margin-right: 0.4rem; font-family: 'skyquakesymbols', 'Inter', sans-serif; }
|
||||
.autocomplete-item .player-stats { color: rgba(255, 255, 255, 0.3); font-size: 0.7rem; display: block; margin-top: 0.1rem; }
|
||||
.sq-active-input { color: #90EE90 !important; font-family: 'skyquakesymbols', 'Inter', sans-serif; }
|
||||
|
||||
/* Results */
|
||||
.results-info {
|
||||
font-size: 0.9rem;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.matches-grid {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
/* Match card (from live.ejs) */
|
||||
.match-card {
|
||||
display: block;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
background: rgba(30, 30, 30, 0.8);
|
||||
border: 1px solid rgba(144, 238, 144, 0.15);
|
||||
border-radius: 0.75rem;
|
||||
padding: 1.25rem 1.5rem;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: border-color 0.2s, transform 0.15s;
|
||||
}
|
||||
.match-card:hover {
|
||||
border-color: rgba(144, 238, 144, 0.35);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.match-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background-image: var(--bg-img);
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
opacity: 0.08;
|
||||
pointer-events: none;
|
||||
}
|
||||
.match-card > * { position: relative; z-index: 1; }
|
||||
|
||||
.match-card-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.match-card-teams {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
.match-card-winner { color: #90EE90; text-shadow: 0 0 8px rgba(144, 238, 144, 0.2); font-family: 'skyquakesymbols', 'Inter', sans-serif; }
|
||||
.match-card-loser { color: rgba(255, 255, 255, 0.6); font-family: 'skyquakesymbols', 'Inter', sans-serif; }
|
||||
.match-card-vs { color: rgba(255, 255, 255, 0.3); font-size: 0.8rem; font-weight: 400; }
|
||||
.match-card-meta {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
font-size: 0.85rem;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
.match-card-meta i { color: #90EE90; margin-right: 0.3rem; }
|
||||
.match-card-badge {
|
||||
padding: 0.15rem 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.65rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
background: rgba(144, 238, 144, 0.12);
|
||||
color: #90EE90;
|
||||
border: 1px solid rgba(144, 238, 144, 0.25);
|
||||
}
|
||||
|
||||
/* States */
|
||||
.loading-state, .empty-state {
|
||||
text-align: center;
|
||||
padding: 4rem 2rem;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
.loading-state .spinner {
|
||||
width: 32px; height: 32px;
|
||||
border: 2px solid rgba(144, 238, 144, 0.2);
|
||||
border-top-color: #90EE90;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
margin: 0 auto 1rem;
|
||||
}
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
.empty-state i { font-size: 2rem; margin-bottom: 0.75rem; opacity: 0.5; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="text-white antialiased">
|
||||
<%- include('partials/nav', { activePage: 'games' }) %>
|
||||
|
||||
<div class="games-container">
|
||||
<div class="games-header">
|
||||
<h1 class="games-title"><%= t('games.title') %></h1>
|
||||
<p class="games-subtitle"><%= t('games.subtitle') %></p>
|
||||
</div>
|
||||
|
||||
<!-- Search -->
|
||||
<div class="search-card">
|
||||
<div class="search-form" id="searchForm">
|
||||
<div class="search-field" style="flex: 2;">
|
||||
<label><%= t('common.player') %></label>
|
||||
<div class="autocomplete-wrapper">
|
||||
<input type="text" class="search-input" id="playerInput" placeholder="<%= t('games.searchPlaceholder') %>" autocomplete="off">
|
||||
<div class="autocomplete-list" id="autocompleteList"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="search-field">
|
||||
<label><%= t('common.squadron') %></label>
|
||||
<div class="autocomplete-wrapper" style="position: relative;">
|
||||
<input type="text" class="search-input" id="squadronInput" placeholder="<%= t('games.squadronPlaceholder') %>" autocomplete="off" style="padding-right: 2rem;">
|
||||
<button id="squadronClear" style="display: none; background: none; border: none; color: rgba(255,255,255,0.3); cursor: pointer; font-size: 11px; padding: 4px 8px; position: absolute; right: 4px; top: 50%; transform: translateY(-50%); z-index: 2;">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
<div class="autocomplete-list" id="squadronDropdown"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="search-field">
|
||||
<label><%= t('games.filterByMap') %></label>
|
||||
<select class="search-select" id="mapSelect">
|
||||
<option value=""><%= t('games.allMaps') %></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="search-field">
|
||||
<label><%= t('leaderboard.from') %></label>
|
||||
<input type="date" class="search-input" id="dateFrom">
|
||||
</div>
|
||||
<div class="search-field">
|
||||
<label><%= t('leaderboard.to') %></label>
|
||||
<input type="date" class="search-input" id="dateTo">
|
||||
</div>
|
||||
<button class="search-btn" id="searchBtn" onclick="searchGames()">
|
||||
<i class="fas fa-search" style="margin-right: 0.4rem;"></i><%= t('games.search') %>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Results -->
|
||||
<div id="resultsInfo" class="results-info" style="display: none;"></div>
|
||||
|
||||
<div id="loadingState" class="loading-state" style="display: none;">
|
||||
<div class="spinner"></div>
|
||||
<p><%= t('games.searchingGames') %></p>
|
||||
</div>
|
||||
|
||||
<div id="emptyState" class="empty-state" style="display: none;">
|
||||
<i class="fas fa-search"></i>
|
||||
<p><%= t('games.noResults') %></p>
|
||||
</div>
|
||||
|
||||
<div class="matches-grid" id="matchesList"></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=3"></script>
|
||||
<script src="/js/header-search.js?v=2"></script>
|
||||
<script>
|
||||
const __t = window.__t;
|
||||
let debounceTimer = null;
|
||||
let squadronTimer = null;
|
||||
let playersData = [];
|
||||
let allSquadrons = [];
|
||||
let selectedSquadron = null; // tagged display name
|
||||
let selectedSquadronShort = null; // plain short name for API query
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function getMapImage(mapName) {
|
||||
if (!mapName) return null;
|
||||
const cleanMapName = mapName.replace(/^\s*\[[^\]]+\]\s*/, '').trim();
|
||||
const fileName = cleanMapName.replace(/\s+/g, '_').replace(/[^\w\u00C0-\u024F\-_.()]/g, '').replace(/_+/g, '_').replace(/^_|_$/g, '').toLowerCase();
|
||||
return `/MAPS/${fileName}.jpg`;
|
||||
}
|
||||
|
||||
// Load player leaderboard for autocomplete data
|
||||
async function loadLeaderboardData() {
|
||||
try {
|
||||
const response = await window.apiClient.getPlayerLeaderboard();
|
||||
playersData = (response.players || []).map(p => ({
|
||||
...p,
|
||||
total_kills: p.total_kills || ((p.ground_kills || 0) + (p.air_kills || 0)),
|
||||
})).sort((a, b) => b.total_kills - a.total_kills);
|
||||
|
||||
const counts = {};
|
||||
const shortNameMap = {};
|
||||
playersData.forEach(p => {
|
||||
if (p.squadron_name) {
|
||||
counts[p.squadron_name] = (counts[p.squadron_name] || 0) + 1;
|
||||
if (p.squadron_short_name) shortNameMap[p.squadron_name] = p.squadron_short_name;
|
||||
}
|
||||
});
|
||||
allSquadrons = Object.entries(counts)
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.map(([name, count]) => ({ name, count, short_name: shortNameMap[name] || name }));
|
||||
} catch (e) { console.error('Failed to load leaderboard data:', e); }
|
||||
}
|
||||
|
||||
// Populate map dropdown
|
||||
async function loadMaps() {
|
||||
try {
|
||||
const data = await window.apiClient.getMaps();
|
||||
const select = document.getElementById('mapSelect');
|
||||
(data.maps || []).forEach(map => {
|
||||
const opt = document.createElement('option');
|
||||
const clean = map.replace(/^\s*\[[^\]]+\]\s*/, '').trim();
|
||||
opt.value = map;
|
||||
opt.textContent = clean;
|
||||
select.appendChild(opt);
|
||||
});
|
||||
} catch (e) { console.error('Failed to load maps:', e); }
|
||||
}
|
||||
|
||||
// ── Player autocomplete ──
|
||||
const playerInput = document.getElementById('playerInput');
|
||||
const autocompleteList = document.getElementById('autocompleteList');
|
||||
|
||||
playerInput.addEventListener('input', function() {
|
||||
clearTimeout(debounceTimer);
|
||||
const query = this.value.trim().toLowerCase();
|
||||
if (query.length < 2) { autocompleteList.style.display = 'none'; return; }
|
||||
if (/^\d+$/.test(query)) { autocompleteList.style.display = 'none'; return; }
|
||||
|
||||
debounceTimer = setTimeout(() => {
|
||||
const hits = playersData
|
||||
.filter(p => p.nick.toLowerCase().includes(query))
|
||||
.slice(0, 10);
|
||||
if (hits.length === 0) { autocompleteList.style.display = 'none'; return; }
|
||||
|
||||
autocompleteList.innerHTML = hits.map(p => {
|
||||
const sqTag = p.squadron_name
|
||||
? `<span class="sq-tag">${escapeHtml(p.squadron_name)}</span>` : '';
|
||||
const kills = p.total_kills || 0;
|
||||
const wr = (p.win_rate || 0).toFixed(1);
|
||||
return `<div class="autocomplete-item" data-nick="${escapeHtml(p.nick || '')}">
|
||||
${sqTag}${escapeHtml(p.nick || 'Unknown')}<span class="uid">${p.uid}</span>
|
||||
<span class="player-stats">${kills} kills · ${wr}% WR</span>
|
||||
</div>`;
|
||||
}).join('');
|
||||
autocompleteList.style.display = 'block';
|
||||
}, 150);
|
||||
});
|
||||
|
||||
autocompleteList.addEventListener('click', function(e) {
|
||||
const item = e.target.closest('.autocomplete-item');
|
||||
if (!item) return;
|
||||
playerInput.value = item.dataset.nick;
|
||||
autocompleteList.style.display = 'none';
|
||||
});
|
||||
|
||||
playerInput.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter') { e.preventDefault(); searchGames(); }
|
||||
if (e.key === 'Escape') { autocompleteList.style.display = 'none'; playerInput.blur(); }
|
||||
});
|
||||
|
||||
// ── Squadron autocomplete ──
|
||||
const sqInput = document.getElementById('squadronInput');
|
||||
const sqDrop = document.getElementById('squadronDropdown');
|
||||
const sqClear = document.getElementById('squadronClear');
|
||||
|
||||
sqInput.addEventListener('input', function() {
|
||||
clearTimeout(squadronTimer);
|
||||
if (selectedSquadron) {
|
||||
selectedSquadron = null;
|
||||
selectedSquadronShort = null;
|
||||
sqInput.classList.remove('sq-active-input');
|
||||
sqClear.style.display = 'none';
|
||||
}
|
||||
const val = this.value.trim().toLowerCase();
|
||||
if (val.length < 1) { sqDrop.style.display = 'none'; return; }
|
||||
|
||||
squadronTimer = setTimeout(() => {
|
||||
const hits = allSquadrons.filter(s =>
|
||||
s.name.toLowerCase().includes(val) || s.short_name.toLowerCase().includes(val)
|
||||
).slice(0, 12);
|
||||
if (!hits.length) {
|
||||
sqDrop.innerHTML = '<div class="autocomplete-item" style="color: rgba(255,255,255,0.3);">' + __t('js.noSquadronsFound') + '</div>';
|
||||
} else {
|
||||
sqDrop.innerHTML = hits.map(s =>
|
||||
`<div class="autocomplete-item" data-name="${escapeHtml(s.name)}" data-short="${escapeHtml(s.short_name)}">
|
||||
<span class="sq-tag">${escapeHtml(s.name)}</span>
|
||||
<span class="uid">${s.count} ${__t('common.playersCount')}</span>
|
||||
</div>`
|
||||
).join('');
|
||||
}
|
||||
sqDrop.style.display = 'block';
|
||||
}, 100);
|
||||
});
|
||||
|
||||
sqDrop.addEventListener('click', function(e) {
|
||||
const item = e.target.closest('.autocomplete-item');
|
||||
if (!item || !item.dataset.name) return;
|
||||
pickSquadron(item.dataset.name, item.dataset.short);
|
||||
});
|
||||
|
||||
function pickSquadron(name, shortName) {
|
||||
selectedSquadron = name;
|
||||
selectedSquadronShort = shortName || name;
|
||||
sqInput.value = name;
|
||||
sqInput.classList.add('sq-active-input');
|
||||
sqClear.style.display = 'block';
|
||||
sqDrop.style.display = 'none';
|
||||
}
|
||||
|
||||
sqClear.addEventListener('click', function() {
|
||||
selectedSquadron = null;
|
||||
selectedSquadronShort = null;
|
||||
sqInput.value = '';
|
||||
sqInput.classList.remove('sq-active-input');
|
||||
sqClear.style.display = 'none';
|
||||
sqInput.focus();
|
||||
});
|
||||
|
||||
sqInput.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Tab') {
|
||||
const val = sqInput.value.trim().toLowerCase();
|
||||
if (!selectedSquadron && val.length >= 1) {
|
||||
const hits = allSquadrons.filter(s => s.name.toLowerCase().includes(val));
|
||||
if (hits.length >= 1) { e.preventDefault(); pickSquadron(hits[0].name, hits[0].short_name); }
|
||||
}
|
||||
}
|
||||
if (e.key === 'Enter') { e.preventDefault(); searchGames(); }
|
||||
if (e.key === 'Escape') { sqDrop.style.display = 'none'; sqInput.blur(); }
|
||||
});
|
||||
|
||||
// Close dropdowns on outside click
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!e.target.closest('.autocomplete-wrapper')) {
|
||||
autocompleteList.style.display = 'none';
|
||||
sqDrop.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
async function searchGames() {
|
||||
const player = document.getElementById('playerInput').value.trim();
|
||||
const map = document.getElementById('mapSelect').value;
|
||||
const squadron = selectedSquadronShort || document.getElementById('squadronInput').value.trim();
|
||||
const dateFrom = document.getElementById('dateFrom').value;
|
||||
const dateTo = document.getElementById('dateTo').value;
|
||||
|
||||
autocompleteList.style.display = 'none';
|
||||
document.getElementById('loadingState').style.display = 'block';
|
||||
document.getElementById('emptyState').style.display = 'none';
|
||||
document.getElementById('matchesList').innerHTML = '';
|
||||
document.getElementById('resultsInfo').style.display = 'none';
|
||||
document.getElementById('searchBtn').disabled = true;
|
||||
|
||||
try {
|
||||
const params = {};
|
||||
if (player) params.player = player;
|
||||
if (map) params.map = map;
|
||||
if (squadron) params.squadron = squadron;
|
||||
if (dateFrom) params.time_from = Math.floor(new Date(dateFrom).getTime() / 1000);
|
||||
if (dateTo) params.time_to = Math.floor(new Date(dateTo + 'T23:59:59').getTime() / 1000);
|
||||
params.limit = 100;
|
||||
|
||||
const data = await window.apiClient.searchGames(params);
|
||||
const matches = data.matches || [];
|
||||
|
||||
document.getElementById('loadingState').style.display = 'none';
|
||||
document.getElementById('searchBtn').disabled = false;
|
||||
|
||||
if (matches.length === 0) {
|
||||
document.getElementById('emptyState').style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('resultsInfo').style.display = 'block';
|
||||
document.getElementById('resultsInfo').textContent = `${matches.length} ${matches.length === 1 ? 'match' : 'matches'} found`;
|
||||
|
||||
document.getElementById('matchesList').innerHTML = matches.map(match => {
|
||||
const mapName = match.map_name || 'Unknown Map';
|
||||
const cleanMapName = mapName.replace(/^\s*\[[^\]]+\]\s*/, '').trim() || 'Unknown Map';
|
||||
const mapImg = getMapImage(mapName);
|
||||
const endTime = match.endtime_iso ? new Date(match.endtime_iso).toISOString().replace('T', ' ').substring(0, 16) + ' UTC' : '';
|
||||
|
||||
return `<a href="/games/${match.session_id}" class="match-card" style="${mapImg ? `--bg-img: url('${mapImg}')` : ''}">
|
||||
<div class="match-card-inner">
|
||||
<div style="display: flex; align-items: center; gap: 1rem;">
|
||||
<div class="match-card-teams">
|
||||
<span class="match-card-winner">${escapeHtml(match.winning_tag || match.winning_squadron || '?')}</span>
|
||||
<span class="match-card-vs">VS</span>
|
||||
<span class="match-card-loser">${escapeHtml(match.losing_tag || match.losing_squadron || '?')}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="match-card-meta">
|
||||
<span><i class="fas fa-map-marker-alt"></i>${escapeHtml(cleanMapName)}</span>
|
||||
<span><i class="fas fa-clock"></i>${endTime}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>`;
|
||||
}).join('');
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error('Search failed:', error);
|
||||
document.getElementById('loadingState').style.display = 'none';
|
||||
document.getElementById('searchBtn').disabled = false;
|
||||
document.getElementById('emptyState').style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
await Promise.all([loadMaps(), loadLeaderboardData()]);
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
if (params.get('player')) {
|
||||
document.getElementById('playerInput').value = params.get('player');
|
||||
}
|
||||
if (params.get('map')) {
|
||||
document.getElementById('mapSelect').value = params.get('map');
|
||||
}
|
||||
if (params.get('squadron')) {
|
||||
pickSquadron(params.get('squadron'));
|
||||
}
|
||||
if (params.get('from')) {
|
||||
document.getElementById('dateFrom').value = params.get('from');
|
||||
}
|
||||
if (params.get('to')) {
|
||||
document.getElementById('dateTo').value = params.get('to');
|
||||
}
|
||||
searchGames();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,651 @@
|
||||
<!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>
|
||||
<%= botName %>
|
||||
</title>
|
||||
<meta name="description" content="<%= botName %> - The Best Squadron Battles Bot.">
|
||||
<link rel="icon" type="image/png" href="/images/transparent_toothlessssss.png">
|
||||
|
||||
<!-- Preload Critical Resources -->
|
||||
<link rel="preload" href="/Fonts/symbols_skyquake.ttf" as="font" type="font/ttf" crossorigin="anonymous">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
|
||||
<!-- Optimized Font Loading -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
|
||||
<link rel="stylesheet" href="/css/output.css">
|
||||
|
||||
<!-- Defer non-critical CSS -->
|
||||
<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');
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* Performance-optimized base styles */
|
||||
* {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Remove individual section backgrounds - let body gradient show through */
|
||||
.hero-bg {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.hero-overlay {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* Optimized gradient text */
|
||||
.gradient-text {
|
||||
background: linear-gradient(135deg, #F5F5DC 0%, #E8E8D0 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
/* Enhanced Card styles */
|
||||
.feature-card {
|
||||
background: linear-gradient(135deg, rgba(62, 78, 62, 0.2) 0%, rgba(44, 44, 44, 0.2) 100%);
|
||||
border: 1px solid rgba(245, 245, 220, 0.08);
|
||||
backdrop-filter: blur(12px);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.card-hover:hover {
|
||||
transform: translateY(-3px);
|
||||
background: linear-gradient(135deg, rgba(62, 78, 62, 0.3) 0%, rgba(44, 44, 44, 0.3) 100%);
|
||||
border-color: rgba(144, 238, 144, 0.3);
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.card-hover:hover i {
|
||||
color: #F5F5DC;
|
||||
/* Icon turns cream on hover */
|
||||
transform: scale(1.1);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
/* Premium Button Glow */
|
||||
.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);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
.btn-primary:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Search Input Polish */
|
||||
.search-input-glass {
|
||||
background: rgba(30, 30, 30, 0.6);
|
||||
border: 1px solid rgba(245, 245, 220, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.search-input-glass:focus {
|
||||
background: rgba(40, 40, 40, 0.8);
|
||||
border-color: rgba(144, 238, 144, 0.4);
|
||||
box-shadow: 0 0 0 2px rgba(144, 238, 144, 0.1), inset 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* Trusted by animation - GPU accelerated */
|
||||
@keyframes scroll {
|
||||
0% {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translate3d(-50%, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-scroll {
|
||||
animation: scroll 60s linear infinite;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.trusted-scroll {
|
||||
mask-image: linear-gradient(90deg, transparent, black 10%, black 90%, transparent);
|
||||
-webkit-mask-image: linear-gradient(90deg, transparent, black 10%, black 90%, transparent);
|
||||
}
|
||||
|
||||
/* Utility classes */
|
||||
.text-accent {
|
||||
color: #F5F5DC;
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: #90EE90;
|
||||
}
|
||||
|
||||
/* Reduce animations on low-end devices */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Unified Search Bar */
|
||||
.search-bar {
|
||||
background: rgba(28, 28, 28, 0.6);
|
||||
border: 1px solid rgba(144, 238, 144, 0.06);
|
||||
border-radius: 16px;
|
||||
backdrop-filter: blur(20px);
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
.search-bar:focus-within {
|
||||
border-color: rgba(144, 238, 144, 0.18);
|
||||
}
|
||||
.search-drop {
|
||||
position: absolute;
|
||||
left: -4px;
|
||||
right: -4px;
|
||||
top: calc(100% + 8px);
|
||||
border-radius: 12px;
|
||||
max-height: 260px;
|
||||
overflow-y: auto;
|
||||
z-index: 999;
|
||||
background: rgba(20, 20, 20, 0.98);
|
||||
border: 1px solid rgba(144, 238, 144, 0.1);
|
||||
backdrop-filter: blur(24px);
|
||||
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.search-drop::-webkit-scrollbar { width: 4px; }
|
||||
.search-drop::-webkit-scrollbar-thumb { background: rgba(144, 238, 144, 0.15); border-radius: 4px; }
|
||||
.search-hit {
|
||||
display: block;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
padding: 10px 16px;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
.search-hit:last-child { border-bottom: none; }
|
||||
.search-hit:hover { background: rgba(144, 238, 144, 0.05); }
|
||||
.sq-active { color: #90EE90 !important; font-family: 'skyquakesymbols', 'Inter', sans-serif; }
|
||||
#squadronInput::placeholder, #playerInput::placeholder { color: rgba(255,255,255,0.35); }
|
||||
#squadronInput:focus, #playerInput:focus { outline: none; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="text-white antialiased">
|
||||
<%- include('partials/nav', { activePage: 'home' }) %>
|
||||
|
||||
<!-- Hero Section - Balanced Spacing -->
|
||||
<section class="hero-bg pt-32 pb-16 lg:pt-40 lg:pb-20 relative">
|
||||
<div class="hero-overlay absolute inset-0 pointer-events-none"></div>
|
||||
|
||||
<div class="max-w-[1400px] mx-auto px-6 lg:px-8 relative z-10">
|
||||
<!-- Top Row: Title and CTA -->
|
||||
<div class="text-center">
|
||||
|
||||
<!-- Heading -->
|
||||
<h1 class="text-3xl sm:text-4xl lg:text-5xl xl:text-6xl font-bold leading-[1.1] tracking-tight">
|
||||
<span class="gradient-text"><%= t('home.squadronBattles') %></span><br>
|
||||
<span class="text-muted"><%= t('home.madeSimple') %></span>
|
||||
</h1>
|
||||
|
||||
<!-- CTA Buttons -->
|
||||
<div class="flex flex-col sm:flex-row items-center justify-center gap-4" style="margin-top: 3rem;">
|
||||
<a href="#" class="btn-primary px-6 py-2.5 rounded-lg text-sm font-bold inline-flex items-center"
|
||||
id="ctaInviteBtn">
|
||||
<i class="fab fa-discord text-base mr-2"></i>
|
||||
<%= t('home.addToDiscord') %>
|
||||
</a>
|
||||
<a href="/docs"
|
||||
class="px-6 py-2.5 rounded-lg border text-accent font-semibold text-sm inline-flex items-center transition-colors hover:bg-white/5 bg-[rgba(168,230,207,0.06)] border-[rgba(168,230,207,0.2)]">
|
||||
<i class="fas fa-book mr-2"></i>
|
||||
<%= t('home.learnMore') %>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search Section -->
|
||||
<div class="max-w-[800px] mx-auto relative" style="margin-top: 5rem; margin-bottom: 4rem; z-index: 100;">
|
||||
<div class="search-bar flex p-3" id="searchBar">
|
||||
<div class="flex-1 px-4 py-3 relative">
|
||||
<div class="text-[10px] font-bold tracking-[0.2em] uppercase mb-2" style="color: rgba(144,238,144,0.35)"><%= t('home.searchBySquadron') %></div>
|
||||
<div class="flex items-center">
|
||||
<input type="text" id="squadronInput"
|
||||
style="flex:1; background:transparent; border:none; outline:none; color:#fff; font-size:14px; font-weight:500; padding:0;"
|
||||
placeholder="<%= t('home.typeSquadronName') %>" autocomplete="off">
|
||||
<button id="squadronClear" class="hidden" style="background:none; border:none; color:rgba(255,255,255,0.2); cursor:pointer; font-size:11px; padding:4px 8px; transition:color 0.2s;">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="squadronDropdown" class="search-drop hidden"></div>
|
||||
</div>
|
||||
<div class="self-stretch my-3" style="width: 1px; background: rgba(144,238,144,0.06)"></div>
|
||||
<div class="flex-1 px-4 py-3 relative">
|
||||
<div class="text-[10px] font-bold tracking-[0.2em] uppercase mb-2" style="color: rgba(144,238,144,0.35)"><%= t('home.orByPlayer') %></div>
|
||||
<div class="flex items-center">
|
||||
<input type="text" id="playerInput"
|
||||
style="flex:1; background:transparent; border:none; outline:none; color:#fff; font-size:14px; font-weight:500; padding:0;"
|
||||
placeholder="<%= t('home.typePlayerName') %>" autocomplete="off">
|
||||
</div>
|
||||
<div id="playerDropdown" class="search-drop hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Feature Cards Grid -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6" style="gap: 1.5rem;">
|
||||
<!-- Feature Cards - Compact -->
|
||||
<a href="/games" class="feature-card card-hover rounded-lg p-4 block">
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="w-10 h-10 rounded-lg flex items-center justify-center bg-[rgba(168,230,207,0.1)] border border-[rgba(168,230,207,0.2)] flex-shrink-0">
|
||||
<i class="fas fa-broadcast-tower text-accent"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-base font-semibold text-accent leading-tight"><%= t('home.liveFeed') %></h3>
|
||||
<p class="text-xs text-muted leading-tight mt-0.5"><%= t('home.realTimeMatches') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="/leaderboard/players" class="feature-card card-hover rounded-lg p-4 block">
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="w-10 h-10 rounded-lg flex items-center justify-center bg-[rgba(168,230,207,0.1)] border border-[rgba(168,230,207,0.2)] flex-shrink-0">
|
||||
<i class="fas fa-trophy text-accent"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-base font-semibold text-accent leading-tight"><%= t('nav.leaderboards') %></h3>
|
||||
<p class="text-xs text-muted leading-tight mt-0.5"><%= t('home.topPlayers') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="/leaderboard/vehicles" class="feature-card card-hover rounded-lg p-4 block">
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="w-10 h-10 rounded-lg flex items-center justify-center bg-[rgba(168,230,207,0.1)] border border-[rgba(168,230,207,0.2)] flex-shrink-0">
|
||||
<i class="fas fa-fighter-jet text-accent"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-base font-semibold text-accent leading-tight"><%= t('home.vehicleStatsCard') %></h3>
|
||||
<p class="text-xs text-muted leading-tight mt-0.5"><%= t('home.performanceMetrics') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="/analytics" class="feature-card card-hover rounded-lg p-4 block">
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="w-10 h-10 rounded-lg flex items-center justify-center bg-[rgba(168,230,207,0.1)] border border-[rgba(168,230,207,0.2)] flex-shrink-0">
|
||||
<i class="fas fa-chart-bar text-accent"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-base font-semibold text-accent leading-tight"><%= t('home.analyticsCard') %></h3>
|
||||
<p class="text-xs text-muted leading-tight mt-0.5"><%= t('home.globalStatistics') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="/squadrons" class="feature-card card-hover rounded-lg p-4 block">
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="w-10 h-10 rounded-lg flex items-center justify-center bg-[rgba(168,230,207,0.1)] border border-[rgba(168,230,207,0.2)] flex-shrink-0">
|
||||
<i class="fas fa-users text-accent"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-base font-semibold text-accent leading-tight"><%= t('home.squadronHubCard') %></h3>
|
||||
<p class="text-xs text-muted leading-tight mt-0.5"><%= t('home.squadronStats') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="/leaderboard/comparison" class="feature-card card-hover rounded-lg p-4 block">
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="w-10 h-10 rounded-lg flex items-center justify-center bg-[rgba(168,230,207,0.1)] border border-[rgba(168,230,207,0.2)] flex-shrink-0">
|
||||
<i class="fas fa-balance-scale text-accent"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-base font-semibold text-accent leading-tight"><%= t('home.comparisonCard') %></h3>
|
||||
<p class="text-xs text-muted leading-tight mt-0.5"><%= t('home.compareStats') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- CTA Section -->
|
||||
<section class="py-16 lg:py-20 relative overflow-hidden">
|
||||
<div class="max-w-[1400px] mx-auto px-6 lg:px-8 relative z-10">
|
||||
<div class="text-center max-w-3xl mx-auto">
|
||||
<h2 class="text-3xl lg:text-4xl font-bold mb-4 text-accent" id="random-squadron-cta">Meow</h2>
|
||||
<p class="text-lg text-muted mb-8"><%= t('home.joinServers') %></p>
|
||||
<a href="#" class="btn-primary px-10 py-4 rounded-xl text-lg inline-flex items-center font-bold"
|
||||
id="ctaInviteBtn2">
|
||||
<i class="fab fa-discord text-2xl mr-3"></i>
|
||||
<%= t('home.addToDiscord') %>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 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"></script>
|
||||
<script>
|
||||
let playersData = [];
|
||||
let allSquadrons = [];
|
||||
let selectedSquadron = null;
|
||||
let selectedSquadronClanId = null;
|
||||
let selectedSquadronShort = null;
|
||||
let searchTimeout, squadronTimeout;
|
||||
let playersState = 'unloaded'; // unloaded → loading → ready | error
|
||||
let squadronsState = 'unloaded';
|
||||
|
||||
const escapeHtml = (t) => { const d = document.createElement('div'); d.textContent = t; return d.innerHTML; };
|
||||
|
||||
const loadSquadronData = async () => {
|
||||
squadronsState = 'loading';
|
||||
try {
|
||||
const response = await window.apiClient.getSquadronLeaderboard();
|
||||
allSquadrons = (response.squadrons || []).map(s => ({
|
||||
name: s.long_name || s.tag_name || s.squadron_name,
|
||||
tag_name: s.tag_name,
|
||||
short_name: s.short_name || s.tag_name,
|
||||
clan_id: s.clan_id,
|
||||
player_count: s.player_count || 0
|
||||
}));
|
||||
squadronsState = 'ready';
|
||||
} catch (e) {
|
||||
console.error('Error loading squadron data:', e);
|
||||
squadronsState = 'error';
|
||||
}
|
||||
};
|
||||
|
||||
const loadPlayerData = async () => {
|
||||
if (playersState === 'ready' || playersState === 'loading') return;
|
||||
playersState = 'loading';
|
||||
try {
|
||||
const response = await window.apiClient.getPlayerLeaderboard();
|
||||
playersData = (response.players || []).map(p => ({
|
||||
...p,
|
||||
total_kills: p.total_kills || ((p.ground_kills || 0) + (p.air_kills || 0)),
|
||||
})).sort((a, b) => b.total_kills - a.total_kills);
|
||||
playersState = 'ready';
|
||||
if (!plDrop.classList.contains('hidden')) {
|
||||
renderPlayers(plInput.value.trim().toLowerCase());
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error loading player data:', e);
|
||||
playersState = 'error';
|
||||
}
|
||||
};
|
||||
|
||||
const sqInput = document.getElementById('squadronInput');
|
||||
const plInput = document.getElementById('playerInput');
|
||||
const sqDrop = document.getElementById('squadronDropdown');
|
||||
const plDrop = document.getElementById('playerDropdown');
|
||||
const sqClear = document.getElementById('squadronClear');
|
||||
|
||||
// --- Squadron search ---
|
||||
sqInput.addEventListener('input', () => {
|
||||
clearTimeout(squadronTimeout);
|
||||
if (selectedSquadron) {
|
||||
selectedSquadron = null;
|
||||
selectedSquadronClanId = null;
|
||||
selectedSquadronShort = null;
|
||||
sqInput.classList.remove('sq-active');
|
||||
sqClear.classList.add('hidden');
|
||||
plInput.placeholder = __t('home.typePlayerName');
|
||||
}
|
||||
const val = sqInput.value.trim().toLowerCase();
|
||||
if (val.length < 1) { sqDrop.classList.add('hidden'); return; }
|
||||
|
||||
if (squadronsState !== 'ready') {
|
||||
sqDrop.innerHTML = '<div class="p-3 text-center text-xs" style="color:rgba(144,238,144,0.25)">' + __t('common.loading') + '</div>';
|
||||
sqDrop.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
squadronTimeout = setTimeout(() => {
|
||||
const hits = allSquadrons.filter(s => {
|
||||
const names = [s.name, s.tag_name, s.short_name].filter(Boolean).map(n => n.toLowerCase());
|
||||
return names.some(n => n.includes(val));
|
||||
}).slice(0, 12);
|
||||
if (!hits.length) {
|
||||
sqDrop.innerHTML = '<div class="p-3 text-center text-xs" style="color:rgba(144,238,144,0.25)">' + __t('home.noSquadronsFound') + '</div>';
|
||||
} else {
|
||||
sqDrop.innerHTML = hits.map(s =>
|
||||
`<div class="search-hit" data-name="${escapeHtml(s.tag_name || s.name)}" data-short="${escapeHtml(s.short_name)}" data-clanid="${s.clan_id != null ? s.clan_id : ''}">
|
||||
<span class="font-['skyquakesymbols'] text-sm" style="color:#A8E6CF">${escapeHtml(s.name)}</span>
|
||||
<span class="text-[11px] ml-2" style="color:rgba(144,238,144,0.25)">${s.player_count} ${__t('common.playersCount')}</span>
|
||||
</div>`
|
||||
).join('');
|
||||
sqDrop.querySelectorAll('.search-hit').forEach(el =>
|
||||
el.addEventListener('click', () => pickSquadron(el.dataset.name, el.dataset.short, el.dataset.clanid))
|
||||
);
|
||||
}
|
||||
sqDrop.classList.remove('hidden');
|
||||
}, 100);
|
||||
});
|
||||
|
||||
sqInput.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Tab') {
|
||||
const val = sqInput.value.trim().toLowerCase();
|
||||
if (!selectedSquadron && val.length >= 1) {
|
||||
const hits = allSquadrons.filter(s => {
|
||||
const names = [s.name, s.tag_name, s.short_name].filter(Boolean).map(n => n.toLowerCase());
|
||||
return names.some(n => n.includes(val));
|
||||
});
|
||||
if (hits.length >= 1) {
|
||||
e.preventDefault();
|
||||
pickSquadron(hits[0].tag_name || hits[0].name, hits[0].short_name, hits[0].clan_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
if (selectedSquadronClanId != null) {
|
||||
window.location.href = '/squadrons/' + selectedSquadronClanId;
|
||||
} else if (selectedSquadronShort) {
|
||||
window.location.href = '/squadrons/' + encodeURIComponent(selectedSquadronShort);
|
||||
} else {
|
||||
const val = sqInput.value.trim().toLowerCase();
|
||||
const match = allSquadrons.find(s => {
|
||||
const names = [s.name, s.tag_name, s.short_name].filter(Boolean).map(n => n.toLowerCase());
|
||||
return names.some(n => n === val);
|
||||
});
|
||||
if (match) {
|
||||
const path = match.clan_id != null ? String(match.clan_id) : encodeURIComponent(match.short_name || match.name);
|
||||
window.location.href = '/squadrons/' + path;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (e.key === 'Escape') { sqDrop.classList.add('hidden'); sqInput.blur(); }
|
||||
});
|
||||
|
||||
function pickSquadron(tagName, shortName, clanId) {
|
||||
selectedSquadron = tagName;
|
||||
selectedSquadronClanId = clanId != null && clanId !== '' ? Number(clanId) : null;
|
||||
selectedSquadronShort = shortName || tagName;
|
||||
sqInput.value = tagName;
|
||||
sqInput.classList.add('sq-active');
|
||||
sqClear.classList.remove('hidden');
|
||||
sqDrop.classList.add('hidden');
|
||||
plInput.placeholder = __t('home.searchPlayersIn') + ' ' + (tagName || '') + '...';
|
||||
plInput.focus();
|
||||
}
|
||||
|
||||
sqClear.addEventListener('click', () => {
|
||||
selectedSquadron = null;
|
||||
selectedSquadronClanId = null;
|
||||
selectedSquadronShort = null;
|
||||
sqInput.value = '';
|
||||
sqInput.classList.remove('sq-active');
|
||||
sqClear.classList.add('hidden');
|
||||
plInput.placeholder = __t('home.typePlayerName');
|
||||
plDrop.classList.add('hidden');
|
||||
sqInput.focus();
|
||||
});
|
||||
|
||||
// --- Player search ---
|
||||
function renderPlayers(val) {
|
||||
if (playersState === 'loading') {
|
||||
plDrop.innerHTML = '<div class="p-3 text-center text-xs" style="color:rgba(144,238,144,0.25)"><i class="fas fa-spinner fa-spin mr-1"></i>' + __t('common.loading') + '</div>';
|
||||
plDrop.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
if (playersState === 'error') {
|
||||
plDrop.innerHTML = '<div class="p-3 text-center text-xs" style="color:rgba(255,80,80,0.5)">Failed to load. <button onclick="playersState=\'unloaded\';loadPlayerData()" style="text-decoration:underline;background:none;border:none;color:rgba(144,238,144,0.5);cursor:pointer">' + __t('common.retry') + '</button></div>';
|
||||
plDrop.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
let src = playersData;
|
||||
if (selectedSquadronClanId != null) {
|
||||
src = src.filter(p => p.squadron_clan_id === selectedSquadronClanId);
|
||||
} else if (selectedSquadron) {
|
||||
src = src.filter(p => (p.squadron_name || '').toLowerCase() === selectedSquadron.toLowerCase());
|
||||
}
|
||||
|
||||
const hits = val.length >= 2
|
||||
? src.filter(p => p.nick.toLowerCase().includes(val))
|
||||
: (selectedSquadron ? src : []);
|
||||
const results = hits.slice(0, 20);
|
||||
|
||||
if (!results.length && (val.length >= 2 || selectedSquadron)) {
|
||||
plDrop.innerHTML = '<div class="p-3 text-center text-xs" style="color:rgba(144,238,144,0.25)">' + __t('home.noPlayersFound') + '</div>';
|
||||
} else if (!results.length) {
|
||||
plDrop.classList.add('hidden'); return;
|
||||
} else {
|
||||
plDrop.innerHTML = results.map(p => {
|
||||
const tag = (!selectedSquadron && p.squadron_name)
|
||||
? `<span class="font-['skyquakesymbols'] text-[11px] mr-1.5" style="color:rgba(144,238,144,0.25)">${escapeHtml(p.squadron_name)}</span>` : '';
|
||||
return `<a href="/players/${p.uid}" class="search-hit">${tag}<span class="text-sm font-medium" style="color:#A8E6CF">${escapeHtml(p.nick)}</span></a>`;
|
||||
}).join('');
|
||||
}
|
||||
plDrop.classList.remove('hidden');
|
||||
}
|
||||
|
||||
plInput.addEventListener('input', () => {
|
||||
clearTimeout(searchTimeout);
|
||||
const val = plInput.value.trim().toLowerCase();
|
||||
if (val.length < 2 && !selectedSquadron) { plDrop.classList.add('hidden'); return; }
|
||||
if (playersState !== 'ready') {
|
||||
loadPlayerData();
|
||||
renderPlayers(val);
|
||||
return;
|
||||
}
|
||||
searchTimeout = setTimeout(() => renderPlayers(val), 100);
|
||||
});
|
||||
|
||||
plInput.addEventListener('focus', () => {
|
||||
if (playersState !== 'ready') {
|
||||
loadPlayerData();
|
||||
}
|
||||
if (selectedSquadron && plInput.value.trim() === '') {
|
||||
if (playersState === 'ready') {
|
||||
renderPlayers('');
|
||||
} else {
|
||||
renderPlayers('');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
plInput.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' && selectedSquadron && plInput.value.trim() === '') {
|
||||
e.preventDefault();
|
||||
if (selectedSquadronClanId != null) {
|
||||
window.location.href = '/squadrons/' + selectedSquadronClanId;
|
||||
} else {
|
||||
window.location.href = '/squadrons/' + encodeURIComponent(selectedSquadronShort);
|
||||
}
|
||||
}
|
||||
if (e.key === 'Escape') { plDrop.classList.add('hidden'); plInput.blur(); }
|
||||
});
|
||||
|
||||
// Close dropdowns on outside click
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!e.target.closest('#searchBar')) { sqDrop.classList.add('hidden'); plDrop.classList.add('hidden'); }
|
||||
});
|
||||
|
||||
// --- Mobile menu ---
|
||||
document.getElementById('mobileMenuBtn').addEventListener('click', () => {
|
||||
document.getElementById('mobileMenu').classList.toggle('hidden');
|
||||
});
|
||||
|
||||
// --- Random CTA ---
|
||||
const ctaPhrases = [__t('home.ctaElev8'), __t('home.ctaReign'), __t('home.ctaMeow'), __t('home.ctaPurr'), __t('home.ctaRawr')];
|
||||
function setRandomCTA() {
|
||||
const el = document.getElementById('random-squadron-cta');
|
||||
if (el) el.textContent = ctaPhrases[Math.floor(Math.random() * ctaPhrases.length)];
|
||||
}
|
||||
|
||||
// --- Subtitle cycling ---
|
||||
const subtitles = [__t('index.subtitle1'), __t('index.subtitle2'), __t('index.subtitle3'), __t('index.subtitle4')];
|
||||
let subIdx = 0;
|
||||
const cycleSubtitle = () => {
|
||||
const el = document.getElementById('cycling-subtitle');
|
||||
if (el) { el.style.opacity = '0.5'; setTimeout(() => { subIdx = (subIdx + 1) % subtitles.length; el.textContent = subtitles[subIdx]; el.style.opacity = '1'; }, 300); }
|
||||
};
|
||||
|
||||
// --- Init ---
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
setRandomCTA();
|
||||
loadSquadronData();
|
||||
// Preload player data in background so it's ready when user searches
|
||||
setTimeout(loadPlayerData, 500);
|
||||
setTimeout(() => setInterval(cycleSubtitle, 4000), 3000);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,758 @@
|
||||
<!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.squadronsTitle') %> | <%= botName %></title>
|
||||
<meta name="description" content="<%= t('leaderboard.squadronsSubtitle') %>, wins, and total score.">
|
||||
<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);
|
||||
}
|
||||
|
||||
.leaderboard-content {
|
||||
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);
|
||||
}
|
||||
|
||||
.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); }
|
||||
}
|
||||
|
||||
.leaderboard-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: rgba(30, 30, 30, 0.8);
|
||||
border-radius: 0.75rem;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
||||
border: 1px solid rgba(144, 238, 144, 0.1);
|
||||
table-layout: auto;
|
||||
min-width: 900px;
|
||||
}
|
||||
|
||||
.leaderboard-table th {
|
||||
background: linear-gradient(135deg, rgba(144, 238, 144, 0.15), rgba(144, 238, 144, 0.05));
|
||||
color: #F5F5DC;
|
||||
padding: 1.25rem;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
border-bottom: 1px solid rgba(144, 238, 144, 0.2);
|
||||
font-size: 0.95rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
text-shadow: 0 0 10px rgba(144, 238, 144, 0.3);
|
||||
}
|
||||
|
||||
.leaderboard-table th:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.leaderboard-table td {
|
||||
padding: 1rem;
|
||||
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;
|
||||
}
|
||||
|
||||
.leaderboard-table td:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.leaderboard-table tr:hover td {
|
||||
background: rgba(144, 238, 144, 0.05);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.leaderboard-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.rank-cell {
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.rank-1 { color: #ffd700; text-shadow: 0 0 10px rgba(255, 215, 0, 0.5); }
|
||||
.rank-2 { color: #c0c0c0; text-shadow: 0 0 10px rgba(192, 192, 192, 0.5); }
|
||||
.rank-3 { color: #cd7f32; text-shadow: 0 0 10px rgba(205, 127, 50, 0.5); }
|
||||
|
||||
.squadron-name {
|
||||
font-family: 'skyquakesymbols', 'Inter', sans-serif !important;
|
||||
font-weight: 600;
|
||||
color: #90EE90;
|
||||
font-size: 1.1rem;
|
||||
text-shadow: 0 0 10px rgba(144, 238, 144, 0.3);
|
||||
}
|
||||
|
||||
.stat-cell {
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.stat-points { color: #ffd700; text-shadow: 0 0 10px rgba(255, 215, 0, 0.5); }
|
||||
.stat-members { color: #a855f7; text-shadow: 0 0 10px rgba(168, 85, 247, 0.5); }
|
||||
.stat-kills { color: #ff4757; text-shadow: 0 0 10px rgba(255, 71, 87, 0.5); }
|
||||
.stat-battles { color: #a855f7; text-shadow: 0 0 10px rgba(168, 85, 247, 0.5); }
|
||||
.stat-wins { color: #10b981; text-shadow: 0 0 10px rgba(16, 185, 129, 0.5); }
|
||||
.stat-winrate { color: #f59e0b; text-shadow: 0 0 10px rgba(245, 158, 11, 0.5); }
|
||||
.stat-kdr { color: #90EE90; text-shadow: 0 0 10px rgba(144, 238, 144, 0.5); }
|
||||
|
||||
.error-state {
|
||||
text-align: center;
|
||||
padding: 4rem;
|
||||
color: #ff3838;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
background: linear-gradient(135deg, #F5F5DC 0%, #E8E8D0 100%);
|
||||
color: #1E1E1E;
|
||||
border: none;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.retry-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(245, 245, 220, 0.4);
|
||||
}
|
||||
|
||||
.responsive-table {
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.leaderboard-controls {
|
||||
background: rgba(30, 30, 30, 0.6);
|
||||
border-radius: 1rem;
|
||||
padding: 1.5rem;
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(144, 238, 144, 0.1);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.controls-row {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
align-items: end;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.filter-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.filter-group label {
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.filter-input {
|
||||
background: rgba(30, 30, 30, 0.9);
|
||||
border: 2px solid rgba(144, 238, 144, 0.2);
|
||||
border-radius: 0.75rem;
|
||||
padding: 0.75rem 1rem;
|
||||
color: #ffffff;
|
||||
font-size: 1rem;
|
||||
transition: all 0.3s ease;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.filter-input:focus {
|
||||
outline: none;
|
||||
border-color: #90EE90;
|
||||
box-shadow: 0 0 20px rgba(144, 238, 144, 0.4);
|
||||
}
|
||||
|
||||
.clear-filters-btn {
|
||||
background: linear-gradient(135deg, rgba(239, 68, 68, 0.2), rgba(220, 38, 38, 0.2));
|
||||
border: 2px solid rgba(239, 68, 68, 0.3);
|
||||
color: #ef4444;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 0.75rem;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.clear-filters-btn:hover {
|
||||
background: linear-gradient(135deg, rgba(239, 68, 68, 0.3), rgba(220, 38, 38, 0.3));
|
||||
border-color: #ef4444;
|
||||
}
|
||||
|
||||
.results-info {
|
||||
color: #90EE90;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sortable {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.sortable:hover {
|
||||
background: linear-gradient(135deg, rgba(144, 238, 144, 0.3), rgba(144, 238, 144, 0.15));
|
||||
}
|
||||
|
||||
.sort-icon {
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.6;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.sortable.sorted-asc .sort-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.sortable.sorted-asc .sort-icon::before {
|
||||
content: "\f0de";
|
||||
}
|
||||
|
||||
.sortable.sorted-desc .sort-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.sortable.sorted-desc .sort-icon::before {
|
||||
content: "\f0dd";
|
||||
}
|
||||
|
||||
.pagination-controls {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-top: 2rem;
|
||||
padding: 1rem;
|
||||
background: rgba(30, 30, 30, 0.6);
|
||||
border-radius: 1rem;
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(144, 238, 144, 0.1);
|
||||
}
|
||||
|
||||
.pagination-btn {
|
||||
background: rgba(30, 30, 30, 0.8);
|
||||
border: 1px solid rgba(144, 238, 144, 0.2);
|
||||
color: #ffffff;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 0.5rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.pagination-btn:hover:not(:disabled) {
|
||||
background: rgba(144, 238, 144, 0.1);
|
||||
border-color: #90EE90;
|
||||
color: #90EE90;
|
||||
}
|
||||
|
||||
.pagination-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.pagination-info {
|
||||
color: #90EE90;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 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.squadronsTitle') %></h1>
|
||||
<p class="leaderboard-subtitle"><%= t('leaderboard.squadronsSubtitle') %></p>
|
||||
<div class="disclaimer">
|
||||
<i class="fas fa-info-circle" style="margin-right: 0.5rem;"></i>
|
||||
<%= t('common.recordingSince') %>
|
||||
</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 active">
|
||||
<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">
|
||||
<i class="fas fa-balance-scale"></i> <%= t('common.comparison') %>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="leaderboard-controls">
|
||||
<div class="controls-row">
|
||||
<div class="filter-group">
|
||||
<label for="squadronSearch"><%= t('leaderboard.searchSquadron') %></label>
|
||||
<input type="text" id="squadronSearch" class="filter-input" placeholder="<%= t('leaderboard.searchBySquadronName') %>">
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label for="minPlayers"><%= t('leaderboard.minPlayers') %></label>
|
||||
<input type="number" id="minPlayers" class="filter-input" placeholder="<%= t('leaderboard.minPlayersPlaceholder') %>" min="0" value="1" style="min-width: 150px;">
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<button class="clear-filters-btn" onclick="clearFilters()">
|
||||
<i class="fas fa-times"></i> <%= t('leaderboard.resetFilters') %>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="results-info" id="resultsInfo">
|
||||
<span id="filteredCount">0</span> <%= t('leaderboard.squadronsShown') %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="leaderboard-content">
|
||||
<div class="loading-state" id="loadingState">
|
||||
<div class="loading-spinner"></div>
|
||||
<p><%= t('leaderboard.loadingSquadronLeaderboard') %></p>
|
||||
</div>
|
||||
|
||||
<div class="error-state" id="errorState" style="display: none;">
|
||||
<i class="fas fa-exclamation-triangle error-icon"></i>
|
||||
<h3><%= t('leaderboard.failedToLoadLeaderboard') %></h3>
|
||||
<p id="errorMessage"><%= t('leaderboard.unableToFetch') %></p>
|
||||
<button class="retry-btn" onclick="loadLeaderboard()">
|
||||
<i class="fas fa-redo"></i> <%= t('common.retry') %>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="responsive-table" id="leaderboardTable" style="display: none;">
|
||||
<table class="leaderboard-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><%= t('common.rank') %></th>
|
||||
<th class="sortable" onclick="sortTable('points')" data-sort="points">
|
||||
<%= t('common.points') %> <i class="fas fa-sort sort-icon"></i>
|
||||
</th>
|
||||
<th class="sortable" onclick="sortTable('squadron_name')" data-sort="squadron_name">
|
||||
<%= t('common.squadron') %> <i class="fas fa-sort sort-icon"></i>
|
||||
</th>
|
||||
<th class="sortable" onclick="sortTable('player_count')" data-sort="player_count">
|
||||
<%= t('common.members') %> <i class="fas fa-sort sort-icon"></i>
|
||||
</th>
|
||||
<th class="sortable" onclick="sortTable('total_kills')" data-sort="total_kills">
|
||||
<%= t('common.totalKills') %> <i class="fas fa-sort sort-icon"></i>
|
||||
</th>
|
||||
<th class="sortable" onclick="sortTable('total_battles')" data-sort="total_battles">
|
||||
<%= t('common.battles') %> <i class="fas fa-sort sort-icon"></i>
|
||||
</th>
|
||||
<th class="sortable" onclick="sortTable('wins')" data-sort="wins">
|
||||
Wins <i class="fas fa-sort sort-icon"></i>
|
||||
</th>
|
||||
<th class="sortable" onclick="sortTable('win_rate')" data-sort="win_rate">
|
||||
Win Rate <i class="fas fa-sort sort-icon"></i>
|
||||
</th>
|
||||
<th class="sortable" onclick="sortTable('kdr')" data-sort="kdr">
|
||||
KDR <i class="fas fa-sort sort-icon"></i>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="leaderboardBody">
|
||||
</tbody>
|
||||
</table>
|
||||
</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/header-search.js?v=2"></script>
|
||||
<script src="/js/seasons-filter.js"></script>
|
||||
<script>
|
||||
let allSquadrons = [];
|
||||
let filteredSquadrons = [];
|
||||
let currentSort = { field: 'points', direction: 'desc' };
|
||||
|
||||
async function loadLeaderboard() {
|
||||
const loadingState = document.getElementById('loadingState');
|
||||
const errorState = document.getElementById('errorState');
|
||||
const leaderboardTable = document.getElementById('leaderboardTable');
|
||||
|
||||
loadingState.style.display = 'block';
|
||||
errorState.style.display = 'none';
|
||||
leaderboardTable.style.display = 'none';
|
||||
|
||||
try {
|
||||
await window.ensureAPIClient();
|
||||
|
||||
const leaderboardData = await window.apiClient.getSquadronLeaderboard();
|
||||
|
||||
if (!leaderboardData || !leaderboardData.squadrons || leaderboardData.squadrons.length === 0) {
|
||||
showError(__t('leaderboard.noSquadronsInLeaderboard'));
|
||||
return;
|
||||
}
|
||||
|
||||
allSquadrons = leaderboardData.squadrons;
|
||||
applyFiltersAndDisplay();
|
||||
|
||||
loadingState.style.display = 'none';
|
||||
leaderboardTable.style.display = 'block';
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading squadron leaderboard:', error);
|
||||
showError(error.message || __t('leaderboard.failedToLoadLeaderboard'));
|
||||
}
|
||||
}
|
||||
|
||||
function applyFiltersAndDisplay() {
|
||||
const searchTerm = document.getElementById('squadronSearch').value.toLowerCase().trim();
|
||||
const minPlayers = parseInt(document.getElementById('minPlayers').value) || 0;
|
||||
|
||||
filteredSquadrons = allSquadrons.filter(squadron => {
|
||||
if (searchTerm) {
|
||||
const names = [squadron.long_name, squadron.tag_name, squadron.short_name, squadron.squadron_name]
|
||||
.filter(Boolean)
|
||||
.map(n => n.toLowerCase());
|
||||
if (!names.some(n => n.includes(searchTerm))) return false;
|
||||
}
|
||||
if (squadron.player_count < minPlayers) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
sortSquadrons();
|
||||
displaySquadrons();
|
||||
}
|
||||
|
||||
function sortSquadrons() {
|
||||
filteredSquadrons.sort((a, b) => {
|
||||
let valueA, valueB;
|
||||
|
||||
if (currentSort.field === 'points') {
|
||||
valueA = (a.points && a.points.total_points) || 0;
|
||||
valueB = (b.points && b.points.total_points) || 0;
|
||||
} else if (currentSort.field === 'squadron_name') {
|
||||
const nameA = a.long_name || a.tag_name || a.squadron_name || '';
|
||||
const nameB = b.long_name || b.tag_name || b.squadron_name || '';
|
||||
valueA = nameA.toLowerCase();
|
||||
valueB = nameB.toLowerCase();
|
||||
} else {
|
||||
valueA = parseFloat(a[currentSort.field]) || 0;
|
||||
valueB = parseFloat(b[currentSort.field]) || 0;
|
||||
}
|
||||
|
||||
if (currentSort.direction === 'asc') {
|
||||
return valueA > valueB ? 1 : valueA < valueB ? -1 : 0;
|
||||
} else {
|
||||
return valueA < valueB ? 1 : valueA > valueB ? -1 : 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function displaySquadrons() {
|
||||
const tbody = document.getElementById('leaderboardBody');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
filteredSquadrons.forEach((squadron, index) => {
|
||||
const row = document.createElement('tr');
|
||||
row.className = 'row-link';
|
||||
const squadronUrlPath = squadron.clan_id != null
|
||||
? String(squadron.clan_id)
|
||||
: encodeURIComponent(squadron.long_name || squadron.tag_name || squadron.squadron_name);
|
||||
|
||||
const rank = index + 1;
|
||||
const rankClass = rank <= 3 ? `rank-${rank}` : '';
|
||||
|
||||
const squadronDisplayName = squadron.tag_name || squadron.squadron_name;
|
||||
|
||||
const points = (squadron.points && squadron.points.total_points) || 0;
|
||||
const pointsDisplay = squadron.points && squadron.points.has_points_data
|
||||
? formatNumber(points)
|
||||
: '<span style="opacity: 0.5;">N/A</span>';
|
||||
|
||||
row.innerHTML = `
|
||||
<td class="rank-cell ${rankClass}">
|
||||
<a href="/squadrons/${squadronUrlPath}" class="row-link-overlay" aria-label="View ${escapeHtml(squadronDisplayName)}"></a>
|
||||
${rank <= 3 ? getRankIcon(rank) : '#' + rank}
|
||||
</td>
|
||||
<td class="stat-cell stat-points">${pointsDisplay}</td>
|
||||
<td class="squadron-name" style="font-family: 'skyquakesymbols', 'Inter', sans-serif !important;">${escapeHtml(squadronDisplayName)}</td>
|
||||
<td class="stat-cell stat-members">${squadron.player_count}</td>
|
||||
<td class="stat-cell stat-kills">${formatNumber(squadron.total_kills)}</td>
|
||||
<td class="stat-cell stat-battles">${formatNumber(squadron.total_battles)}</td>
|
||||
<td class="stat-cell stat-wins">${formatNumber(squadron.wins)}</td>
|
||||
<td class="stat-cell stat-winrate">${squadron.win_rate.toFixed(1)}%</td>
|
||||
<td class="stat-cell stat-kdr">${squadron.kdr.toFixed(2)}</td>
|
||||
`;
|
||||
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
|
||||
document.getElementById('filteredCount').textContent = filteredSquadrons.length;
|
||||
}
|
||||
|
||||
function sortTable(field) {
|
||||
if (currentSort.field === field) {
|
||||
currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
currentSort.field = field;
|
||||
currentSort.direction = (field === 'squadron_name') ? 'asc' : 'desc';
|
||||
}
|
||||
|
||||
document.querySelectorAll('.sortable').forEach(header => {
|
||||
header.classList.remove('sorted-asc', 'sorted-desc');
|
||||
});
|
||||
|
||||
const currentHeader = document.querySelector(`[data-sort="${field}"]`);
|
||||
if (currentHeader) {
|
||||
currentHeader.classList.add(currentSort.direction === 'asc' ? 'sorted-asc' : 'sorted-desc');
|
||||
}
|
||||
|
||||
applyFiltersAndDisplay();
|
||||
}
|
||||
|
||||
function clearFilters() {
|
||||
document.getElementById('squadronSearch').value = '';
|
||||
document.getElementById('minPlayers').value = '1';
|
||||
applyFiltersAndDisplay();
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
const loadingState = document.getElementById('loadingState');
|
||||
const errorState = document.getElementById('errorState');
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
|
||||
loadingState.style.display = 'none';
|
||||
errorState.style.display = 'block';
|
||||
errorMessage.textContent = message;
|
||||
}
|
||||
|
||||
function getRankIcon(rank) {
|
||||
switch(rank) {
|
||||
case 1: return '<i class="fas fa-crown" style="margin-right: 0.5rem; color: #ffd700;"></i>';
|
||||
case 2: return '<i class="fas fa-medal" style="margin-right: 0.5rem; color: #c0c0c0;"></i>';
|
||||
case 3: return '<i class="fas fa-trophy" style="margin-right: 0.5rem; color: #cd7f32;"></i>';
|
||||
default: return '';
|
||||
}
|
||||
}
|
||||
|
||||
function formatNumber(num) {
|
||||
if (num >= 1000000) {
|
||||
return (num / 1000000).toFixed(1) + 'M';
|
||||
} else if (num >= 1000) {
|
||||
return (num / 1000).toFixed(1) + 'K';
|
||||
}
|
||||
return num.toString();
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
let searchTimeout;
|
||||
document.getElementById('squadronSearch').addEventListener('input', function() {
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(() => {
|
||||
applyFiltersAndDisplay();
|
||||
}, 300);
|
||||
});
|
||||
|
||||
document.getElementById('minPlayers').addEventListener('input', function() {
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(() => {
|
||||
applyFiltersAndDisplay();
|
||||
}, 300);
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
setTimeout(loadLeaderboard, 100);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,909 @@
|
||||
<!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.statsTitle') %> | <%= botName %></title>
|
||||
<meta name="description" content="<%= t('leaderboard.statsSubtitle') %>">
|
||||
<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);
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.stats-card {
|
||||
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);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stats-card-title {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
color: #90EE90;
|
||||
margin-bottom: 1rem;
|
||||
text-shadow: 0 0 10px rgba(144, 238, 144, 0.3);
|
||||
}
|
||||
|
||||
.stats-card-value {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
margin-bottom: 0.5rem;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.stats-card-label {
|
||||
font-size: 0.9rem;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.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); }
|
||||
}
|
||||
|
||||
.vehicles-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);
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
.vehicles-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.vehicle-item {
|
||||
display: block;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
background: rgba(30, 30, 30, 0.8);
|
||||
border-radius: 0.75rem;
|
||||
padding: 1.5rem;
|
||||
border: 1px solid rgba(144, 238, 144, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.vehicle-item:hover {
|
||||
border-color: rgba(144, 238, 144, 0.3);
|
||||
background: rgba(30, 30, 30, 0.95);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.vehicle-rank {
|
||||
font-size: 0.9rem;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.vehicle-name {
|
||||
font-family: 'skyquakesymbols', 'Inter', sans-serif !important;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: #90EE90;
|
||||
margin-bottom: 0.5rem;
|
||||
text-shadow: 0 0 10px rgba(144, 238, 144, 0.3);
|
||||
}
|
||||
|
||||
.vehicle-usage {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.vehicle-label {
|
||||
font-size: 0.8rem;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.error-state {
|
||||
text-align: center;
|
||||
padding: 4rem;
|
||||
color: #ff3838;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
background: linear-gradient(135deg, #F5F5DC 0%, #E8E8D0 100%);
|
||||
color: #1E1E1E;
|
||||
border: none;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.retry-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(245, 245, 220, 0.4);
|
||||
}
|
||||
|
||||
.last-updated {
|
||||
text-align: center;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 0.9rem;
|
||||
margin-top: 2rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.vehicle-kills-leaderboard {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.leaderboard-table-container {
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.vehicle-kills-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: rgba(30, 30, 30, 0.8);
|
||||
border-radius: 0.75rem;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
||||
border: 1px solid rgba(144, 238, 144, 0.1);
|
||||
table-layout: auto;
|
||||
min-width: 800px;
|
||||
}
|
||||
|
||||
.vehicle-kills-table th {
|
||||
background: linear-gradient(135deg, rgba(144, 238, 144, 0.15), rgba(144, 238, 144, 0.05));
|
||||
color: #F5F5DC;
|
||||
padding: 1.25rem;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
border-bottom: 1px solid rgba(144, 238, 144, 0.2);
|
||||
font-size: 0.95rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
text-shadow: 0 0 10px rgba(144, 238, 144, 0.3);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.vehicle-kills-table th:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.vehicle-kills-table th.sortable {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.vehicle-kills-table th.sortable:hover {
|
||||
background: linear-gradient(135deg, rgba(144, 238, 144, 0.25), rgba(144, 238, 144, 0.1));
|
||||
}
|
||||
|
||||
.vehicle-kills-table .sort-icon {
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.6;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.vehicle-kills-table th.sortable.sorted-asc .sort-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.vehicle-kills-table th.sortable.sorted-asc .sort-icon::before {
|
||||
content: "\f0de"; /* fa-sort-up */
|
||||
}
|
||||
|
||||
.vehicle-kills-table th.sortable.sorted-desc .sort-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.vehicle-kills-table th.sortable.sorted-desc .sort-icon::before {
|
||||
content: "\f0dd"; /* fa-sort-down */
|
||||
}
|
||||
|
||||
.vehicle-kills-table td {
|
||||
padding: 1rem;
|
||||
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;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.vehicle-kills-table td:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.vehicle-kills-table tr:hover td {
|
||||
background: rgba(144, 238, 144, 0.05);
|
||||
}
|
||||
|
||||
.vehicle-kills-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.vehicle-kills-table th:nth-child(1),
|
||||
.vehicle-kills-table td:nth-child(1) {
|
||||
width: 80px;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.vehicle-kills-table th:nth-child(2),
|
||||
.vehicle-kills-table td:nth-child(2) {
|
||||
width: 25%;
|
||||
min-width: 180px;
|
||||
text-align: left !important;
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
.vehicle-kills-table th:nth-child(3),
|
||||
.vehicle-kills-table td:nth-child(3),
|
||||
.vehicle-kills-table th:nth-child(4),
|
||||
.vehicle-kills-table td:nth-child(4),
|
||||
.vehicle-kills-table th:nth-child(5),
|
||||
.vehicle-kills-table td:nth-child(5),
|
||||
.vehicle-kills-table th:nth-child(6),
|
||||
.vehicle-kills-table td:nth-child(6),
|
||||
.vehicle-kills-table th:nth-child(7),
|
||||
.vehicle-kills-table td:nth-child(7),
|
||||
.vehicle-kills-table th:nth-child(8),
|
||||
.vehicle-kills-table td:nth-child(8) {
|
||||
width: 110px;
|
||||
min-width: 110px;
|
||||
}
|
||||
|
||||
.vehicle-kills-rank-cell {
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.vehicle-kills-rank-1 { color: #ffd700; text-shadow: 0 0 10px rgba(255, 215, 0, 0.5); }
|
||||
.vehicle-kills-rank-2 { color: #c0c0c0; text-shadow: 0 0 10px rgba(192, 192, 192, 0.5); }
|
||||
.vehicle-kills-rank-3 { color: #cd7f32; text-shadow: 0 0 10px rgba(205, 127, 50, 0.5); }
|
||||
|
||||
.vehicle-kills-name-cell {
|
||||
font-family: 'skyquakesymbols', 'Inter', sans-serif !important;
|
||||
font-weight: 600;
|
||||
color: #90EE90;
|
||||
font-size: 1.1rem;
|
||||
text-shadow: 0 0 10px rgba(144, 238, 144, 0.3);
|
||||
}
|
||||
|
||||
.vehicle-kills-stat-cell {
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
color: #90EE90;
|
||||
text-shadow: 0 0 10px rgba(144, 238, 144, 0.5);
|
||||
}
|
||||
|
||||
/* 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.statsTitle') %></h1>
|
||||
<p class="leaderboard-subtitle"><%= t('leaderboard.statsSubtitle') %></p>
|
||||
<div class="disclaimer">
|
||||
<i class="fas fa-info-circle" style="margin-right: 0.5rem;"></i>
|
||||
<%= t('common.recordingSince') %>
|
||||
</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 active">
|
||||
<i class="fas fa-chart-bar"></i> <%= t('common.statistics') %>
|
||||
</a>
|
||||
<a href="/leaderboard/comparison" class="leaderboard-tab">
|
||||
<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.loadingGlobalStats') %></p>
|
||||
</div>
|
||||
|
||||
<div class="error-state" id="errorState" style="display: none;">
|
||||
<i class="fas fa-exclamation-triangle error-icon"></i>
|
||||
<h3><%= t('leaderboard.failedToLoadStats') %></h3>
|
||||
<p id="errorMessage"><%= t('leaderboard.unableToFetchStats') %></p>
|
||||
<button class="retry-btn" onclick="loadStatistics()">
|
||||
<i class="fas fa-redo"></i> <%= t('common.retry') %>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="statisticsContent" style="display: none;">
|
||||
<div class="stats-grid" id="statsGrid">
|
||||
</div>
|
||||
|
||||
<div class="vehicles-section">
|
||||
<h2 class="section-title"><%= t('leaderboard.mostPopularVehicles') %></h2>
|
||||
<div class="vehicles-grid" id="vehiclesGrid">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="vehicles-section" style="margin-top: 2rem;">
|
||||
<h2 class="section-title"><%= t('leaderboard.vehicleKillsLeaderboardTitle') %></h2>
|
||||
<div class="vehicle-kills-leaderboard">
|
||||
<div class="leaderboard-table-container">
|
||||
<table class="vehicle-kills-table" id="vehicleKillsTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<%= t('common.rank') %>
|
||||
</th>
|
||||
<th>
|
||||
<%= t('common.vehicle') %>
|
||||
</th>
|
||||
<th class="sortable" onclick="sortVehicleKills('total_battles')" data-sort="total_battles">
|
||||
<%= t('common.totalBattles') %> <i class="fas fa-sort sort-icon"></i>
|
||||
</th>
|
||||
<th class="sortable" onclick="sortVehicleKills('avg_win_rate')" data-sort="avg_win_rate">
|
||||
<%= t('leaderboard.avgWinRate') %> <i class="fas fa-sort sort-icon"></i>
|
||||
</th>
|
||||
<th class="sortable" onclick="sortVehicleKills('ground_kills')" data-sort="ground_kills">
|
||||
<%= t('common.groundKills') %> <i class="fas fa-sort sort-icon"></i>
|
||||
</th>
|
||||
<th class="sortable" onclick="sortVehicleKills('air_kills')" data-sort="air_kills">
|
||||
Air Kills <i class="fas fa-sort sort-icon"></i>
|
||||
</th>
|
||||
<th class="sortable" onclick="sortVehicleKills('players')" data-sort="players">
|
||||
Players <i class="fas fa-sort sort-icon"></i>
|
||||
</th>
|
||||
<th class="sortable" onclick="sortVehicleKills('avg_kills')" data-sort="avg_kills">
|
||||
Average Kills/Player <i class="fas fa-sort sort-icon"></i>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="vehicleKillsBody">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="loading-placeholder" id="vehicleKillsLoading" style="text-align: center; padding: 2rem; color: #90EE90;">
|
||||
<div class="loading-spinner"></div>
|
||||
<p><%= t('leaderboard.loadingVehicleKills') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="last-updated" id="lastUpdated"></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>
|
||||
let statisticsData = null;
|
||||
|
||||
async function loadStatistics() {
|
||||
const loadingState = document.getElementById('loadingState');
|
||||
const errorState = document.getElementById('errorState');
|
||||
const statisticsContent = document.getElementById('statisticsContent');
|
||||
|
||||
loadingState.style.display = 'block';
|
||||
errorState.style.display = 'none';
|
||||
statisticsContent.style.display = 'none';
|
||||
|
||||
try {
|
||||
if (!window.apiClient || typeof window.apiClient.getLeaderboardStats !== 'function') {
|
||||
throw new Error(__t('leaderboard.apiNotLoaded'));
|
||||
}
|
||||
|
||||
await window.ensureAPIClient();
|
||||
|
||||
statisticsData = await window.apiClient.getLeaderboardStats();
|
||||
displayStatistics();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading statistics:', error);
|
||||
showError(error.message || __t('leaderboard.failedToLoadStats'));
|
||||
}
|
||||
}
|
||||
|
||||
function displayStatistics() {
|
||||
const loadingState = document.getElementById('loadingState');
|
||||
const statisticsContent = document.getElementById('statisticsContent');
|
||||
|
||||
if (!statisticsData) {
|
||||
showError(__t('leaderboard.noStatsData'));
|
||||
return;
|
||||
}
|
||||
|
||||
loadingState.style.display = 'none';
|
||||
statisticsContent.style.display = 'block';
|
||||
|
||||
displayGlobalStats();
|
||||
|
||||
displayTopVehicles();
|
||||
|
||||
loadVehicleKillsLeaderboard();
|
||||
|
||||
displayLastUpdated();
|
||||
}
|
||||
|
||||
function displayGlobalStats() {
|
||||
const statsGrid = document.getElementById('statsGrid');
|
||||
|
||||
const stats = [
|
||||
{
|
||||
title: __t('leaderboard.totalPlayersCard'),
|
||||
value: formatLargeNumber(statisticsData.total_players || 0),
|
||||
label: __t('leaderboard.activePlayers'),
|
||||
icon: 'fas fa-users'
|
||||
},
|
||||
{
|
||||
title: __t('leaderboard.vehiclesUsed'),
|
||||
value: formatLargeNumber(statisticsData.total_vehicles_used || 0),
|
||||
label: __t('leaderboard.differentVehicles'),
|
||||
icon: 'fas fa-tank'
|
||||
},
|
||||
{
|
||||
title: __t('common.totalBattles'),
|
||||
value: formatLargeNumber(Math.floor((statisticsData.total_battles || 0) / 16)),
|
||||
label: __t('leaderboard.squadronBattlesLabel'),
|
||||
icon: 'fas fa-crosshairs'
|
||||
}
|
||||
];
|
||||
|
||||
statsGrid.innerHTML = '';
|
||||
|
||||
stats.forEach(stat => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'stats-card';
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="stats-card-title">
|
||||
<i class="${stat.icon}" style="margin-right: 0.5rem;"></i>
|
||||
${stat.title}
|
||||
</div>
|
||||
<div class="stats-card-value">${stat.value}</div>
|
||||
<div class="stats-card-label">${stat.label}</div>
|
||||
`;
|
||||
|
||||
statsGrid.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
function displayTopVehicles() {
|
||||
const vehiclesGrid = document.getElementById('vehiclesGrid');
|
||||
|
||||
if (!statisticsData.top_vehicles || statisticsData.top_vehicles.length === 0) {
|
||||
vehiclesGrid.innerHTML = '<p style="text-align: center; color: rgba(255, 255, 255, 0.6);">' + __t('leaderboard.noVehicleData') + '</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
vehiclesGrid.innerHTML = '';
|
||||
|
||||
console.log(`API returned ${statisticsData.top_vehicles.length} vehicles, requesting 12`);
|
||||
|
||||
const topVehicles = statisticsData.top_vehicles.slice(0, 12);
|
||||
|
||||
topVehicles.forEach((vehicle, index) => {
|
||||
const item = document.createElement('a');
|
||||
item.className = 'vehicle-item';
|
||||
item.href = `/leaderboard/vehicles?vehicle=${encodeURIComponent(vehicle.vehicle)}`;
|
||||
|
||||
item.innerHTML = `
|
||||
<div class="vehicle-rank">#${index + 1} ${__t('leaderboard.mostPopular')}</div>
|
||||
<div class="vehicle-name" data-vehicle-internal="${escapeHtml(vehicle.vehicle_internal || '')}">${escapeHtml(vehicle.vehicle)}</div>
|
||||
<div class="vehicle-usage">${formatLargeNumber(vehicle.usage_count)}</div>
|
||||
<div class="vehicle-label">${__t('leaderboard.timesUsed')}</div>
|
||||
`;
|
||||
|
||||
vehiclesGrid.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
function displayLastUpdated() {
|
||||
const lastUpdated = document.getElementById('lastUpdated');
|
||||
|
||||
if (statisticsData.last_updated) {
|
||||
const date = new Date(statisticsData.last_updated);
|
||||
const formattedDate = date.toLocaleString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
timeZoneName: 'short'
|
||||
});
|
||||
|
||||
lastUpdated.innerHTML = `
|
||||
<i class="fas fa-clock" style="margin-right: 0.5rem;"></i>
|
||||
${__t('leaderboard.lastUpdated')}: ${formattedDate}
|
||||
`;
|
||||
} else {
|
||||
lastUpdated.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
const loadingState = document.getElementById('loadingState');
|
||||
const errorState = document.getElementById('errorState');
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
|
||||
loadingState.style.display = 'none';
|
||||
errorState.style.display = 'block';
|
||||
errorMessage.textContent = message;
|
||||
}
|
||||
|
||||
function formatLargeNumber(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;
|
||||
}
|
||||
|
||||
let vehicleKillsData = [];
|
||||
let vehicleKillsSort = { field: 'total_battles', direction: 'desc' };
|
||||
|
||||
async function loadVehicleKillsLeaderboard() {
|
||||
const loadingEl = document.getElementById('vehicleKillsLoading');
|
||||
const tableEl = document.getElementById('vehicleKillsTable');
|
||||
|
||||
try {
|
||||
loadingEl.style.display = 'block';
|
||||
tableEl.style.display = 'none';
|
||||
|
||||
const vehicleData = await window.apiClient.getVehicleLeaderboard(null);
|
||||
|
||||
if (!vehicleData || !vehicleData.vehicles) {
|
||||
throw new Error(__t('leaderboard.noVehicleData'));
|
||||
}
|
||||
|
||||
const vehicleAggregates = {};
|
||||
|
||||
vehicleData.vehicles.forEach(entry => {
|
||||
const vehicleName = entry.vehicle;
|
||||
|
||||
if (!vehicleAggregates[vehicleName]) {
|
||||
vehicleAggregates[vehicleName] = {
|
||||
vehicle: vehicleName,
|
||||
total_battles: 0,
|
||||
total_wins: 0,
|
||||
ground_kills: 0,
|
||||
air_kills: 0,
|
||||
players: 0,
|
||||
total_kills: 0
|
||||
};
|
||||
}
|
||||
|
||||
vehicleAggregates[vehicleName].total_battles += entry.battles || 0;
|
||||
vehicleAggregates[vehicleName].total_wins += entry.wins || 0;
|
||||
vehicleAggregates[vehicleName].ground_kills += entry.ground_kills || 0;
|
||||
vehicleAggregates[vehicleName].air_kills += entry.air_kills || 0;
|
||||
vehicleAggregates[vehicleName].total_kills += entry.total_kills || 0;
|
||||
vehicleAggregates[vehicleName].players += 1;
|
||||
});
|
||||
|
||||
vehicleKillsData = Object.values(vehicleAggregates).map(vehicle => ({
|
||||
...vehicle,
|
||||
avg_win_rate: vehicle.total_battles > 0 ? ((vehicle.total_wins / vehicle.total_battles) * 100) : 0,
|
||||
avg_kills: vehicle.players > 0 ? (vehicle.total_kills / vehicle.players) : 0
|
||||
}));
|
||||
|
||||
sortVehicleKillsData();
|
||||
displayVehicleKillsLeaderboard();
|
||||
|
||||
loadingEl.style.display = 'none';
|
||||
tableEl.style.display = 'table';
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading vehicle kills leaderboard:', error);
|
||||
loadingEl.innerHTML = `
|
||||
<p style="color: #ff3838;">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
${__t('leaderboard.failedToLoadVehicleKills')}
|
||||
</p>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function sortVehicleKillsData() {
|
||||
const field = vehicleKillsSort.field;
|
||||
const direction = vehicleKillsSort.direction;
|
||||
|
||||
vehicleKillsData.sort((a, b) => {
|
||||
let aVal = a[field];
|
||||
let bVal = b[field];
|
||||
|
||||
if (field === 'vehicle') {
|
||||
aVal = (aVal || '').toLowerCase();
|
||||
bVal = (bVal || '').toLowerCase();
|
||||
return direction === 'asc' ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal);
|
||||
}
|
||||
|
||||
aVal = Number(aVal) || 0;
|
||||
bVal = Number(bVal) || 0;
|
||||
|
||||
return direction === 'asc' ? aVal - bVal : bVal - aVal;
|
||||
});
|
||||
}
|
||||
|
||||
function displayVehicleKillsLeaderboard() {
|
||||
const tbody = document.getElementById('vehicleKillsBody');
|
||||
const fragment = document.createDocumentFragment();
|
||||
|
||||
// 20
|
||||
const topVehicles = vehicleKillsData.slice(0, 20);
|
||||
|
||||
topVehicles.forEach((vehicle, index) => {
|
||||
const row = document.createElement('tr');
|
||||
const rank = index + 1;
|
||||
const rankClass = rank <= 3 ? `vehicle-kills-rank-${rank}` : '';
|
||||
|
||||
row.innerHTML = `
|
||||
<td class="vehicle-kills-rank-cell ${rankClass}">
|
||||
${rank <= 3 ? getRankIcon(rank) : `#${rank}`}
|
||||
</td>
|
||||
<td class="vehicle-kills-name-cell" data-vehicle-internal="${escapeHtml(vehicle.vehicle_internal || '')}">${escapeHtml(vehicle.vehicle)}</td>
|
||||
<td class="vehicle-kills-stat-cell">${formatLargeNumber(vehicle.total_battles)}</td>
|
||||
<td class="vehicle-kills-stat-cell">${vehicle.avg_win_rate.toFixed(1)}%</td>
|
||||
<td class="vehicle-kills-stat-cell">${formatLargeNumber(vehicle.ground_kills)}</td>
|
||||
<td class="vehicle-kills-stat-cell">${formatLargeNumber(vehicle.air_kills)}</td>
|
||||
<td class="vehicle-kills-stat-cell">${formatLargeNumber(vehicle.players)}</td>
|
||||
<td class="vehicle-kills-stat-cell">${vehicle.avg_kills.toFixed(1)}</td>
|
||||
`;
|
||||
|
||||
fragment.appendChild(row);
|
||||
});
|
||||
|
||||
tbody.innerHTML = '';
|
||||
tbody.appendChild(fragment);
|
||||
}
|
||||
|
||||
|
||||
function sortVehicleKills(field) {
|
||||
if (vehicleKillsSort.field === field) {
|
||||
vehicleKillsSort.direction = vehicleKillsSort.direction === 'desc' ? 'asc' : 'desc';
|
||||
} else {
|
||||
vehicleKillsSort.field = field;
|
||||
vehicleKillsSort.direction = 'desc';
|
||||
}
|
||||
|
||||
document.querySelectorAll('.vehicle-kills-table th').forEach(th => {
|
||||
th.classList.remove('sorted-asc', 'sorted-desc');
|
||||
});
|
||||
|
||||
const currentHeader = document.querySelector(`.vehicle-kills-table th[data-sort="${field}"]`);
|
||||
if (currentHeader) {
|
||||
currentHeader.classList.add(`sorted-${vehicleKillsSort.direction}`);
|
||||
}
|
||||
|
||||
sortVehicleKillsData();
|
||||
displayVehicleKillsLeaderboard();
|
||||
}
|
||||
|
||||
function getRankIcon(rank) {
|
||||
switch(rank) {
|
||||
case 1: return '<i class="fas fa-crown" style="margin-right: 0.5rem; color: #ffd700;"></i>';
|
||||
case 2: return '<i class="fas fa-medal" style="margin-right: 0.5rem; color: #c0c0c0;"></i>';
|
||||
case 3: return '<i class="fas fa-trophy" style="margin-right: 0.5rem; color: #cd7f32;"></i>';
|
||||
default: return '';
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
setTimeout(loadStatistics, 100);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,47 @@
|
||||
<footer class="py-6 border-t border-[rgba(144,238,144,0.08)]">
|
||||
<div class="max-w-[1400px] mx-auto px-6 lg:px-8">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-4 gap-4 mb-4">
|
||||
<!-- Brand -->
|
||||
<div class="lg:col-span-1">
|
||||
<div class="flex items-center space-x-2.5">
|
||||
<img src="/images/toothless_server.gif" alt="<%= botName %>" class="h-6 w-auto" width="24" height="24">
|
||||
<span class="text-sm font-semibold text-accent"><%= botName %></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Product Links - Two Columns -->
|
||||
<div>
|
||||
<h4 class="font-semibold mb-1.5 text-accent text-xs uppercase tracking-wide opacity-70"><%= t('footer.services') %></h4>
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-1">
|
||||
<a href="/games" class="text-muted hover:text-accent transition-colors text-[11px]"><%= t('footer.matchFeed') %></a>
|
||||
<a href="/leaderboard/players" class="text-muted hover:text-accent transition-colors text-[11px]"><%= t('nav.leaderboards') %></a>
|
||||
<a href="/leaderboard/vehicles" class="text-muted hover:text-accent transition-colors text-[11px]"><%= t('footer.vehicleStats') %></a>
|
||||
<a href="/analytics" class="text-muted hover:text-accent transition-colors text-[11px]"><%= t('footer.analytics') %></a>
|
||||
<a href="/squadrons" class="text-muted hover:text-accent transition-colors text-[11px]"><%= t('footer.squadronHub') %></a>
|
||||
<a href="/leaderboard/comparison" class="text-muted hover:text-accent transition-colors text-[11px]"><%= t('footer.comparison') %></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 class="font-semibold mb-1.5 text-accent text-xs uppercase tracking-wide opacity-70"><%= t('footer.resources') %></h4>
|
||||
<ul class="space-y-1">
|
||||
<li><a href="/docs" class="text-muted hover:text-accent transition-colors text-[11px]"><%= t('footer.documentation') %></a></li>
|
||||
<li><a href="/support" class="text-muted hover:text-accent transition-colors text-[11px]"><%= t('nav.support') %></a></li>
|
||||
<li><a href="#" class="text-muted hover:text-accent transition-colors text-[11px]" id="footerInviteBtn"><%= t('footer.inviteBot') %></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 class="font-semibold mb-1.5 text-accent text-xs uppercase tracking-wide opacity-70"><%= t('footer.legal') %></h4>
|
||||
<ul class="space-y-1">
|
||||
<li><a href="/terms" class="text-muted hover:text-accent transition-colors text-[11px]"><%= t('footer.termsOfService') %></a></li>
|
||||
<li><a href="/terms" class="text-muted hover:text-accent transition-colors text-[11px]"><%= t('footer.privacyPolicy') %></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pt-3 text-center text-[10px] border-t border-[rgba(144,238,144,0.08)] text-muted opacity-60">
|
||||
<p><%= t('footer.meowing') %> | <%= t('footer.websiteBy') %> <a href="https://clippi.dev/" target="_blank" rel="noopener noreferrer" class="text-accent hover:underline">@clippi.dev</a> <%= t('footer.andToothless') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
@@ -0,0 +1,102 @@
|
||||
<style>
|
||||
#langDropdown .hidden-dropdown { display: none; }
|
||||
#langDropdown.open .hidden-dropdown { display: block; }
|
||||
</style>
|
||||
<!-- Navigation Bar -->
|
||||
<nav class="fixed top-0 left-0 right-0 bg-[#1E1E1E]/98 backdrop-blur-md border-b border-white/5" style="z-index: 1000;">
|
||||
<div class="w-full px-6 lg:px-8">
|
||||
<div class="flex items-center h-16">
|
||||
<!-- Left: Logo anchored -->
|
||||
<a href="/" class="flex items-center space-x-3 flex-shrink-0">
|
||||
<img src="/images/toothless_server.gif" alt="<%= botName %>" class="h-8 w-auto" width="32" height="32">
|
||||
<span class="text-lg font-semibold"><span class="text-[#90EE90]">Toothless</span> <span class="text-accent">SREBOT</span></span>
|
||||
</a>
|
||||
<!-- Center: Nav links -->
|
||||
<div class="hidden lg:flex items-center justify-center flex-1 space-x-6">
|
||||
<% const navLinks = [
|
||||
{ href: '/', key: 'home' },
|
||||
{ href: '/squadrons', key: 'squadrons' },
|
||||
{ href: '/leaderboard/players', key: 'leaderboards' },
|
||||
{ href: '/analytics', key: 'analytics' },
|
||||
{ href: '/docs', key: 'docs' },
|
||||
]; %>
|
||||
<% navLinks.forEach(link => { %>
|
||||
<% if (typeof activePage !== 'undefined' && activePage === link.key) { %>
|
||||
<a href="<%= link.href %>" class="text-accent transition-colors text-sm font-medium border-b-2 border-accent pb-1"><%= t('nav.' + link.key) %></a>
|
||||
<% } else { %>
|
||||
<a href="<%= link.href %>" class="text-muted hover:text-accent transition-colors text-sm font-medium"><%= t('nav.' + link.key) %></a>
|
||||
<% } %>
|
||||
<% }); %>
|
||||
<a href="/premium" class="<%= typeof activePage !== 'undefined' && activePage === 'premium' ? 'nav-premium transition-colors text-sm font-medium border-b-2 border-[#ffd700] pb-1' : 'nav-premium transition-colors text-sm font-medium' %>"><%= t('nav.premium') %></a>
|
||||
<a href="/support" class="nav-rainbow transition-colors text-sm font-medium flex items-center">
|
||||
<i class="fas fa-life-ring mr-1.5 text-xs"></i>
|
||||
<%= t('nav.support') %>
|
||||
</a>
|
||||
<a href="https://ko-fi.com/notsotoothless" target="_blank" rel="noopener noreferrer" class="nav-donate transition-colors text-sm font-medium flex items-center">
|
||||
<i class="fas fa-heart mr-1.5 text-xs"></i>
|
||||
<%= t('nav.donate') %>
|
||||
</a>
|
||||
</div>
|
||||
<!-- Right: Language toggle + Discord -->
|
||||
<div class="hidden lg:flex items-center space-x-6 flex-shrink-0">
|
||||
<div class="relative" id="langDropdown">
|
||||
<button onclick="document.getElementById('langDropdown').classList.toggle('open')" class="flex items-center gap-3 px-3 py-1.5 text-sm font-medium rounded-md border transition-colors" style="border-color: rgba(144,238,144,0.2); color: #90EE90;">
|
||||
<span id="langCurrent"><%= lang === 'en' ? 'ENG' : lang === 'ru' ? 'РУС' : lang === 'fr' ? 'FRA' : lang === 'it' ? 'ITA' : lang === 'uk' ? 'УКР' : lang === 'de' ? 'DEU' : lang === 'es' ? 'ESP' : lang === 'pl' ? 'POL' : lang === 'cs' ? 'CES' : '简中' %></span>
|
||||
<i class="fas fa-chevron-down text-[10px] opacity-60"></i>
|
||||
</button>
|
||||
<div class="absolute right-0 top-full mt-1 min-w-[170px] bg-[#2A2A2A] border border-white/10 rounded-lg shadow-xl overflow-hidden hidden-dropdown" style="z-index: 100;">
|
||||
<button onclick="switchLanguage('en')" class="lang-option w-full text-left px-3 py-2 text-sm hover:bg-white/5 transition-colors <%= lang === 'en' ? 'text-[#90EE90]' : 'text-white/70' %>">ENG <span class="text-white/30 ml-1">English</span></button>
|
||||
<button onclick="switchLanguage('ru')" class="lang-option w-full text-left px-3 py-2 text-sm hover:bg-white/5 transition-colors <%= lang === 'ru' ? 'text-[#90EE90]' : 'text-white/70' %>">РУС <span class="text-white/30 ml-1">Русский</span></button>
|
||||
<button onclick="switchLanguage('fr')" class="lang-option w-full text-left px-3 py-2 text-sm hover:bg-white/5 transition-colors <%= lang === 'fr' ? 'text-[#90EE90]' : 'text-white/70' %>">FRA <span class="text-white/30 ml-1">Français</span></button>
|
||||
<button onclick="switchLanguage('it')" class="lang-option w-full text-left px-3 py-2 text-sm hover:bg-white/5 transition-colors <%= lang === 'it' ? 'text-[#90EE90]' : 'text-white/70' %>">ITA <span class="text-white/30 ml-1">Italiano</span></button>
|
||||
<button onclick="switchLanguage('uk')" class="lang-option w-full text-left px-3 py-2 text-sm hover:bg-white/5 transition-colors <%= lang === 'uk' ? 'text-[#90EE90]' : 'text-white/70' %>">УКР <span class="text-white/30 ml-1">Українська</span></button>
|
||||
<button onclick="switchLanguage('de')" class="lang-option w-full text-left px-3 py-2 text-sm hover:bg-white/5 transition-colors <%= lang === 'de' ? 'text-[#90EE90]' : 'text-white/70' %>">DEU <span class="text-white/30 ml-1">Deutsch</span></button>
|
||||
<button onclick="switchLanguage('es')" class="lang-option w-full text-left px-3 py-2 text-sm hover:bg-white/5 transition-colors <%= lang === 'es' ? 'text-[#90EE90]' : 'text-white/70' %>">ESP <span class="text-white/30 ml-1">Español</span></button>
|
||||
<button onclick="switchLanguage('pl')" class="lang-option w-full text-left px-3 py-2 text-sm hover:bg-white/5 transition-colors <%= lang === 'pl' ? 'text-[#90EE90]' : 'text-white/70' %>">POL <span class="text-white/30 ml-1">Polski</span></button>
|
||||
<button onclick="switchLanguage('cs')" class="lang-option w-full text-left px-3 py-2 text-sm hover:bg-white/5 transition-colors <%= lang === 'cs' ? 'text-[#90EE90]' : 'text-white/70' %>">CES <span class="text-white/30 ml-1">Čeština</span></button>
|
||||
<button onclick="switchLanguage('zh-CN')" class="lang-option w-full text-left px-3 py-2 text-sm hover:bg-white/5 transition-colors <%= lang === 'zh-CN' ? 'text-[#90EE90]' : 'text-white/70' %>">简中 <span class="text-white/30 ml-1">简体中文</span></button>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn-primary px-4 py-2 rounded-lg text-sm font-bold inline-flex items-center" id="inviteBtn">
|
||||
<i class="fab fa-discord mr-2"></i><%= t('nav.addToDiscord') %>
|
||||
</button>
|
||||
</div>
|
||||
<button class="lg:hidden text-accent" id="mobileMenuBtn">
|
||||
<i class="fas fa-bars text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hidden lg:hidden border-t border-white/5 bg-[#1E1E1E]/98" id="mobileMenu">
|
||||
<div class="px-6 py-4 space-y-2">
|
||||
<% navLinks.forEach(link => { %>
|
||||
<% if (typeof activePage !== 'undefined' && activePage === link.key) { %>
|
||||
<a href="<%= link.href %>" class="block px-4 py-2 text-accent bg-white/5 rounded-md"><%= t('nav.' + link.key) %></a>
|
||||
<% } else { %>
|
||||
<a href="<%= link.href %>" class="block px-4 py-2 text-muted hover:bg-white/5 rounded-md"><%= t('nav.' + link.key) %></a>
|
||||
<% } %>
|
||||
<% }); %>
|
||||
<a href="/premium" class="block px-4 py-2 nav-premium <%= typeof activePage !== 'undefined' && activePage === 'premium' ? 'bg-white/5' : 'hover:bg-white/5' %> rounded-md"><%= t('nav.premium') %></a>
|
||||
<a href="/support" class="block px-4 py-2 nav-rainbow hover:bg-white/5 rounded-md">
|
||||
<i class="fas fa-life-ring mr-2"></i><%= t('nav.support') %>
|
||||
</a>
|
||||
<a href="https://ko-fi.com/notsotoothless" target="_blank" rel="noopener noreferrer" class="block px-4 py-2 nav-donate hover:bg-white/5 rounded-md">
|
||||
<i class="fas fa-heart mr-2"></i><%= t('nav.donate') %>
|
||||
</a>
|
||||
<button class="block w-full px-4 py-2 btn-primary font-semibold rounded-md text-center" id="inviteBtnMobile">
|
||||
<i class="fab fa-discord mr-2"></i><%= t('nav.addToDiscord') %>
|
||||
</button>
|
||||
<div class="border border-white/10 rounded-md overflow-hidden">
|
||||
<button onclick="switchLanguage('en')" class="block w-full text-left px-4 py-2 text-sm transition-colors <%= lang === 'en' ? 'bg-[rgba(144,238,144,0.15)] text-[#90EE90]' : 'text-white/50 hover:bg-white/5' %>">ENG — English</button>
|
||||
<button onclick="switchLanguage('ru')" class="block w-full text-left px-4 py-2 text-sm transition-colors <%= lang === 'ru' ? 'bg-[rgba(144,238,144,0.15)] text-[#90EE90]' : 'text-white/50 hover:bg-white/5' %>">РУС — Русский</button>
|
||||
<button onclick="switchLanguage('fr')" class="block w-full text-left px-4 py-2 text-sm transition-colors <%= lang === 'fr' ? 'bg-[rgba(144,238,144,0.15)] text-[#90EE90]' : 'text-white/50 hover:bg-white/5' %>">FRA — Français</button>
|
||||
<button onclick="switchLanguage('it')" class="block w-full text-left px-4 py-2 text-sm transition-colors <%= lang === 'it' ? 'bg-[rgba(144,238,144,0.15)] text-[#90EE90]' : 'text-white/50 hover:bg-white/5' %>">ITA — Italiano</button>
|
||||
<button onclick="switchLanguage('uk')" class="block w-full text-left px-4 py-2 text-sm transition-colors <%= lang === 'uk' ? 'bg-[rgba(144,238,144,0.15)] text-[#90EE90]' : 'text-white/50 hover:bg-white/5' %>">УКР — Українська</button>
|
||||
<button onclick="switchLanguage('de')" class="block w-full text-left px-4 py-2 text-sm transition-colors <%= lang === 'de' ? 'bg-[rgba(144,238,144,0.15)] text-[#90EE90]' : 'text-white/50 hover:bg-white/5' %>">DEU — Deutsch</button>
|
||||
<button onclick="switchLanguage('es')" class="block w-full text-left px-4 py-2 text-sm transition-colors <%= lang === 'es' ? 'bg-[rgba(144,238,144,0.15)] text-[#90EE90]' : 'text-white/50 hover:bg-white/5' %>">ESP — Español</button>
|
||||
<button onclick="switchLanguage('pl')" class="block w-full text-left px-4 py-2 text-sm transition-colors <%= lang === 'pl' ? 'bg-[rgba(144,238,144,0.15)] text-[#90EE90]' : 'text-white/50 hover:bg-white/5' %>">POL — Polski</button>
|
||||
<button onclick="switchLanguage('cs')" class="block w-full text-left px-4 py-2 text-sm transition-colors <%= lang === 'cs' ? 'bg-[rgba(144,238,144,0.15)] text-[#90EE90]' : 'text-white/50 hover:bg-white/5' %>">CES — Čeština</button>
|
||||
<button onclick="switchLanguage('zh-CN')" class="block w-full text-left px-4 py-2 text-sm transition-colors <%= lang === 'zh-CN' ? 'bg-[rgba(144,238,144,0.15)] text-[#90EE90]' : 'text-white/50 hover:bg-white/5' %>">简中 — 简体中文</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,539 @@
|
||||
<!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>Premium - <%= botName %></title>
|
||||
<meta name="description" content="Upgrade to <%= botName %> Premium for automatic game log posts and more.">
|
||||
<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;
|
||||
}
|
||||
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
background: #1b1b1b;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* ── Palette tokens ── */
|
||||
.gradient-text {
|
||||
background: linear-gradient(135deg, #F5F5DC 0%, #d4d4b0 50%, #F5F5DC 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.text-accent { color: #F5F5DC; }
|
||||
.text-muted { color: #90EE90; }
|
||||
|
||||
/* ── Buttons ── */
|
||||
.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 30px rgba(245, 245, 220, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.4);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.btn-primary:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* ── Hero background effects ── */
|
||||
.hero-section {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.crown-glow {
|
||||
text-shadow: 0 0 60px rgba(245, 245, 220, 0.25), 0 0 120px rgba(245, 245, 220, 0.08);
|
||||
}
|
||||
|
||||
/* Pricing accent corner glow */
|
||||
.pricing-glow {
|
||||
position: absolute;
|
||||
top: -80px;
|
||||
right: -80px;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
background: radial-gradient(circle, rgba(144, 238, 144, 0.06) 0%, transparent 70%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* ── Steps ── */
|
||||
.step-card {
|
||||
background: rgba(30, 30, 30, 0.4);
|
||||
border: 1px solid rgba(245, 245, 220, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.step-card:hover {
|
||||
border-color: rgba(245, 245, 220, 0.1);
|
||||
background: rgba(30, 30, 30, 0.6);
|
||||
}
|
||||
|
||||
.step-number {
|
||||
background: linear-gradient(135deg, rgba(144, 238, 144, 0.15) 0%, rgba(144, 238, 144, 0.05) 100%);
|
||||
border: 1px solid rgba(144, 238, 144, 0.2);
|
||||
color: #90EE90;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* ── Success state ── */
|
||||
.success-card {
|
||||
background: linear-gradient(135deg, rgba(62, 78, 62, 0.25) 0%, rgba(44, 78, 44, 0.15) 100%);
|
||||
border: 1px solid rgba(144, 238, 144, 0.15);
|
||||
backdrop-filter: blur(12px);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.success-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, transparent, rgba(144, 238, 144, 0.4), transparent);
|
||||
}
|
||||
.success-icon-ring {
|
||||
background: linear-gradient(135deg, rgba(144, 238, 144, 0.15) 0%, rgba(144, 238, 144, 0.05) 100%);
|
||||
border: 2px solid rgba(144, 238, 144, 0.3);
|
||||
}
|
||||
|
||||
/* ── Check items ── */
|
||||
.check-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.75rem;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
.check-icon {
|
||||
color: #90EE90;
|
||||
flex-shrink: 0;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* ── Dividers ── */
|
||||
.accent-divider {
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, transparent, rgba(144, 238, 144, 0.2), transparent);
|
||||
}
|
||||
|
||||
/* ── Plan cards ── */
|
||||
.plan-card {
|
||||
background: linear-gradient(160deg, rgba(40, 40, 40, 0.5) 0%, rgba(30, 30, 30, 0.4) 100%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
backdrop-filter: blur(12px);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
.plan-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.08), transparent);
|
||||
}
|
||||
|
||||
.plan-card-premium {
|
||||
background: linear-gradient(160deg, rgba(62, 78, 62, 0.25) 0%, rgba(30, 30, 30, 0.5) 50%, rgba(62, 78, 62, 0.15) 100%);
|
||||
border: 1px solid rgba(245, 245, 220, 0.12);
|
||||
}
|
||||
.plan-card-premium::before {
|
||||
background: linear-gradient(90deg, transparent, rgba(245, 245, 220, 0.3), transparent);
|
||||
}
|
||||
|
||||
.plan-feature-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.6rem 0;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
.plan-feature-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* ── Animations ── */
|
||||
@keyframes fadeUp {
|
||||
from { opacity: 0; transform: translateY(16px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
.fade-up { animation: fadeUp 0.6s ease-out both; }
|
||||
.fade-up-1 { animation-delay: 0.05s; }
|
||||
.fade-up-2 { animation-delay: 0.12s; }
|
||||
.fade-up-3 { animation-delay: 0.19s; }
|
||||
.fade-up-4 { animation-delay: 0.26s; }
|
||||
.fade-up-5 { animation-delay: 0.33s; }
|
||||
.fade-up-6 { animation-delay: 0.40s; }
|
||||
|
||||
@keyframes shimmer {
|
||||
0% { background-position: -200% center; }
|
||||
100% { background-position: 200% center; }
|
||||
}
|
||||
.shimmer-border::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
padding: 1px;
|
||||
background: linear-gradient(90deg, transparent 0%, rgba(245, 245, 220, 0.15) 50%, transparent 100%);
|
||||
background-size: 200% 100%;
|
||||
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
animation: shimmer 4s ease-in-out infinite;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.fade-up { animation: none; opacity: 1; }
|
||||
.shimmer-border::after { animation: none; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="text-white antialiased">
|
||||
<%- include('partials/nav', { activePage: 'premium' }) %>
|
||||
|
||||
<% if (success) { %>
|
||||
|
||||
<!-- ════════════ SUCCESS STATE ════════════ -->
|
||||
<section class="pt-32 pb-20 lg:pt-40">
|
||||
<div class="max-w-[640px] mx-auto px-6 lg:px-8">
|
||||
<div class="success-card rounded-2xl p-8 lg:p-10 text-center fade-up">
|
||||
<div class="success-icon-ring w-20 h-20 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||
<i class="fas fa-check text-3xl text-muted"></i>
|
||||
</div>
|
||||
|
||||
<h1 class="text-2xl lg:text-3xl font-bold text-accent mb-3"><%= t('premium.successTitle') %></h1>
|
||||
<p class="text-white/70 leading-relaxed mb-8 max-w-md mx-auto">
|
||||
<%= t('premium.successDesc') %>
|
||||
</p>
|
||||
|
||||
<div class="accent-divider mb-8"></div>
|
||||
|
||||
<h3 class="text-sm font-semibold text-accent uppercase tracking-wider mb-4"><%= t('premium.whatHappensNext') %></h3>
|
||||
<div class="space-y-3 text-left max-w-sm mx-auto">
|
||||
<div class="check-item">
|
||||
<i class="fas fa-circle-check check-icon text-sm"></i>
|
||||
<span class="text-white/70 text-sm"><%= t('premium.autoLogging') %></span>
|
||||
</div>
|
||||
<div class="check-item">
|
||||
<i class="fas fa-circle-check check-icon text-sm"></i>
|
||||
<span class="text-white/70 text-sm"><%= t('premium.setLogChannel') %> <code class="text-accent/80 bg-white/5 px-1.5 py-0.5 rounded text-xs">/quick-log</code></span>
|
||||
</div>
|
||||
<div class="check-item">
|
||||
<i class="fas fa-circle-check check-icon text-sm"></i>
|
||||
<span class="text-white/70 text-sm"><%= t('premium.everyResult') %></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8">
|
||||
<a href="/docs" class="text-muted hover:text-accent transition-colors text-sm font-medium">
|
||||
<i class="fas fa-book mr-1.5"></i><%= t('premium.readSetupGuide') %>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<% } else { %>
|
||||
|
||||
<!-- ════════════ HERO ════════════ -->
|
||||
<section class="hero-section pt-32 pb-16 lg:pt-40 lg:pb-20">
|
||||
<div class="max-w-[1100px] mx-auto px-6 lg:px-8 relative z-10">
|
||||
|
||||
<div class="text-center fade-up fade-up-1">
|
||||
|
||||
<h1 class="text-3xl md:text-4xl lg:text-5xl font-extrabold mb-6 crown-glow leading-tight">
|
||||
<span class="gradient-text"><%= t('premium.upgradeTitle') %></span>
|
||||
</h1>
|
||||
<p class="text-sm md:text-base text-white/45 max-w-2xl mx-auto leading-loose mb-10">
|
||||
<%= t('premium.heroDesc') %>
|
||||
</p>
|
||||
|
||||
<!-- Compact feature pills -->
|
||||
<div class="flex flex-wrap justify-center gap-3 fade-up fade-up-2">
|
||||
<div class="inline-flex items-center gap-2 bg-white/[0.04] border border-white/[0.06] rounded-full px-4 py-2">
|
||||
<i class="fas fa-bolt text-muted text-[10px]"></i>
|
||||
<span class="text-[12px] text-white/50 font-medium"><%= t('premium.instantScoreboards') %></span>
|
||||
</div>
|
||||
<div class="inline-flex items-center gap-2 bg-white/[0.04] border border-white/[0.06] rounded-full px-4 py-2">
|
||||
<i class="fas fa-route text-muted text-[10px]"></i>
|
||||
<span class="text-[12px] text-white/50 font-medium"><%= t('premium.viewPaths') %></span>
|
||||
</div>
|
||||
<div class="inline-flex items-center gap-2 bg-white/[0.04] border border-white/[0.06] rounded-full px-4 py-2">
|
||||
<i class="fas fa-comments text-muted text-[10px]"></i>
|
||||
<span class="text-[12px] text-white/50 font-medium"><%= t('premium.chatBattleLogs') %></span>
|
||||
</div>
|
||||
<div class="inline-flex items-center gap-2 bg-white/[0.04] border border-white/[0.06] rounded-full px-4 py-2">
|
||||
<i class="fas fa-gamepad text-muted text-[10px]"></i>
|
||||
<span class="text-[12px] text-white/50 font-medium"><%= t('premium.replayLookups') %></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════════ FREE vs TIERED PLANS ════════════ -->
|
||||
<section class="pb-14 lg:pb-24">
|
||||
<div class="max-w-[1200px] mx-auto px-6 lg:px-8">
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 lg:gap-5">
|
||||
|
||||
<!-- ── FREE PLAN ── -->
|
||||
<div id="free" class="plan-card rounded-2xl p-6 fade-up fade-up-3 flex flex-col">
|
||||
<div class="mb-5">
|
||||
<div class="inline-flex items-center gap-2 bg-white/[0.04] border border-white/[0.06] rounded-full px-4 py-1.5 mb-3">
|
||||
<i class="fas fa-paper-plane text-white/30 text-[10px]"></i>
|
||||
<span class="text-[11px] font-semibold text-white/40 uppercase tracking-wider"><%= t('premium.free') %></span>
|
||||
</div>
|
||||
<div class="flex items-baseline gap-2">
|
||||
<span class="text-2xl font-extrabold text-white/70 tracking-tight">$0</span>
|
||||
<span class="text-white/25 text-xs font-medium"><%= t('premium.perMonth') %></span>
|
||||
</div>
|
||||
<p class="text-[11px] text-white/25 mt-1"><%= t('premium.alwaysFree') %> · <%= t('premium.noCardNeeded') %></p>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="text-[10px] font-semibold text-white/20 uppercase tracking-wider mb-2"><%= t('premium.included') %></p>
|
||||
<div class="plan-feature-row">
|
||||
<i class="fas fa-check text-xs text-white/25"></i>
|
||||
<span class="text-[13px] text-white/50"><%= t('premium.manualLookups') %></span>
|
||||
</div>
|
||||
<div class="plan-feature-row">
|
||||
<i class="fas fa-check text-xs text-white/25"></i>
|
||||
<span class="text-[13px] text-white/50"><%= t('premium.playerStats') %></span>
|
||||
</div>
|
||||
<div class="plan-feature-row">
|
||||
<i class="fas fa-check text-xs text-white/25"></i>
|
||||
<span class="text-[13px] text-white/50"><%= t('premium.leaderboards') %></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<a href="https://discord.com/oauth2/authorize?client_id=1254679514466877540&permissions=2048&scope=bot%20applications.commands" target="_blank" rel="noopener noreferrer"
|
||||
class="w-full py-2.5 rounded-xl text-[13px] font-bold inline-flex items-center justify-center gap-2 text-center border border-white/10 text-white/50 hover:border-white/20 hover:text-white/70 hover:bg-white/[0.03] transition-all cursor-pointer">
|
||||
<%= t('premium.stickWithFree') %>
|
||||
<span class="text-white/20">·</span>
|
||||
<i class="fab fa-discord text-xs"></i> <%= t('nav.addToDiscord') %>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── STANDARD TIER ── -->
|
||||
<div id="standard" class="plan-card rounded-2xl p-6 fade-up fade-up-3 flex flex-col">
|
||||
<div class="mb-5">
|
||||
<div class="inline-flex items-center gap-2 bg-white/[0.04] border border-white/[0.06] rounded-full px-4 py-1.5 mb-3">
|
||||
<i class="fas fa-shield-alt text-white/40 text-[10px]"></i>
|
||||
<span class="text-[11px] font-semibold text-white/60 uppercase tracking-wider"><%= t('premium.tierStandardName') %></span>
|
||||
</div>
|
||||
<div class="flex items-baseline gap-2">
|
||||
<span class="text-2xl font-extrabold text-white/80 tracking-tight">$<%= plans.standard.price %></span>
|
||||
<span class="text-white/30 text-xs font-medium"><%= t('premium.perMonth') %></span>
|
||||
</div>
|
||||
<p class="text-[11px] text-white/30 mt-1"><%= t('premium.perServer') %> · <%= t('premium.cancelAnytime') %></p>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="text-[10px] font-semibold text-accent uppercase tracking-wider mb-2">
|
||||
<%= t('premium.squadCap', { cap: plans.standard.squadCap }) %>
|
||||
</p>
|
||||
<div class="plan-feature-row">
|
||||
<i class="fas fa-circle-check text-xs text-muted"></i>
|
||||
<span class="text-[13px] text-white/60"><%= t('premium.autoScoreboards') %></span>
|
||||
</div>
|
||||
<div class="plan-feature-row">
|
||||
<i class="fas fa-circle-check text-xs text-muted"></i>
|
||||
<span class="text-[13px] text-white/60"><%= t('premium.pathMaps') %></span>
|
||||
</div>
|
||||
<div class="plan-feature-row">
|
||||
<i class="fas fa-circle-check text-xs text-muted"></i>
|
||||
<span class="text-[13px] text-white/60"><%= t('premium.chatLogs') %></span>
|
||||
</div>
|
||||
<div class="plan-feature-row">
|
||||
<i class="fas fa-circle-check text-xs text-muted"></i>
|
||||
<span class="text-[13px] text-white/60"><%= t('premium.replayLookupsFeature') %></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<% if (plans.standard.whopPlanId) { %>
|
||||
<a href="https://whop.com/checkout/<%= plans.standard.whopPlanId %>/?d2c=true" target="_blank" rel="noopener noreferrer"
|
||||
class="btn-primary w-full py-2.5 rounded-xl text-[13px] font-bold inline-block text-center">
|
||||
<i class="fas fa-shield-alt mr-2"></i><%= t('premium.subscribeNow') %>
|
||||
</a>
|
||||
<% } else { %>
|
||||
<button class="btn-primary w-full py-2.5 rounded-xl text-[13px] font-bold" disabled>
|
||||
<%= t('premium.comingSoon') %>
|
||||
</button>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── PRO TIER (highlighted) ── -->
|
||||
<div id="pro" class="plan-card plan-card-premium shimmer-border rounded-2xl p-6 fade-up fade-up-4 flex flex-col">
|
||||
<div class="pricing-glow"></div>
|
||||
<div class="relative z-10 flex flex-col h-full">
|
||||
<div class="mb-5">
|
||||
<div class="inline-flex items-center gap-2 bg-white/[0.04] border border-white/[0.06] rounded-full px-4 py-1.5 mb-3">
|
||||
<i class="fas fa-crown text-accent text-[10px]"></i>
|
||||
<span class="text-[11px] font-semibold text-accent uppercase tracking-wider"><%= t('premium.tierProName') %></span>
|
||||
</div>
|
||||
<div class="flex items-baseline gap-2">
|
||||
<span class="text-2xl font-extrabold text-accent tracking-tight">
|
||||
<% if (plans.pro.price) { %>$<%= plans.pro.price %><% } else { %>—<% } %>
|
||||
</span>
|
||||
<span class="text-white/30 text-xs font-medium"><%= t('premium.perMonth') %></span>
|
||||
</div>
|
||||
<p class="text-[11px] text-white/30 mt-1"><%= t('premium.perServer') %> · <%= t('premium.cancelAnytime') %></p>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="text-[10px] font-semibold text-accent uppercase tracking-wider mb-2">
|
||||
<%= t('premium.squadCap', { cap: plans.pro.squadCap }) %>
|
||||
</p>
|
||||
<div class="plan-feature-row">
|
||||
<i class="fas fa-circle-check text-xs text-muted"></i>
|
||||
<span class="text-[13px] text-white/60"><%= t('premium.everythingInStandard') %></span>
|
||||
</div>
|
||||
<div class="plan-feature-row">
|
||||
<i class="fas fa-circle-check text-xs text-muted"></i>
|
||||
<span class="text-[13px] text-white/60"><%= t('premium.wildcardSupport') %></span>
|
||||
</div>
|
||||
<div class="plan-feature-row">
|
||||
<i class="fas fa-circle-check text-xs text-muted"></i>
|
||||
<span class="text-[13px] text-white/60"><%= t('premium.unlimitedComp') %></span>
|
||||
</div>
|
||||
<div class="plan-feature-row">
|
||||
<i class="fas fa-circle-check text-xs text-muted"></i>
|
||||
<span class="text-[13px] text-white/60"><%= t('premium.prioritySupport') %></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<% if (plans.pro.whopPlanId) { %>
|
||||
<a href="https://whop.com/checkout/<%= plans.pro.whopPlanId %>/?d2c=true" target="_blank" rel="noopener noreferrer"
|
||||
class="btn-primary w-full py-2.5 rounded-xl text-[13px] font-bold inline-block text-center">
|
||||
<i class="fas fa-crown mr-2"></i><%= t('premium.subscribeNow') %>
|
||||
</a>
|
||||
<% } else { %>
|
||||
<button class="btn-primary w-full py-2.5 rounded-xl text-[13px] font-bold" disabled>
|
||||
<%= t('premium.comingSoon') %>
|
||||
</button>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── MAX TIER ── -->
|
||||
<div id="max" class="plan-card rounded-2xl p-6 fade-up fade-up-4 flex flex-col">
|
||||
<div class="mb-5">
|
||||
<div class="inline-flex items-center gap-2 bg-white/[0.04] border border-white/[0.06] rounded-full px-4 py-1.5 mb-3">
|
||||
<i class="fas fa-infinity text-accent text-[10px]"></i>
|
||||
<span class="text-[11px] font-semibold text-accent uppercase tracking-wider"><%= t('premium.tierMaxName') %></span>
|
||||
</div>
|
||||
<div class="flex items-baseline gap-2">
|
||||
<span class="text-2xl font-extrabold text-accent tracking-tight">
|
||||
<% if (plans.max.price) { %>$<%= plans.max.price %><% } else { %>—<% } %>
|
||||
</span>
|
||||
<span class="text-white/30 text-xs font-medium"><%= t('premium.perMonth') %></span>
|
||||
</div>
|
||||
<p class="text-[11px] text-white/30 mt-1"><%= t('premium.perServer') %> · <%= t('premium.cancelAnytime') %></p>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="text-[10px] font-semibold text-accent uppercase tracking-wider mb-2">
|
||||
<%= t('premium.squadCapUnlimited') %>
|
||||
</p>
|
||||
<div class="plan-feature-row">
|
||||
<i class="fas fa-circle-check text-xs text-muted"></i>
|
||||
<span class="text-[13px] text-white/60"><%= t('premium.everythingInPro') %></span>
|
||||
</div>
|
||||
<div class="plan-feature-row">
|
||||
<i class="fas fa-circle-check text-xs text-muted"></i>
|
||||
<span class="text-[13px] text-white/60"><%= t('premium.noSquadCap') %></span>
|
||||
</div>
|
||||
<div class="plan-feature-row">
|
||||
<i class="fas fa-circle-check text-xs text-muted"></i>
|
||||
<span class="text-[13px] text-white/60"><%= t('premium.earlyAccessFeatures') %></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<% if (plans.max.whopPlanId) { %>
|
||||
<a href="https://whop.com/checkout/<%= plans.max.whopPlanId %>/?d2c=true" target="_blank" rel="noopener noreferrer"
|
||||
class="btn-primary w-full py-2.5 rounded-xl text-[13px] font-bold inline-block text-center">
|
||||
<i class="fas fa-infinity mr-2"></i><%= t('premium.subscribeNow') %>
|
||||
</a>
|
||||
<% } else { %>
|
||||
<button class="btn-primary w-full py-2.5 rounded-xl text-[13px] font-bold" disabled>
|
||||
<%= t('premium.comingSoon') %>
|
||||
</button>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Server ID info (compact inline stepper) -->
|
||||
<div class="max-w-lg mx-auto mt-6 rounded-xl p-4 step-card fade-up fade-up-5">
|
||||
<p class="text-white/30 text-xs leading-relaxed mb-3 text-center">
|
||||
<i class="fas fa-info-circle mr-1 text-white/20"></i>
|
||||
<%= t('premium.serverIdInfo') %> <span class="text-white/50 font-medium"><%= t('premium.discordServerId') %></span> <%= t('premium.duringCheckout') %>
|
||||
</p>
|
||||
<div class="flex items-center justify-center gap-2 text-[11px] text-white/30">
|
||||
<span class="step-number w-5 h-5 rounded-full flex items-center justify-center flex-shrink-0 text-[10px]">1</span>
|
||||
<span><%= t('premium.developerMode') %></span>
|
||||
<i class="fas fa-chevron-right text-[8px] text-white/15 mx-1"></i>
|
||||
<span class="step-number w-5 h-5 rounded-full flex items-center justify-center flex-shrink-0 text-[10px]">2</span>
|
||||
<span><%= t('premium.rightClickServer') %></span>
|
||||
<i class="fas fa-chevron-right text-[8px] text-white/15 mx-1"></i>
|
||||
<span class="step-number w-5 h-5 rounded-full flex items-center justify-center flex-shrink-0 text-[10px]">3</span>
|
||||
<span><%= t('premium.copyId') %></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<% } %>
|
||||
|
||||
<!-- 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"></script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,576 @@
|
||||
<!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('squadrons.title') %> | <%= botName %></title>
|
||||
<meta name="description" content="<%= t('squadrons.subtitle') %>">
|
||||
<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);
|
||||
}
|
||||
|
||||
.squadron-hub-container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 6rem 1rem 2rem;
|
||||
min-height: calc(100vh - 200px);
|
||||
}
|
||||
|
||||
.squadron-header {
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.squadron-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;
|
||||
}
|
||||
|
||||
.squadron-subtitle {
|
||||
font-size: 1.1rem;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.squadron-search-section {
|
||||
background: rgba(30, 30, 30, 0.6);
|
||||
border-radius: 1rem;
|
||||
padding: 1.5rem 2rem;
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(144, 238, 144, 0.1);
|
||||
margin-bottom: 2rem;
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.search-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: #90EE90;
|
||||
margin-bottom: 0.75rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.squadron-search-container {
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.squadron-search-input {
|
||||
width: 100%;
|
||||
padding: 0.875rem 0.875rem 0.875rem 2.75rem;
|
||||
background: rgba(30, 30, 30, 0.9);
|
||||
border: 1px solid rgba(144, 238, 144, 0.2);
|
||||
border-radius: 0.5rem;
|
||||
color: #ffffff;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.squadron-search-input:focus {
|
||||
outline: none;
|
||||
border-color: rgba(144, 238, 144, 0.4);
|
||||
box-shadow: 0 0 0 2px rgba(144, 238, 144, 0.1);
|
||||
}
|
||||
|
||||
.squadron-search-input::placeholder {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.squadron-search-icon {
|
||||
position: absolute;
|
||||
left: 0.875rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #90EE90;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.squadron-search-results {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: rgba(30, 30, 30, 0.98);
|
||||
border: 1px solid rgba(144, 238, 144, 0.2);
|
||||
border-radius: 0.75rem;
|
||||
margin-top: 0.5rem;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
z-index: 999;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.squadron-search-results.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.squadron-search-result-item {
|
||||
display: block;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
padding: 1rem 1.5rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
border-bottom: 1px solid rgba(144, 238, 144, 0.1);
|
||||
}
|
||||
|
||||
.squadron-search-result-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.squadron-search-result-item:hover {
|
||||
background: rgba(144, 238, 144, 0.05);
|
||||
}
|
||||
|
||||
.squadron-result-name {
|
||||
font-weight: 600;
|
||||
color: #90EE90;
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.squadron-result-stats {
|
||||
font-size: 0.9rem;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: rgba(30, 30, 30, 0.6);
|
||||
border-radius: 0.75rem;
|
||||
padding: 1.25rem;
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(144, 238, 144, 0.1);
|
||||
text-align: center;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
border-color: rgba(144, 238, 144, 0.3);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
color: #F5F5DC;
|
||||
display: block;
|
||||
margin-bottom: 0.35rem;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.9rem;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.leaderboard-preview {
|
||||
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);
|
||||
}
|
||||
|
||||
.leaderboard-preview-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.leaderboard-preview-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: #F5F5DC;
|
||||
}
|
||||
|
||||
.view-full-btn {
|
||||
background: linear-gradient(135deg, #F5F5DC 0%, #E8E8D0 100%);
|
||||
color: #1E1E1E;
|
||||
border: none;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
box-shadow: 0 4px 20px rgba(245, 245, 220, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.view-full-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(245, 245, 220, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.squadron-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: rgba(30, 30, 30, 0.8);
|
||||
border-radius: 0.75rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.squadron-table th {
|
||||
background: linear-gradient(135deg, rgba(144, 238, 144, 0.15), rgba(144, 238, 144, 0.05));
|
||||
color: #F5F5DC;
|
||||
padding: 1rem;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.9rem;
|
||||
text-shadow: 0 0 10px rgba(144, 238, 144, 0.3);
|
||||
border-bottom: 1px solid rgba(144, 238, 144, 0.2);
|
||||
}
|
||||
|
||||
.squadron-table td {
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid rgba(144, 238, 144, 0.1);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.squadron-table tr:hover {
|
||||
background: rgba(144, 238, 144, 0.05);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.squadron-name {
|
||||
font-family: 'skyquakesymbols', 'Inter', sans-serif !important;
|
||||
font-weight: 600;
|
||||
color: #90EE90;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.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); }
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body class="text-white antialiased">
|
||||
<%- include('partials/nav', { activePage: 'squadrons' }) %>
|
||||
|
||||
<div class="squadron-hub-container">
|
||||
<div class="squadron-header">
|
||||
<h1 class="squadron-title"><%= t('squadrons.title') %></h1>
|
||||
<p class="squadron-subtitle"><%= t('squadrons.subtitle') %></p>
|
||||
</div>
|
||||
|
||||
<div class="squadron-search-section">
|
||||
<h2 class="search-title"><%= t('squadrons.findSquadron') %></h2>
|
||||
<div class="squadron-search-container">
|
||||
<i class="fas fa-search squadron-search-icon"></i>
|
||||
<input type="text"
|
||||
class="squadron-search-input"
|
||||
id="squadronSearchInput"
|
||||
placeholder="<%= t('squadrons.searchPlaceholder') %>"
|
||||
oninput="searchSquadrons()"
|
||||
onkeydown="handleSquadronKeydown(event)">
|
||||
<div class="squadron-search-results" id="squadronSearchResults"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats-grid" id="statsGrid">
|
||||
<div class="stat-card">
|
||||
<span class="stat-value" id="totalSquadrons">-</span>
|
||||
<span class="stat-label"><%= t('squadrons.totalSquadrons') %></span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-value" id="totalPlayers">-</span>
|
||||
<span class="stat-label"><%= t('squadrons.totalPlayers') %></span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-value" id="totalBattles">-</span>
|
||||
<span class="stat-label"><%= t('squadrons.totalBattles') %></span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-value" id="avgWinRate">-</span>
|
||||
<span class="stat-label"><%= t('squadrons.avgWinRate') %></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="leaderboard-preview">
|
||||
<div class="leaderboard-preview-header">
|
||||
<h2 class="leaderboard-preview-title"><%= t('squadrons.topSquadrons') %></h2>
|
||||
<a href="/leaderboard/squadrons" class="view-full-btn">
|
||||
<i class="fas fa-list"></i> <%= t('leaderboard.viewFullLeaderboard') %>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="loading-state" id="loadingState">
|
||||
<div class="loading-spinner"></div>
|
||||
<p><%= t('squadrons.loadingSquadrons') %></p>
|
||||
</div>
|
||||
|
||||
<div id="leaderboardPreview" style="display: none;">
|
||||
<table class="squadron-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><%= t('common.rank') %></th>
|
||||
<th><%= t('common.squadron') %></th>
|
||||
<th><%= t('common.players') %></th>
|
||||
<th><%= t('common.wins') %></th>
|
||||
<th><%= t('common.winRate') %></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="squadronTableBody">
|
||||
</tbody>
|
||||
</table>
|
||||
</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/header-search.js?v=2"></script>
|
||||
<script>
|
||||
let allSquadrons = [];
|
||||
let searchTimeout;
|
||||
|
||||
async function loadSquadronData() {
|
||||
const loadingState = document.getElementById('loadingState');
|
||||
const leaderboardPreview = document.getElementById('leaderboardPreview');
|
||||
|
||||
try {
|
||||
await window.ensureAPIClient();
|
||||
|
||||
const leaderboardData = await window.apiClient.getSquadronLeaderboard();
|
||||
|
||||
if (!leaderboardData || !leaderboardData.squadrons || leaderboardData.squadrons.length === 0) {
|
||||
loadingState.innerHTML = '<p>' + __t('squadrons.noSquadronData') + '</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
allSquadrons = leaderboardData.squadrons;
|
||||
|
||||
// Update stats
|
||||
document.getElementById('totalSquadrons').textContent = leaderboardData.total_squadrons || allSquadrons.length;
|
||||
|
||||
let totalPlayers = 0;
|
||||
let totalBattles = 0;
|
||||
let totalWinRate = 0;
|
||||
|
||||
allSquadrons.forEach(squadron => {
|
||||
totalPlayers += squadron.player_count || 0;
|
||||
totalBattles += squadron.total_battles || 0;
|
||||
totalWinRate += squadron.win_rate || 0;
|
||||
});
|
||||
|
||||
document.getElementById('totalPlayers').textContent = formatNumber(totalPlayers);
|
||||
document.getElementById('totalBattles').textContent = formatNumber(totalBattles);
|
||||
document.getElementById('avgWinRate').textContent = (totalWinRate / allSquadrons.length).toFixed(1) + '%';
|
||||
|
||||
// Display top 10 squadrons
|
||||
displayTopSquadrons(allSquadrons.slice(0, 10));
|
||||
|
||||
loadingState.style.display = 'none';
|
||||
leaderboardPreview.style.display = 'block';
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading squadron data:', error);
|
||||
loadingState.innerHTML = '<p style="color: #ff3838;">' + __t('squadrons.failedToLoad') + '</p>';
|
||||
}
|
||||
}
|
||||
|
||||
function displayTopSquadrons(squadrons) {
|
||||
const tbody = document.getElementById('squadronTableBody');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
squadrons.forEach((squadron, index) => {
|
||||
const row = document.createElement('tr');
|
||||
row.className = 'row-link';
|
||||
// Canonical URL is /squadrons/<clan_id>; fall back to name only
|
||||
// for older API responses that didn't include clan_id.
|
||||
const squadronUrlPath = squadron.clan_id != null
|
||||
? String(squadron.clan_id)
|
||||
: encodeURIComponent(squadron.long_name || squadron.tag_name || squadron.squadron_name);
|
||||
const squadronDisplayName = squadron.tag_name || squadron.squadron_name;
|
||||
|
||||
row.innerHTML = `
|
||||
<td><a href="/squadrons/${squadronUrlPath}" class="row-link-overlay" aria-label="View ${escapeHtml(squadronDisplayName)}"></a>${index + 1}</td>
|
||||
<td class="squadron-name" style="font-family: 'skyquakesymbols', 'Inter', sans-serif !important;">${escapeHtml(squadronDisplayName)}</td>
|
||||
<td>${squadron.player_count}</td>
|
||||
<td>${formatNumber(squadron.wins)}</td>
|
||||
<td>${squadron.win_rate.toFixed(1)}%</td>
|
||||
`;
|
||||
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
function searchSquadrons() {
|
||||
const searchTerm = document.getElementById('squadronSearchInput').value.trim();
|
||||
const resultsDiv = document.getElementById('squadronSearchResults');
|
||||
|
||||
clearTimeout(searchTimeout);
|
||||
|
||||
if (searchTerm.length < 2) {
|
||||
resultsDiv.classList.remove('show');
|
||||
return;
|
||||
}
|
||||
|
||||
searchTimeout = setTimeout(() => {
|
||||
const searchLower = searchTerm.toLowerCase();
|
||||
const seenClanIds = new Set();
|
||||
const filteredSquadrons = allSquadrons
|
||||
.filter(squadron => {
|
||||
const names = [squadron.long_name, squadron.tag_name, squadron.short_name, squadron.squadron_name]
|
||||
.filter(Boolean)
|
||||
.map(n => n.toLowerCase());
|
||||
if (!names.some(n => n.includes(searchLower))) return false;
|
||||
// De-dupe by clan_id so a short_name + long_name hit on the same clan
|
||||
// can't show twice in the dropdown.
|
||||
if (squadron.clan_id != null) {
|
||||
if (seenClanIds.has(squadron.clan_id)) return false;
|
||||
seenClanIds.add(squadron.clan_id);
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.slice(0, 10);
|
||||
|
||||
if (filteredSquadrons.length === 0) {
|
||||
resultsDiv.innerHTML = '<div class="squadron-search-result-item">' + __t('common.noSquadronsFound') + '</div>';
|
||||
} else {
|
||||
resultsDiv.innerHTML = filteredSquadrons.map(squadron => {
|
||||
const squadronUrlPath = squadron.clan_id != null
|
||||
? String(squadron.clan_id)
|
||||
: encodeURIComponent(squadron.long_name || squadron.tag_name || squadron.squadron_name);
|
||||
const squadronDisplayName = squadron.tag_name && squadron.long_name
|
||||
? `${squadron.tag_name} ${squadron.long_name}`
|
||||
: (squadron.tag_name || squadron.long_name || squadron.squadron_name);
|
||||
return `
|
||||
<a href="/squadrons/${escapeHtml(squadronUrlPath)}" class="squadron-search-result-item">
|
||||
<div class="squadron-result-name" style="font-family: 'skyquakesymbols', 'Inter', sans-serif !important;">${escapeHtml(squadronDisplayName)}</div>
|
||||
<div class="squadron-result-stats">${squadron.player_count} ${__t('common.playersCount')} • ${formatNumber(squadron.total_kills)} ${__t('js.killsSuffix')} • ${squadron.win_rate.toFixed(1)}% ${__t('js.winRateSuffix')}</div>
|
||||
</a>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
resultsDiv.classList.add('show');
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function handleSquadronKeydown(event) {
|
||||
if (event.key === 'Escape') {
|
||||
document.getElementById('squadronSearchResults').classList.remove('show');
|
||||
document.getElementById('squadronSearchInput').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.toString();
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
document.addEventListener('click', function(event) {
|
||||
if (!event.target.closest('.squadron-search-container')) {
|
||||
document.getElementById('squadronSearchResults').classList.remove('show');
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
setTimeout(loadSquadronData, 100);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,291 @@
|
||||
<!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>Terms of Service & Privacy Policy - <%= botName %></title>
|
||||
<meta name="description" content="Terms of Service and Privacy Policy for <%= botName %> Discord Bot">
|
||||
<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;
|
||||
}
|
||||
|
||||
.gradient-text {
|
||||
background: linear-gradient(135deg, #F5F5DC 0%, #E8E8D0 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
.terms-card {
|
||||
background: linear-gradient(135deg, rgba(62, 78, 62, 0.2) 0%, rgba(44, 44, 44, 0.2) 100%);
|
||||
border: 1px solid rgba(245, 245, 220, 0.08);
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.terms-section h2 {
|
||||
color: #F5F5DC;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid rgba(144, 238, 144, 0.2);
|
||||
}
|
||||
|
||||
.terms-section h3 {
|
||||
color: #90EE90;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.terms-section p {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
line-height: 1.7;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.terms-section ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.terms-section li {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
padding: 0.5rem 0 0.5rem 1.5rem;
|
||||
position: relative;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.terms-section li::before {
|
||||
content: "•";
|
||||
color: #90EE90;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.terms-section li strong {
|
||||
color: #F5F5DC;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="text-white antialiased">
|
||||
<%- include('partials/nav', { activePage: 'terms' }) %>
|
||||
|
||||
<!-- Header -->
|
||||
<section class="pt-32 pb-12 lg:pt-40 lg:pb-16">
|
||||
<div class="max-w-[1400px] mx-auto px-6 lg:px-8">
|
||||
<div class="text-center mb-8">
|
||||
<h1 class="text-4xl lg:text-5xl font-bold mb-4 text-accent"><%= t('terms.pageTitle') %></h1>
|
||||
<p class="text-lg text-muted"><%= t('terms.lastUpdated') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Content -->
|
||||
<section class="pb-20">
|
||||
<div class="max-w-[900px] mx-auto px-6 lg:px-8">
|
||||
<div class="terms-card rounded-xl p-8 mb-8">
|
||||
<div class="terms-section">
|
||||
<h2><i class="fas fa-file-contract mr-2"></i><%= t('terms.termsOfService') %></h2>
|
||||
|
||||
<p><%= t('terms.byUsing') %> <%= botName %><%= t('terms.youAgree') %></p>
|
||||
|
||||
<ul>
|
||||
<li><strong><%= t('terms.useResponsibly') %></strong> – <%= t('terms.useResponsiblyDesc') %></li>
|
||||
<li><strong><%= t('terms.noFunnyBusiness') %></strong> – <%= t('terms.noFunnyBusinessDesc') %></li>
|
||||
<li><strong><%= t('terms.statsAsIs') %></strong> – <%= t('terms.statsAsIsDesc') %></li>
|
||||
<li><strong><%= t('terms.uptimeNotGuaranteed') %></strong> – <%= t('terms.uptimeNotGuaranteedDesc') %></li>
|
||||
<li><strong><%= t('terms.weCanBanYou') %></strong> – <%= t('terms.weCanBanYouDesc') %></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="terms-card rounded-xl p-8 mb-8">
|
||||
<div class="terms-section">
|
||||
<h2><i class="fas fa-crown mr-2"></i><%= t('terms.premiumTitle') %></h2>
|
||||
|
||||
<h3><%= t('terms.premiumWhatYouGet') %></h3>
|
||||
<p><%= t('terms.premiumWhatYouGetDesc') %></p>
|
||||
<ul>
|
||||
<li><%= t('terms.premiumFeature1') %></li>
|
||||
<li><%= t('terms.premiumFeature2') %></li>
|
||||
<li><%= t('terms.premiumFeature3') %></li>
|
||||
<li><%= t('terms.premiumFeature4') %></li>
|
||||
<li><%= t('terms.premiumFeature5') %></li>
|
||||
</ul>
|
||||
|
||||
<h3><%= t('terms.premiumBilling') %></h3>
|
||||
<p><%= t('terms.premiumBillingDesc') %></p>
|
||||
|
||||
<h3><%= t('terms.premiumCancellation') %></h3>
|
||||
<p><%= t('terms.premiumCancellationDesc') %></p>
|
||||
|
||||
<h3><%= t('terms.premiumRefunds') %></h3>
|
||||
<p><%= t('terms.premiumRefundsDesc') %></p>
|
||||
|
||||
<h3><%= t('terms.premiumPriceChanges') %></h3>
|
||||
<p><%= t('terms.premiumPriceChangesDesc') %></p>
|
||||
|
||||
<h3><%= t('terms.premiumTermination') %></h3>
|
||||
<p><%= t('terms.premiumTerminationDesc') %></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="terms-card rounded-xl p-8 mb-8">
|
||||
<div class="terms-section">
|
||||
<h2><i class="fas fa-server mr-2"></i><%= t('terms.serviceAvailabilityTitle') %></h2>
|
||||
|
||||
<h3><%= t('terms.serviceNoWarranty') %></h3>
|
||||
<p><%= t('terms.serviceNoWarrantyDesc') %></p>
|
||||
|
||||
<h3><%= t('terms.serviceLiability') %></h3>
|
||||
<p><%= t('terms.serviceLiabilityDesc') %></p>
|
||||
|
||||
<h3><%= t('terms.serviceCredits') %></h3>
|
||||
<p><%= t('terms.serviceCreditsDesc') %></p>
|
||||
|
||||
<h3><%= t('terms.serviceForceM') %></h3>
|
||||
<p><%= t('terms.serviceForceMDesc') %></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="terms-card rounded-xl p-8 mb-8">
|
||||
<div class="terms-section">
|
||||
<h2><i class="fas fa-shield-alt mr-2"></i><%= t('terms.privacyPolicy') %></h2>
|
||||
|
||||
<h3><%= t('terms.infoWeCollect') %></h3>
|
||||
<p><%= botName %> <%= t('terms.collectsFollowing') %></p>
|
||||
<ul>
|
||||
<li><strong><%= t('terms.discordUserIds') %></strong> <%= t('terms.discordUserIdsDesc') %></li>
|
||||
<li><strong><%= t('terms.squadronIds') %></strong> <%= t('terms.squadronIdsDesc') %></li>
|
||||
<li><strong><%= t('terms.battleData') %></strong> <%= t('terms.battleDataDesc') %></li>
|
||||
<li><strong><%= t('terms.commandUsage') %></strong> <%= t('terms.commandUsageDesc') %></li>
|
||||
</ul>
|
||||
|
||||
<h3><%= t('terms.howWeUse') %></h3>
|
||||
<p><%= t('terms.usedExclusively') %></p>
|
||||
<ul>
|
||||
<li><%= t('terms.trackingPerformance') %></li>
|
||||
<li><%= t('terms.providingHistorical') %></li>
|
||||
<li><%= t('terms.improvingBot') %></li>
|
||||
<li><%= t('terms.troubleshootingIssues') %></li>
|
||||
</ul>
|
||||
|
||||
<h3><%= t('terms.dataStorage') %></h3>
|
||||
<p><%= t('terms.dataStoredSecurely') %></p>
|
||||
<ul>
|
||||
<li><%= t('terms.encryptedServers') %></li>
|
||||
<li><%= t('terms.limitedAccess') %></li>
|
||||
<li><%= t('terms.regularBackups') %></li>
|
||||
</ul>
|
||||
|
||||
<h3><%= t('terms.dataSharing') %></h3>
|
||||
<p><%= t('terms.weDoNot') %></p>
|
||||
<ul>
|
||||
<li><%= t('terms.sellData') %></li>
|
||||
<li><%= t('terms.shareData') %></li>
|
||||
<li><%= t('terms.useForAds') %></li>
|
||||
<li><%= t('terms.transferData') %></li>
|
||||
</ul>
|
||||
|
||||
<h3><%= t('terms.dataRetention') %></h3>
|
||||
<p><%= t('terms.dataRetentionDesc') %></p>
|
||||
|
||||
<h3><%= t('terms.discordIntegration') %></h3>
|
||||
<p><%= t('terms.discordIntegrationDesc') %></p>
|
||||
|
||||
<h3><%= t('terms.ageRestrictions') %></h3>
|
||||
<p><%= t('terms.ageRestrictionsDesc') %></p>
|
||||
|
||||
<h3><%= t('terms.changesToPolicy') %></h3>
|
||||
<p><%= t('terms.changesToPolicyDesc') %></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="terms-card rounded-xl p-8 mb-8">
|
||||
<div class="terms-section">
|
||||
<h2><i class="fas fa-exclamation-triangle mr-2"></i><%= t('terms.disclaimer') %></h2>
|
||||
<p><%= botName %> <%= t('terms.warThunderDisclaimer') %></p>
|
||||
<p><%= t('terms.acknowledgement') %> <%= botName %><%= t('terms.acknowledgementEnd') %></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<a href="/" class="btn-primary px-8 py-4 rounded-xl text-base font-bold inline-flex items-center">
|
||||
<i class="fas fa-home mr-3"></i>
|
||||
<%= t('common.backToHome') %>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 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"></script>
|
||||
<script>
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user