533 lines
19 KiB
JavaScript
533 lines
19 KiB
JavaScript
/**
|
||
* Phantom Protocol - Главный JavaScript файл
|
||
* © NeroWorld AI, 2025
|
||
*/
|
||
|
||
(function($) {
|
||
'use strict';
|
||
|
||
// ========================================
|
||
// Инициализация при загрузке DOM
|
||
// ========================================
|
||
$(document).ready(function() {
|
||
initTheme();
|
||
initNavbar();
|
||
initAOS();
|
||
initCopyButtons();
|
||
// initHeroBackground(); // ОТКЛЮЧЕНО - используется общий background.js для всех страниц
|
||
initArchitectureAnimation();
|
||
initDemoVisualization();
|
||
initComparisonTable();
|
||
initSmoothScroll();
|
||
});
|
||
|
||
// ========================================
|
||
// Система тем (Dark/Light)
|
||
// ========================================
|
||
function initTheme() {
|
||
const themeToggle = $('#themeToggle');
|
||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||
const savedTheme = localStorage.getItem('phantom-theme') || (prefersDark ? 'dark' : 'light');
|
||
|
||
setTheme(savedTheme);
|
||
|
||
themeToggle.on('click', function() {
|
||
const currentTheme = $('body').attr('data-theme');
|
||
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
||
setTheme(newTheme);
|
||
localStorage.setItem('phantom-theme', newTheme);
|
||
});
|
||
}
|
||
|
||
function setTheme(theme) {
|
||
$('body').attr('data-theme', theme);
|
||
const icon = theme === 'dark' ? 'fa-sun' : 'fa-moon';
|
||
$('#themeToggle i').removeClass('fa-moon fa-sun').addClass(icon);
|
||
}
|
||
|
||
// ========================================
|
||
// Навигация (прозрачность при скролле)
|
||
// ========================================
|
||
function initNavbar() {
|
||
$(window).on('scroll', function() {
|
||
const navbar = $('.navbar');
|
||
if ($(window).scrollTop() > 100) {
|
||
navbar.addClass('glass-nav');
|
||
} else {
|
||
navbar.removeClass('glass-nav');
|
||
}
|
||
});
|
||
}
|
||
|
||
// ========================================
|
||
// Инициализация AOS (Animate On Scroll)
|
||
// ========================================
|
||
function initAOS() {
|
||
if (typeof AOS !== 'undefined') {
|
||
AOS.init({
|
||
duration: 800,
|
||
easing: 'ease-out-cubic',
|
||
once: true,
|
||
offset: 100,
|
||
disable: 'mobile'
|
||
});
|
||
}
|
||
}
|
||
|
||
// ========================================
|
||
// Копирование команд в буфер обмена
|
||
// ========================================
|
||
function initCopyButtons() {
|
||
$('.copy-btn').on('click', function() {
|
||
const targetId = $(this).data('target');
|
||
const codeElement = $('#' + targetId);
|
||
const text = codeElement.text();
|
||
|
||
navigator.clipboard.writeText(text).then(function() {
|
||
showToast();
|
||
|
||
// Визуальная обратная связь
|
||
const btn = $(this);
|
||
const originalText = btn.html();
|
||
btn.html('<i class="fas fa-check"></i> Скопировано!');
|
||
|
||
setTimeout(function() {
|
||
btn.html(originalText);
|
||
}.bind(this), 2000);
|
||
}.bind(this), function(err) {
|
||
console.error('Ошибка копирования: ', err);
|
||
});
|
||
});
|
||
}
|
||
|
||
function showToast() {
|
||
const toast = new bootstrap.Toast($('#copyToast')[0]);
|
||
toast.show();
|
||
}
|
||
|
||
// ========================================
|
||
// Hero Background - Анимированная сеть узлов
|
||
// ========================================
|
||
function initHeroBackground() {
|
||
const container = $('#heroBackground');
|
||
if (container.length === 0) return;
|
||
|
||
const canvas = $('<canvas></canvas>');
|
||
container.append(canvas);
|
||
const ctx = canvas[0].getContext('2d');
|
||
|
||
// Установка размеров canvas
|
||
function resizeCanvas() {
|
||
canvas[0].width = container.width();
|
||
canvas[0].height = container.height();
|
||
}
|
||
resizeCanvas();
|
||
$(window).on('resize', resizeCanvas);
|
||
|
||
// Создание узлов сети
|
||
const nodes = [];
|
||
const nodeCount = 50;
|
||
const connectionDistance = 150;
|
||
|
||
class Node {
|
||
constructor() {
|
||
this.x = Math.random() * canvas[0].width;
|
||
this.y = Math.random() * canvas[0].height;
|
||
this.vx = (Math.random() - 0.5) * 0.5;
|
||
this.vy = (Math.random() - 0.5) * 0.5;
|
||
this.radius = Math.random() * 2 + 1;
|
||
this.opacity = Math.random() * 0.5 + 0.5;
|
||
}
|
||
|
||
update() {
|
||
this.x += this.vx;
|
||
this.y += this.vy;
|
||
|
||
// Отражение от границ
|
||
if (this.x < 0 || this.x > canvas[0].width) this.vx *= -1;
|
||
if (this.y < 0 || this.y > canvas[0].height) this.vy *= -1;
|
||
}
|
||
|
||
draw() {
|
||
ctx.beginPath();
|
||
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
|
||
ctx.fillStyle = `rgba(0, 240, 255, ${this.opacity})`;
|
||
ctx.fill();
|
||
}
|
||
}
|
||
|
||
// Создание узлов
|
||
for (let i = 0; i < nodeCount; i++) {
|
||
nodes.push(new Node());
|
||
}
|
||
|
||
// Анимационный цикл
|
||
function animate() {
|
||
ctx.clearRect(0, 0, canvas[0].width, canvas[0].height);
|
||
|
||
// Обновление и отрисовка узлов
|
||
nodes.forEach(node => {
|
||
node.update();
|
||
node.draw();
|
||
});
|
||
|
||
// Рисование соединений между близкими узлами
|
||
for (let i = 0; i < nodes.length; i++) {
|
||
for (let j = i + 1; j < nodes.length; j++) {
|
||
const dx = nodes[i].x - nodes[j].x;
|
||
const dy = nodes[i].y - nodes[j].y;
|
||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||
|
||
if (distance < connectionDistance) {
|
||
const opacity = (1 - distance / connectionDistance) * 0.3;
|
||
ctx.beginPath();
|
||
ctx.moveTo(nodes[i].x, nodes[i].y);
|
||
ctx.lineTo(nodes[j].x, nodes[j].y);
|
||
ctx.strokeStyle = `rgba(122, 62, 255, ${opacity})`;
|
||
ctx.lineWidth = 1;
|
||
ctx.stroke();
|
||
}
|
||
}
|
||
}
|
||
|
||
requestAnimationFrame(animate);
|
||
}
|
||
|
||
animate();
|
||
}
|
||
|
||
// ========================================
|
||
// Анимация архитектурных слоев
|
||
// ========================================
|
||
function initArchitectureAnimation() {
|
||
const layers = $('.arch-layer');
|
||
|
||
layers.on('mouseenter', function() {
|
||
const layer = $(this);
|
||
layer.css('transform', 'translateX(15px)');
|
||
|
||
// Пульсация номера слоя
|
||
const number = layer.find('.layer-number');
|
||
number.css('transform', 'scale(1.1)');
|
||
});
|
||
|
||
layers.on('mouseleave', function() {
|
||
const layer = $(this);
|
||
layer.css('transform', '');
|
||
|
||
const number = layer.find('.layer-number');
|
||
number.css('transform', '');
|
||
});
|
||
|
||
// Последовательная анимация при загрузке
|
||
layers.each(function(index) {
|
||
const layer = $(this);
|
||
setTimeout(function() {
|
||
layer.addClass('aos-animate');
|
||
}, index * 100);
|
||
});
|
||
}
|
||
|
||
// ========================================
|
||
// Таблица сравнения - развернуть/свернуть
|
||
// ========================================
|
||
function initComparisonTable() {
|
||
$('#expandComparison').on('click', function() {
|
||
const btn = $(this);
|
||
const isExpanded = btn.data('expanded');
|
||
|
||
if (!isExpanded) {
|
||
// Здесь можно добавить дополнительные строки
|
||
btn.html('<i class="fas fa-compress"></i> Свернуть таблицу');
|
||
btn.data('expanded', true);
|
||
} else {
|
||
btn.html('<i class="fas fa-expand"></i> Развернуть полное сравнение');
|
||
btn.data('expanded', false);
|
||
}
|
||
});
|
||
}
|
||
|
||
// ========================================
|
||
// Демо визуализация - построение анонимного пути
|
||
// ========================================
|
||
function initDemoVisualization() {
|
||
const canvas = $('#demoCanvas');
|
||
if (canvas.length === 0) return;
|
||
|
||
const ctx = canvas[0].getContext('2d');
|
||
const width = canvas[0].width;
|
||
const height = canvas[0].height;
|
||
|
||
let nodes = [];
|
||
let path = [];
|
||
let animationFrame = 0;
|
||
let isAnimating = false;
|
||
|
||
// Создание сети узлов
|
||
function createNodes() {
|
||
nodes = [];
|
||
const nodeCount = 20;
|
||
|
||
for (let i = 0; i < nodeCount; i++) {
|
||
nodes.push({
|
||
id: generatePhantomId(),
|
||
x: Math.random() * (width - 100) + 50,
|
||
y: Math.random() * (height - 100) + 50,
|
||
radius: 8,
|
||
color: '#9999b8'
|
||
});
|
||
}
|
||
|
||
drawNetwork();
|
||
}
|
||
|
||
// Генерация фантомного ID
|
||
function generatePhantomId() {
|
||
const chars = 'ABCDEF0123456789';
|
||
let id = '';
|
||
for (let i = 0; i < 8; i++) {
|
||
id += chars.charAt(Math.floor(Math.random() * chars.length));
|
||
}
|
||
return id;
|
||
}
|
||
|
||
// Отрисовка сети
|
||
function drawNetwork() {
|
||
ctx.clearRect(0, 0, width, height);
|
||
|
||
// Рисование соединений
|
||
ctx.strokeStyle = 'rgba(153, 153, 184, 0.1)';
|
||
ctx.lineWidth = 1;
|
||
for (let i = 0; i < nodes.length; i++) {
|
||
for (let j = i + 1; j < nodes.length; j++) {
|
||
const dx = nodes[i].x - nodes[j].x;
|
||
const dy = nodes[i].y - nodes[j].y;
|
||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||
|
||
if (distance < 200) {
|
||
ctx.beginPath();
|
||
ctx.moveTo(nodes[i].x, nodes[i].y);
|
||
ctx.lineTo(nodes[j].x, nodes[j].y);
|
||
ctx.stroke();
|
||
}
|
||
}
|
||
}
|
||
|
||
// Рисование узлов
|
||
nodes.forEach(node => {
|
||
ctx.beginPath();
|
||
ctx.arc(node.x, node.y, node.radius, 0, Math.PI * 2);
|
||
ctx.fillStyle = node.color;
|
||
ctx.fill();
|
||
ctx.strokeStyle = 'rgba(0, 240, 255, 0.3)';
|
||
ctx.lineWidth = 2;
|
||
ctx.stroke();
|
||
|
||
// ID узла
|
||
ctx.fillStyle = '#e6e6ff';
|
||
ctx.font = '10px JetBrains Mono';
|
||
ctx.fillText(node.id, node.x - 25, node.y - 15);
|
||
});
|
||
}
|
||
|
||
// Построение анонимного пути
|
||
function buildPath() {
|
||
path = [];
|
||
const pathLength = Math.floor(Math.random() * 3) + 4; // 4-6 узлов
|
||
const usedIndices = new Set();
|
||
|
||
for (let i = 0; i < pathLength; i++) {
|
||
let index;
|
||
do {
|
||
index = Math.floor(Math.random() * nodes.length);
|
||
} while (usedIndices.has(index));
|
||
|
||
usedIndices.add(index);
|
||
path.push(nodes[index]);
|
||
}
|
||
}
|
||
|
||
// Анимация пути
|
||
function animatePath() {
|
||
if (!isAnimating) return;
|
||
|
||
const currentNode = Math.floor(animationFrame / 20);
|
||
|
||
if (currentNode >= path.length) {
|
||
isAnimating = false;
|
||
updateStats('complete', path.length);
|
||
return;
|
||
}
|
||
|
||
drawNetwork();
|
||
|
||
// Подсветка пройденных узлов
|
||
for (let i = 0; i <= currentNode && i < path.length; i++) {
|
||
const node = path[i];
|
||
|
||
// Пульсирующий узел
|
||
const scale = i === currentNode ? 1 + Math.sin(animationFrame * 0.2) * 0.3 : 1;
|
||
|
||
ctx.beginPath();
|
||
ctx.arc(node.x, node.y, node.radius * scale, 0, Math.PI * 2);
|
||
ctx.fillStyle = i === currentNode ? '#00f0ff' : '#7a3eff';
|
||
ctx.fill();
|
||
ctx.strokeStyle = '#00f0ff';
|
||
ctx.lineWidth = 3;
|
||
ctx.stroke();
|
||
|
||
// Соединения пути
|
||
if (i > 0) {
|
||
const prevNode = path[i - 1];
|
||
ctx.beginPath();
|
||
ctx.moveTo(prevNode.x, prevNode.y);
|
||
ctx.lineTo(node.x, node.y);
|
||
ctx.strokeStyle = '#00f0ff';
|
||
ctx.lineWidth = 2;
|
||
ctx.stroke();
|
||
|
||
// Анимация "пакета"
|
||
if (i === currentNode) {
|
||
const progress = (animationFrame % 20) / 20;
|
||
const packetX = prevNode.x + (node.x - prevNode.x) * progress;
|
||
const packetY = prevNode.y + (node.y - prevNode.y) * progress;
|
||
|
||
ctx.beginPath();
|
||
ctx.arc(packetX, packetY, 5, 0, Math.PI * 2);
|
||
ctx.fillStyle = '#00ff9d';
|
||
ctx.fill();
|
||
}
|
||
}
|
||
}
|
||
|
||
animationFrame++;
|
||
updateStats('progress', currentNode + 1);
|
||
requestAnimationFrame(animatePath);
|
||
}
|
||
|
||
// Обновление статистики
|
||
function updateStats(status, pathLength) {
|
||
if (status === 'progress') {
|
||
$('#pathLength').text(`${pathLength} / ${path.length} узлов`);
|
||
$('#entropy').text(`${(Math.log2(nodes.length * pathLength)).toFixed(2)} бит`);
|
||
$('#latency').text(`${(pathLength * 85 + Math.random() * 50).toFixed(0)} мс`);
|
||
$('#status').text('Передача...');
|
||
} else if (status === 'complete') {
|
||
$('#pathLength').text(`${pathLength} узлов`);
|
||
$('#entropy').text(`${(Math.log2(nodes.length * pathLength)).toFixed(2)} бит`);
|
||
$('#latency').text(`${(pathLength * 85).toFixed(0)} мс`);
|
||
$('#status').text('Доставлено').css('color', '#00ff9d');
|
||
} else {
|
||
$('#pathLength').text('—');
|
||
$('#entropy').text('—');
|
||
$('#latency').text('—');
|
||
$('#status').text('Готов').css('color', '');
|
||
}
|
||
}
|
||
|
||
// Кнопки управления
|
||
$('#startDemo').on('click', function() {
|
||
if (isAnimating) return;
|
||
|
||
isAnimating = true;
|
||
animationFrame = 0;
|
||
buildPath();
|
||
animatePath();
|
||
|
||
$(this).prop('disabled', true);
|
||
setTimeout(() => $(this).prop('disabled', false), 6000);
|
||
});
|
||
|
||
$('#resetDemo').on('click', function() {
|
||
isAnimating = false;
|
||
animationFrame = 0;
|
||
path = [];
|
||
createNodes();
|
||
updateStats('reset');
|
||
});
|
||
|
||
// Инициализация
|
||
createNodes();
|
||
}
|
||
|
||
// ========================================
|
||
// Плавная прокрутка к якорям
|
||
// ========================================
|
||
function initSmoothScroll() {
|
||
$('a[href^="#"]').on('click', function(e) {
|
||
const target = $(this.hash);
|
||
|
||
if (target.length) {
|
||
e.preventDefault();
|
||
|
||
$('html, body').animate({
|
||
scrollTop: target.offset().top - 80
|
||
}, 800, 'swing');
|
||
|
||
// Закрыть мобильное меню
|
||
$('.navbar-collapse').collapse('hide');
|
||
}
|
||
});
|
||
|
||
// Scroll indicator
|
||
$('.scroll-indicator').on('click', function() {
|
||
$('html, body').animate({
|
||
scrollTop: $('#usp').offset().top - 80
|
||
}, 800, 'swing');
|
||
});
|
||
}
|
||
|
||
// ========================================
|
||
// Parallax эффект для Hero секции
|
||
// ========================================
|
||
$(window).on('scroll', function() {
|
||
const scrolled = $(window).scrollTop();
|
||
const heroContent = $('.hero-content');
|
||
|
||
if (scrolled < window.innerHeight) {
|
||
heroContent.css('transform', `translateY(${scrolled * 0.5}px)`);
|
||
heroContent.css('opacity', 1 - scrolled / 600);
|
||
}
|
||
});
|
||
|
||
// ========================================
|
||
// Ленивая загрузка и оптимизация
|
||
// ========================================
|
||
if ('IntersectionObserver' in window) {
|
||
const observer = new IntersectionObserver((entries) => {
|
||
entries.forEach(entry => {
|
||
if (entry.isIntersecting) {
|
||
const img = entry.target;
|
||
if (img.dataset.src) {
|
||
img.src = img.dataset.src;
|
||
img.removeAttribute('data-src');
|
||
}
|
||
observer.unobserve(img);
|
||
}
|
||
});
|
||
});
|
||
|
||
document.querySelectorAll('img[data-src]').forEach(img => {
|
||
observer.observe(img);
|
||
});
|
||
}
|
||
|
||
// ========================================
|
||
// Прелоадер (опционально)
|
||
// ========================================
|
||
$(window).on('load', function() {
|
||
$('body').addClass('loaded');
|
||
|
||
// Trigger AOS refresh
|
||
if (typeof AOS !== 'undefined') {
|
||
AOS.refresh();
|
||
}
|
||
});
|
||
|
||
// ========================================
|
||
// Отладка: вывод версии
|
||
// ========================================
|
||
console.log('%c🔮 Phantom Protocol v2025', 'color: #00f0ff; font-size: 16px; font-weight: bold;');
|
||
console.log('%cДобро пожаловать в анонимную сеть будущего', 'color: #7a3eff; font-size: 12px;');
|
||
|
||
})(jQuery); |