Initial commit with all files

This commit is contained in:
2025-05-02 12:48:13 +00:00
commit 1b8c572d89
29 changed files with 4100 additions and 0 deletions

6
js/all.min.js vendored Normal file

File diff suppressed because one or more lines are too long

45
js/echarts.min.js vendored Normal file

File diff suppressed because one or more lines are too long

4
js/fade-up.js Normal file
View File

@@ -0,0 +1,4 @@
// Анимация появления блоков при загрузке
document.querySelectorAll('.fade-up').forEach((el,i) => {
setTimeout(() => el.classList.add('animate-fadeUp'), 70+i*90)
});

109
js/features.js Normal file
View File

@@ -0,0 +1,109 @@
const originalFeaturesData = [
{icon:'shield-halved', color:'#0ff'},
{icon:'user-secret', color:'#6efaff'},
{icon:'envelope-open-text', color:'#91eaff'},
{icon:'cloud', color:'#fd7cff'},
{icon:'file-shield', color:'#fedd60'},
{icon:'coins', color:'#41fdc2'},
{icon:'eye-slash', color:'#a6fca8'},
{icon:'users', color:'#dc9afd'},
];
const originalProductsData = [
{color:'#0ff', data:[1,1,1,1,1,1,1,1]},
{color:'#38bdf8', data:[1,1,0,0,0,0,0,1]},
{color:'#8b5cf6', data:[1,1,0,0,0,0,0,0]},
];
let languageData = {};
async function fetchLanguageData(lang) {
try {
let response = await fetch(`/lang/${lang}/json/${lang}.json`);
if (!response.ok) {
console.error(`Failed to load language data for ${lang}: ${response.status}`);
return null;
}
languageData = await response.json();
return languageData;
} catch (error) {
console.error(`Error fetching language data for ${lang}:`, error);
return null;
}
}
function renderFeatures(data) {
const container = document.getElementById('featuresContainer');
container.innerHTML = '';
data.features.forEach((feature, index) => {
const featureRow = document.createElement('div');
featureRow.className = 'feature-row grid grid-cols-4 gap-4 py-3 border-b border-gray-700/50';
featureRow.id = `feature-${index}`;
// Feature name
const nameCell = document.createElement('div');
nameCell.className = 'col-span-1 text-gray-300 text-right pr-4 flex items-center justify-end';
const icon = originalFeaturesData[index].icon;
const color = originalFeaturesData[index].color;
nameCell.innerHTML = `<i class="fas fa-${icon} mr-2" style="color:${color}"></i>${feature.name}`;
// BlackBox
const bbCell = document.createElement('div');
bbCell.className = 'col-span-1 text-center flex items-center justify-center';
const bbData = originalProductsData[0].data[index];
bbCell.innerHTML = bbData
? `<i class="fas fa-check-circle check-icon text-2xl text-cyan-400"></i>`
: `<i class="fas fa-times-circle cross-icon text-xl text-gray-500"></i>`;
// VPN Router
const routerCell = document.createElement('div');
routerCell.className = 'col-span-1 text-center flex items-center justify-center';
const routerData = originalProductsData[1].data[index];
routerCell.innerHTML = routerData
? `<i class="fas fa-check-circle check-icon text-xl text-blue-400 opacity-70"></i>`
: `<i class="fas fa-times-circle cross-icon text-xl text-gray-500"></i>`;
// VPN Service
const vpnCell = document.createElement('div');
vpnCell.className = 'col-span-1 text-center flex items-center justify-center';
const vpnData = originalProductsData[2].data[index];
vpnCell.innerHTML = vpnData
? `<i class="fas fa-check-circle check-icon text-xl text-purple-400 opacity-70"></i>`
: `<i class="fas fa-times-circle cross-icon text-xl text-gray-500"></i>`;
featureRow.appendChild(nameCell);
featureRow.appendChild(bbCell);
featureRow.appendChild(routerCell);
featureRow.appendChild(vpnCell);
container.appendChild(featureRow);
});
}
function setupHoverEffects(data) {
document.querySelectorAll('#featuresContainer > div').forEach((row, index) => {
row.addEventListener('mouseenter', () => {
// Update hint
document.getElementById('dynCompareHint').innerHTML =
`<i class="fas fa-lightbulb text-cyan-300"></i> <span class="font-semibold">${data.features[index].name}:</span> ${data.features[index].hint}`;
});
row.addEventListener('mouseleave', () => {
// Clear hint
document.getElementById('dynCompareHint').innerHTML = '';
});
});
}
document.addEventListener('DOMContentLoaded', async function() {
const currentPath = window.location.pathname;
const lang = currentPath.split('/')[1] || 'en'; // Get language from URL, default to 'en'
const data = await fetchLanguageData(lang);
if (data) {
renderFeatures(data);
setupHoverEffects(data);
}
});

