@@ -0,0 +1,483 @@
|
||||
// DOM Content Loaded
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Mobile Navigation
|
||||
const hamburger = document.querySelector('.hamburger');
|
||||
const navMenu = document.querySelector('.nav-menu');
|
||||
const navLinks = document.querySelectorAll('.nav-link');
|
||||
|
||||
if (hamburger && navMenu) {
|
||||
hamburger.addEventListener('click', function() {
|
||||
hamburger.classList.toggle('active');
|
||||
navMenu.classList.toggle('active');
|
||||
|
||||
// Prevent body scroll when menu is open
|
||||
if (navMenu.classList.contains('active')) {
|
||||
document.body.style.overflow = 'hidden';
|
||||
} else {
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
});
|
||||
|
||||
// Keyboard navigation support
|
||||
hamburger.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
hamburger.click();
|
||||
}
|
||||
});
|
||||
|
||||
// Close mobile menu when clicking on nav links
|
||||
navLinks.forEach(link => {
|
||||
link.addEventListener('click', function() {
|
||||
hamburger.classList.remove('active');
|
||||
navMenu.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
});
|
||||
});
|
||||
|
||||
// Close mobile menu when clicking outside
|
||||
document.addEventListener('click', function(e) {
|
||||
if (hamburger && navMenu && !hamburger.contains(e.target) && !navMenu.contains(e.target)) {
|
||||
hamburger.classList.remove('active');
|
||||
navMenu.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
});
|
||||
|
||||
// Close mobile menu on window resize if desktop size
|
||||
window.addEventListener('resize', function() {
|
||||
if (window.innerWidth > 768) {
|
||||
hamburger.classList.remove('active');
|
||||
navMenu.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Smooth Scrolling for Navigation Links
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(this.getAttribute('href'));
|
||||
if (target) {
|
||||
target.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Button Click Handlers
|
||||
const inviteButtons = [
|
||||
'inviteBtn',
|
||||
'inviteBtnMobile',
|
||||
'heroInviteBtn',
|
||||
'ctaInviteBtn',
|
||||
'footerInviteBtn',
|
||||
'freePlanInviteBtn'
|
||||
];
|
||||
|
||||
const supportButtons = [
|
||||
'supportBtn',
|
||||
'heroSupportBtn',
|
||||
'footerSupportBtn'
|
||||
];
|
||||
|
||||
// Handle Invite Button Clicks
|
||||
inviteButtons.forEach(buttonId => {
|
||||
const button = document.getElementById(buttonId);
|
||||
if (button) {
|
||||
button.addEventListener('click', function() {
|
||||
handleInviteClick();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Handle Support Button Clicks
|
||||
supportButtons.forEach(buttonId => {
|
||||
const button = document.getElementById(buttonId);
|
||||
if (button) {
|
||||
button.addEventListener('click', function() {
|
||||
handleSupportClick();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch and Update Stats
|
||||
updateStats();
|
||||
|
||||
// Update stats every 30 seconds
|
||||
setInterval(updateStats, 30000);
|
||||
|
||||
// New nav mobile menu toggle
|
||||
const mobileMenuBtn = document.getElementById('mobileMenuBtn');
|
||||
const mobileMenu = document.getElementById('mobileMenu');
|
||||
if (mobileMenuBtn && mobileMenu) {
|
||||
mobileMenuBtn.addEventListener('click', () => {
|
||||
mobileMenu.classList.toggle('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
// Navbar Scroll Effect
|
||||
const navbar = document.querySelector('.navbar');
|
||||
if (navbar) {
|
||||
window.addEventListener('scroll', function() {
|
||||
if (window.scrollY > 100) {
|
||||
navbar.style.background = 'rgba(13, 14, 15, 0.98)';
|
||||
} else {
|
||||
navbar.style.background = 'rgba(13, 14, 15, 1)';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Animate Numbers on Stats Section Intersection
|
||||
const statsSection = document.querySelector('#stats');
|
||||
if (statsSection) {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
animateNumbers();
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.5 });
|
||||
|
||||
observer.observe(statsSection);
|
||||
}
|
||||
|
||||
// Handle touch events for better mobile experience
|
||||
let touchStartY = 0;
|
||||
let touchEndY = 0;
|
||||
|
||||
document.addEventListener('touchstart', function(e) {
|
||||
touchStartY = e.changedTouches[0].screenY;
|
||||
}, { passive: true });
|
||||
|
||||
document.addEventListener('touchend', function(e) {
|
||||
touchEndY = e.changedTouches[0].screenY;
|
||||
handleSwipe();
|
||||
}, { passive: true });
|
||||
|
||||
function handleSwipe() {
|
||||
const swipeThreshold = 50;
|
||||
const diff = touchStartY - touchEndY;
|
||||
|
||||
// Close mobile menu on upward swipe when menu is open
|
||||
if (navMenu && hamburger && navMenu.classList.contains('active') && diff > swipeThreshold) {
|
||||
hamburger.classList.remove('active');
|
||||
navMenu.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Handle Invite Button Click
|
||||
async function handleInviteClick() {
|
||||
try {
|
||||
// Direct invite URL for Toothless SQB Bot
|
||||
const inviteUrl = 'https://discord.com/oauth2/authorize?client_id=1254679514466877540&permissions=2048&scope=bot%20applications.commands';
|
||||
|
||||
window.open(inviteUrl, '_blank');
|
||||
showNotification(window.__t ? __t('js.openingDiscordInvite') : 'Opening Discord invite!', 'success');
|
||||
} catch (error) {
|
||||
console.error('Error opening invite link:', error);
|
||||
showNotification(window.__t ? __t('js.errorOpeningInvite') : 'Error opening invite link. Please try again later.', 'error');
|
||||
|
||||
// Fallback - same URL but ensure it opens
|
||||
const fallbackUrl = 'https://discord.com/oauth2/authorize?client_id=1254679514466877540&permissions=2048&scope=bot%20applications.commands';
|
||||
window.open(fallbackUrl, '_blank');
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Support Button Click
|
||||
async function handleSupportClick() {
|
||||
try {
|
||||
showNotification(window.__t ? __t('js.gettingSupportLink') : 'Getting support server link...', 'info');
|
||||
|
||||
const response = await fetch('/api/support');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.supportUrl) {
|
||||
window.open(data.supportUrl, '_blank');
|
||||
showNotification(window.__t ? __t('js.openingSupportServer') : 'Opening support server!', 'success');
|
||||
} else {
|
||||
throw new Error('No support URL received');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error getting support link:', error);
|
||||
showNotification(window.__t ? __t('js.errorGettingSupport') : 'Error getting support link. Please try again later.', 'error');
|
||||
|
||||
// Fallback - Real support server invite
|
||||
const fallbackUrl = 'https://discord.gg/BCvkK8JhPe';
|
||||
window.open(fallbackUrl, '_blank');
|
||||
}
|
||||
}
|
||||
|
||||
// Update Stats from API
|
||||
async function updateStats() {
|
||||
try {
|
||||
let stats;
|
||||
|
||||
// Check if API client is available, if not use fallback
|
||||
if (window.apiClient && window.apiClient.getStats) {
|
||||
stats = await window.apiClient.getStats();
|
||||
} else {
|
||||
// Fallback for pages without API client
|
||||
const response = await fetch('/api/stats');
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
stats = await response.json();
|
||||
}
|
||||
|
||||
// Update server count
|
||||
const serverCountEl = document.getElementById('serverCount');
|
||||
if (serverCountEl && stats.servers) {
|
||||
serverCountEl.textContent = formatNumber(stats.servers);
|
||||
}
|
||||
|
||||
// Update user count
|
||||
const userCountEl = document.getElementById('userCount');
|
||||
if (userCountEl && stats.users) {
|
||||
userCountEl.textContent = formatNumber(stats.users) + '+';
|
||||
}
|
||||
|
||||
// Update command count
|
||||
const commandCountEl = document.getElementById('commandCount');
|
||||
if (commandCountEl && stats.commands) {
|
||||
commandCountEl.textContent = stats.commands + '+';
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
// Silently ignore — stat counters are non-critical and failures flash an annoying banner
|
||||
}
|
||||
}
|
||||
|
||||
// Format numbers with commas
|
||||
function formatNumber(num) {
|
||||
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||
}
|
||||
|
||||
// Animate numbers when stats section comes into view
|
||||
function animateNumbers() {
|
||||
const numbers = document.querySelectorAll('.stat-number');
|
||||
|
||||
numbers.forEach(number => {
|
||||
const target = parseInt(number.textContent.replace(/[^0-9]/g, ''));
|
||||
const duration = 2000;
|
||||
const start = performance.now();
|
||||
|
||||
function updateNumber(currentTime) {
|
||||
const elapsed = currentTime - start;
|
||||
const progress = Math.min(elapsed / duration, 1);
|
||||
|
||||
// Easing function
|
||||
const easeOutQuart = 1 - Math.pow(1 - progress, 4);
|
||||
const current = Math.floor(target * easeOutQuart);
|
||||
|
||||
if (number.textContent.includes('+')) {
|
||||
number.textContent = formatNumber(current) + '+';
|
||||
} else {
|
||||
number.textContent = formatNumber(current);
|
||||
}
|
||||
|
||||
if (progress < 1) {
|
||||
requestAnimationFrame(updateNumber);
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(updateNumber);
|
||||
});
|
||||
}
|
||||
|
||||
// Show notification system
|
||||
function showNotification(message, type = 'info') {
|
||||
// Remove existing notifications
|
||||
const existingNotification = document.querySelector('.notification');
|
||||
if (existingNotification) {
|
||||
existingNotification.remove();
|
||||
}
|
||||
|
||||
// Create notification element
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `notification notification-${type}`;
|
||||
notification.textContent = message;
|
||||
|
||||
// Style the notification
|
||||
notification.style.cssText = `
|
||||
position: fixed;
|
||||
top: 100px;
|
||||
right: 20px;
|
||||
background: ${type === 'success' ? '#4caf50' : type === 'error' ? '#f44336' : '#2196f3'};
|
||||
color: white;
|
||||
padding: 15px 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
z-index: 10000;
|
||||
font-weight: 500;
|
||||
opacity: 0;
|
||||
transform: translateX(100%);
|
||||
transition: all 0.3s ease;
|
||||
`;
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// Animate in
|
||||
setTimeout(() => {
|
||||
notification.style.opacity = '1';
|
||||
notification.style.transform = 'translateX(0)';
|
||||
}, 100);
|
||||
|
||||
// Auto remove after 3 seconds
|
||||
setTimeout(() => {
|
||||
notification.style.opacity = '0';
|
||||
notification.style.transform = 'translateX(100%)';
|
||||
setTimeout(() => {
|
||||
if (notification.parentNode) {
|
||||
notification.remove();
|
||||
}
|
||||
}, 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Easter egg - Konami code
|
||||
let konamiCode = [];
|
||||
const konamiSequence = [
|
||||
'ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown',
|
||||
'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight',
|
||||
'KeyB', 'KeyA'
|
||||
];
|
||||
|
||||
function triggerKonamiEasterEgg() {
|
||||
var t = typeof __t === 'function' ? __t : null;
|
||||
|
||||
// --- 1. Screen shake ---
|
||||
document.body.style.transition = 'none';
|
||||
var shakeFrames = [
|
||||
'3px 0', '-3px 1px', '2px -1px', '-2px 2px',
|
||||
'1px -2px', '-1px 1px', '2px 0', '0 0'
|
||||
];
|
||||
var si = 0;
|
||||
var shakeInterval = setInterval(function () {
|
||||
if (si >= shakeFrames.length) { clearInterval(shakeInterval); document.body.style.transform = ''; return; }
|
||||
document.body.style.transform = 'translate(' + shakeFrames[si] + ')';
|
||||
si++;
|
||||
}, 40);
|
||||
|
||||
// --- 2. Confetti burst ---
|
||||
var colors = ['#ff6b6b', '#ffd93d', '#6bcb77', '#4d96ff', '#ff922b', '#cc5de8', '#20c997'];
|
||||
var confettiCount = 80;
|
||||
var container = document.createElement('div');
|
||||
container.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:99999;overflow:hidden;';
|
||||
document.body.appendChild(container);
|
||||
for (var i = 0; i < confettiCount; i++) {
|
||||
var piece = document.createElement('div');
|
||||
var size = Math.random() * 8 + 4;
|
||||
var color = colors[Math.floor(Math.random() * colors.length)];
|
||||
var startX = 50 + (Math.random() - 0.5) * 20;
|
||||
var startY = 50 + (Math.random() - 0.5) * 10;
|
||||
var dx = (Math.random() - 0.5) * 120;
|
||||
var dy = -(Math.random() * 60 + 30);
|
||||
var rot = Math.random() * 720 - 360;
|
||||
var dur = Math.random() * 1.5 + 1.5;
|
||||
piece.style.cssText =
|
||||
'position:absolute;width:' + size + 'px;height:' + (size * 0.6) + 'px;' +
|
||||
'background:' + color + ';border-radius:2px;' +
|
||||
'left:' + startX + '%;top:' + startY + '%;' +
|
||||
'opacity:1;pointer-events:none;';
|
||||
piece.animate([
|
||||
{ transform: 'translate(0,0) rotate(0deg)', opacity: 1 },
|
||||
{ transform: 'translate(' + dx + 'vw,' + dy + 'vh) rotate(' + rot + 'deg)', opacity: 0 }
|
||||
], { duration: dur * 1000, easing: 'cubic-bezier(.25,.8,.25,1)', fill: 'forwards' });
|
||||
container.appendChild(piece);
|
||||
}
|
||||
setTimeout(function () { container.remove(); }, 4000);
|
||||
|
||||
// --- 3. Barrel roll ---
|
||||
setTimeout(function () {
|
||||
var style = document.createElement('style');
|
||||
style.textContent = '@keyframes konamiBarrelRoll{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}';
|
||||
document.head.appendChild(style);
|
||||
document.body.style.transformOrigin = 'center center';
|
||||
document.body.style.animation = 'konamiBarrelRoll 1s ease-in-out';
|
||||
document.body.addEventListener('animationend', function handler() {
|
||||
document.body.style.animation = '';
|
||||
document.body.style.transformOrigin = '';
|
||||
style.remove();
|
||||
document.body.removeEventListener('animationend', handler);
|
||||
});
|
||||
}, 350);
|
||||
|
||||
// --- 4. Themed notification ---
|
||||
var msg = t ? t('js.konamiActivated') : 'Achievement Unlocked: Secret Code!';
|
||||
var notif = document.createElement('div');
|
||||
notif.style.cssText =
|
||||
'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%) scale(0);' +
|
||||
'background:linear-gradient(135deg,rgba(30,30,30,0.95),rgba(50,50,50,0.95));' +
|
||||
'border:2px solid #ffd93d;color:#ffd93d;padding:20px 40px;border-radius:12px;' +
|
||||
'z-index:100000;font-size:1.4rem;font-weight:700;text-align:center;' +
|
||||
'box-shadow:0 0 40px rgba(255,217,61,0.3);pointer-events:none;' +
|
||||
'font-family:inherit;letter-spacing:1px;text-transform:uppercase;' +
|
||||
'transition:transform 0.4s cubic-bezier(.34,1.56,.64,1),opacity 0.3s ease;opacity:0;';
|
||||
notif.textContent = msg;
|
||||
document.body.appendChild(notif);
|
||||
setTimeout(function () {
|
||||
notif.style.transform = 'translate(-50%,-50%) scale(1)';
|
||||
notif.style.opacity = '1';
|
||||
}, 50);
|
||||
setTimeout(function () {
|
||||
notif.style.transform = 'translate(-50%,-50%) scale(0.8)';
|
||||
notif.style.opacity = '0';
|
||||
setTimeout(function () { notif.remove(); }, 400);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', function(e) {
|
||||
konamiCode.push(e.code);
|
||||
|
||||
if (konamiCode.length > konamiSequence.length) {
|
||||
konamiCode.shift();
|
||||
}
|
||||
|
||||
if (konamiCode.join(',') === konamiSequence.join(',')) {
|
||||
triggerKonamiEasterEgg();
|
||||
konamiCode = [];
|
||||
}
|
||||
});
|
||||
|
||||
// Mobile menu toggle
|
||||
function toggleMobileMenu() {
|
||||
const navMenu = document.querySelector('.nav-menu');
|
||||
const hamburger = document.querySelector('.hamburger');
|
||||
|
||||
navMenu.classList.toggle('active');
|
||||
hamburger.classList.toggle('active');
|
||||
}
|
||||
|
||||
// Languages dropdown toggle
|
||||
function toggleLanguagesList() {
|
||||
const languagesList = document.getElementById('languagesList');
|
||||
const dropdownToggle = document.querySelector('.dropdown-toggle');
|
||||
|
||||
languagesList.classList.toggle('show');
|
||||
dropdownToggle.classList.toggle('active');
|
||||
}
|
||||
|
||||
// Language switcher (ENG/RUS)
|
||||
function switchLanguage(lang) {
|
||||
const 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();
|
||||
}
|
||||
|
||||
// Language dropdown: close on outside click
|
||||
document.addEventListener('click', function(e) {
|
||||
var dd = document.getElementById('langDropdown');
|
||||
if (dd && !dd.contains(e.target)) {
|
||||
dd.classList.remove('open');
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user