153
js/flipbook.js Normal file
View File

@@ -0,0 +1,153 @@
// Навигация флипбука
let currentPage = 3;
const totalPages = 6;
const flipbookContainer = document.querySelector('.flipbook-container');
const pages = document.querySelectorAll('.flipbook-page');
const indicator = document.querySelector('.flipbook-indicator');
let startTranslateX = 0; // Initial translateX of the active page
const pageSpacing = 16 * 1; // 1rem margin on each side of the page (0.5rem * 2)
function updatePages() {
pages.forEach(page => {
const pageNum = parseInt(page.dataset.page);
const diff = pageNum - currentPage;
// Apply original classes for animation on both mobile and desktop
page.classList.remove('active', 'left', 'right', 'far-left', 'far-right', 'far-far-right', 'far-far-left');
if (diff === 0) {
page.classList.add('active');
} else if (diff === -1) {
page.classList.add('left');
} else if (diff === -2) {
page.classList.add('far-left');
} else if (diff === -3) {
page.classList.add('far-far-left');
} else if (diff === 1) {
page.classList.add('right');
} else if (diff === 2) {
page.classList.add('far-right');
} else if (diff === 3) {
page.classList.add('far-far-right');
}
// Calculate and apply translateX for each page based on its position relative to the active page
// This is the core change for custom scrolling
let translateX;
// Calculate horizontal translation for each page
if (window.innerWidth <= 768) {
// Mobile: Adjusted translateX with tighter spacing
translateX = (pageNum - currentPage) * (page.offsetWidth * 0.6 + pageSpacing) - 15;
// Centering is handled by flexbox on the container
} else {
// Desktop: Adjusted translateX for spacing and to contribute to the curve effect
// Multiplier (0.9) controls horizontal spacing between pages
const desktopSpacing = page.offsetWidth * 0.9 + pageSpacing;
translateX = (pageNum - currentPage) * desktopSpacing;
// Centering is handled by flexbox on the container
}
// Apply combined transform: translateX, rotateY (for flip effect), and scale (for perspective)
// rotateY: Controls the rotation around the Y-axis (the "flip")
// Multipliers (12 for mobile, 20 for desktop) control the degree of rotation per page difference
// scale: Controls the size of the page, creating a perspective effect
// Multipliers (0.08 for mobile, 0.05 for desktop) control how much smaller pages get further away
page.style.transform = `translateX(${translateX}px) rotateY(${diff * (window.innerWidth <= 768 ? 12 : 20)}deg) scale(${1 - Math.abs(diff) * (window.innerWidth <= 768 ? 0.08 : 0.05)})`;
// Adjust z-index based on distance from active page to control stacking order
page.style.zIndex = totalPages - Math.abs(diff);
// Adjust opacity based on distance to create a fading effect for far pages
// Multipliers (0.15 for mobile, 0.1 for desktop) control how much pages fade
page.style.opacity = 1 - Math.abs(diff) * (window.innerWidth <= 768 ? 0.15 : 0.1);
});
// Update indicator position
let position;
if (window.innerWidth <= 768) {
// Indicator position for mobile (centered)
position = ((currentPage - 1) / (totalPages - 1)) * 100 - 50;
} else {
// Indicator position for desktop (adjusted for wider range)
position = ((currentPage - 1) / (totalPages - 1)) * 200 - 100;
}
indicator.style.transform = `translateX(${position}%)`;
}
// Click navigation (keep original logic)
pages.forEach(page => {
page.addEventListener('click', (e) => {
if (!page.classList.contains('active')) {
currentPage = parseInt(page.dataset.page);
updatePages();
}
});
});
// Mouse hover navigation (keep original logic)
let hoverTimer;
pages.forEach(page => {
page.addEventListener('mouseenter', () => {
if (!page.classList.contains('active')) {
clearTimeout(hoverTimer);
hoverTimer = setTimeout(() => {
currentPage = parseInt(page.dataset.page);
updatePages();
}, 300);
}
});
page.addEventListener('mouseleave', () => {
clearTimeout(hoverTimer);
});
});
// Touch swipe detection (removed for mobile as per user request)
// The following touch event listeners and related variables are removed.
// Navigation on mobile will now be handled by clicking on the cards.
// Handle window resize to switch between mobile/desktop layouts
window.addEventListener('resize', updatePages);
// Initialize
updatePages();
// Product details data - will be populated from HTML
const productDetails = {};
// Initialize product details from data attributes
function initProductDetails() {
const productElements = document.querySelectorAll('.flipbook-page');
productElements.forEach(page => {
const pageNum = parseInt(page.dataset.page);
const title = page.querySelector('.product-title').textContent;
const description = page.querySelector('.product-description').textContent;
productDetails[pageNum] = { title, description };
});
}
// Show product details modal
function showProductDetails(pageNum) {
const details = productDetails[pageNum];
if (details) {
document.getElementById('modalTitle').textContent = details.title;
document.getElementById('modalDescription').textContent = details.description;
document.getElementById('productModal').classList.add('show');
}
}
function hideProductDetails() {
document.getElementById('productModal').classList.remove('show');
}
// Close modal when clicking outside
document.getElementById('productModal').addEventListener('click', function(e) {
if (e.target === this) {
hideProductDetails();
}
});
// Initialize
initProductDetails();
updatePages();

View File

@@ -0,0 +1,21 @@
document.addEventListener('DOMContentLoaded', function() {
const currentPath = window.location.pathname;
const supportedLanguages = ['en', 'ru', 'es'];
const browserLang = (navigator.language || navigator.userLanguage).split('-')[0];
// Check if the current path already includes a supported language prefix
const isOnLangPage = currentPath.startsWith('/en/') ||
currentPath.startsWith('/ru/') ||
currentPath.startsWith('/es/');
if (!isOnLangPage) {
// Redirect to the browser's language version if supported, otherwise default to English
const targetLang = supportedLanguages.includes(browserLang) ? browserLang : 'en';
// Construct the new URL, preserving the rest of the path if any
let newPath = currentPath;
if (!currentPath.startsWith('/en/') && !currentPath.startsWith('/ru/') && !currentPath.startsWith('/es/')) {
newPath = '/' + targetLang + currentPath;
}
window.location.replace(newPath);
}
});

80
js/language-switcher.js Normal file
View File

@@ -0,0 +1,80 @@
document.addEventListener('DOMContentLoaded', function() {
const supportedLanguages = {
'en': 'English',
'ru': 'Русский',
'es': 'Español'
};
const switcherContainer = document.createElement('div');
switcherContainer.id = 'language-switcher';
switcherContainer.style.position = 'fixed';
switcherContainer.style.bottom = '20px';
switcherContainer.style.right = '20px';
switcherContainer.style.zIndex = '1000';
switcherContainer.style.cursor = 'pointer';
const currentLang = window.location.pathname.split('/')[1];
const currentLangDisplay = supportedLanguages[currentLang] || 'Language'; // Fallback if not on a supported lang path
switcherContainer.innerHTML = `
<div id="language-icon" style="width: 40px; height: 40px; border-radius: 50%; overflow: hidden; background-color: #007bff; text-align: center; line-height: 40px; font-weight: bold;">
<img src="https://flagcdn.com/w40/${currentLang === 'en' ? 'gb' : currentLang}.png" alt="${currentLang.toUpperCase()}" style="width: 100%; height: 100%; object-fit: cover;">
</div>
<div id="language-options-container" class="language-options-container" style="position: absolute; bottom: 50px; right: 0;">
${Object.keys(supportedLanguages).map(lang => `
<div class="language-option" data-lang="${lang}" style="width: 40px; height: 40px; border-radius: 50%; overflow: hidden; margin-bottom: 10px; cursor: pointer;">
<img src="https://flagcdn.com/w40/${lang === 'en' ? 'gb' : lang}.png" alt="${supportedLanguages[lang]}" style="width: 100%; height: 100%; object-fit: cover;">
</div>
`).join('')}
</div>
`;
document.body.appendChild(switcherContainer);
const languageIcon = document.getElementById('language-icon');
const languageOptionsContainer = document.getElementById('language-options-container');
languageIcon.addEventListener('click', function(event) {
// Close any other open language switchers first
document.querySelectorAll('.language-options-container.show').forEach(el => {
if (el !== languageOptionsContainer) {
el.classList.remove('show');
}
});
// Toggle current switcher
languageOptionsContainer.classList.toggle('show');
// Prevent event bubbling and default behavior
event.stopPropagation();
event.preventDefault();
});
document.addEventListener('click', function(event) {
if (!switcherContainer.contains(event.target)) {
languageOptionsContainer.classList.remove('show');
}
});
// Close language switcher when other modals open
document.addEventListener('modalOpened', function() {
languageOptionsContainer.classList.remove('show');
});
languageOptionsContainer.querySelectorAll('.language-option').forEach(item => {
item.addEventListener('click', function() {
const lang = this.getAttribute('data-lang');
const currentPathname = window.location.pathname;
const pathParts = currentPathname.split('/').filter(part => part !== '');
// Remove existing language prefix if present
if (supportedLanguages[pathParts[0]]) {
pathParts.shift();
}
// Construct new path with selected language
const newPath = '/' + lang + '/' + pathParts.join('/');
window.location.href = newPath;
});
});
});