unified: Phantom Protocol 2025 complete archive integration
This commit is contained in:
237
website/js/background.js
Normal file
237
website/js/background.js
Normal file
@@ -0,0 +1,237 @@
|
||||
/* ==========================================
|
||||
Phantom Protocol - Advanced Background Animation
|
||||
DHT Network visualization with nodes and connections
|
||||
Based on Hero section animation
|
||||
© NeroWorld AI, 2025
|
||||
========================================== */
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// Конфигурация
|
||||
const config = {
|
||||
nodeCount: 60, // Количество узлов DHT
|
||||
connectionDistance: 180, // Расстояние для связей
|
||||
nodeSpeedMin: 0.2, // Минимальная скорость
|
||||
nodeSpeedMax: 0.8, // Максимальная скорость
|
||||
nodeRadiusMin: 1.5, // Минимальный размер узла
|
||||
nodeRadiusMax: 3, // Максимальный размер узла
|
||||
colors: {
|
||||
node: 'rgba(0, 240, 255, ', // Цвет узлов (cyan) + opacity
|
||||
connection: 'rgba(122, 62, 255, ', // Цвет связей (purple) + opacity
|
||||
glow: 'rgba(0, 240, 255, 0.5)' // Цвет свечения
|
||||
}
|
||||
};
|
||||
|
||||
// Класс узла DHT сети
|
||||
class DHTNode {
|
||||
constructor(canvas) {
|
||||
this.canvas = canvas;
|
||||
this.reset();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.x = Math.random() * this.canvas.width;
|
||||
this.y = Math.random() * this.canvas.height;
|
||||
this.vx = (Math.random() - 0.5) * (config.nodeSpeedMin + Math.random() * (config.nodeSpeedMax - config.nodeSpeedMin));
|
||||
this.vy = (Math.random() - 0.5) * (config.nodeSpeedMin + Math.random() * (config.nodeSpeedMax - config.nodeSpeedMin));
|
||||
this.radius = config.nodeRadiusMin + Math.random() * (config.nodeRadiusMax - config.nodeRadiusMin);
|
||||
this.opacity = Math.random() * 0.5 + 0.5; // 0.5 - 1.0
|
||||
}
|
||||
|
||||
update() {
|
||||
this.x += this.vx;
|
||||
this.y += this.vy;
|
||||
|
||||
// Отскок от границ
|
||||
if (this.x < 0 || this.x > this.canvas.width) {
|
||||
this.vx *= -1;
|
||||
this.x = Math.max(0, Math.min(this.canvas.width, this.x));
|
||||
}
|
||||
if (this.y < 0 || this.y > this.canvas.height) {
|
||||
this.vy *= -1;
|
||||
this.y = Math.max(0, Math.min(this.canvas.height, this.y));
|
||||
}
|
||||
}
|
||||
|
||||
draw(ctx) {
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
|
||||
ctx.fillStyle = config.colors.node + this.opacity + ')';
|
||||
ctx.fill();
|
||||
|
||||
// Добавляем свечение для больших узлов
|
||||
if (this.radius > 2) {
|
||||
ctx.shadowBlur = 15;
|
||||
ctx.shadowColor = config.colors.glow;
|
||||
ctx.fill();
|
||||
ctx.shadowBlur = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Класс анимации DHT сети
|
||||
class DHTNetworkAnimation {
|
||||
constructor(canvasId) {
|
||||
this.canvas = document.getElementById(canvasId);
|
||||
if (!this.canvas) {
|
||||
console.warn('Canvas element not found:', canvasId);
|
||||
return;
|
||||
}
|
||||
|
||||
this.ctx = this.canvas.getContext('2d');
|
||||
this.nodes = [];
|
||||
this.animationId = null;
|
||||
this.resizeTimeout = null;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
// Установка размера canvas
|
||||
this.resize();
|
||||
|
||||
// Создание узлов DHT
|
||||
for (let i = 0; i < config.nodeCount; i++) {
|
||||
this.nodes.push(new DHTNode(this.canvas));
|
||||
}
|
||||
|
||||
// Обработчик изменения размера окна с throttling
|
||||
window.addEventListener('resize', () => {
|
||||
if (this.resizeTimeout) clearTimeout(this.resizeTimeout);
|
||||
this.resizeTimeout = setTimeout(() => this.resize(), 150);
|
||||
});
|
||||
|
||||
// Запуск анимации
|
||||
this.animate();
|
||||
}
|
||||
|
||||
resize() {
|
||||
if (!this.canvas) return;
|
||||
|
||||
const newWidth = window.innerWidth;
|
||||
const newHeight = Math.max(
|
||||
document.documentElement.scrollHeight,
|
||||
document.body.scrollHeight,
|
||||
document.documentElement.clientHeight,
|
||||
window.innerHeight
|
||||
);
|
||||
|
||||
// Обновляем только если размер изменился
|
||||
if (this.canvas.width !== newWidth || this.canvas.height !== newHeight) {
|
||||
this.canvas.width = newWidth;
|
||||
this.canvas.height = newHeight;
|
||||
|
||||
// Переинициализируем узлы при большом изменении размера
|
||||
if (Math.abs(this.canvas.width - newWidth) > 100) {
|
||||
this.nodes.forEach(node => node.reset());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawConnections() {
|
||||
const { connectionDistance, colors } = config;
|
||||
|
||||
for (let i = 0; i < this.nodes.length; i++) {
|
||||
for (let j = i + 1; j < this.nodes.length; j++) {
|
||||
const dx = this.nodes[i].x - this.nodes[j].x;
|
||||
const dy = this.nodes[i].y - this.nodes[j].y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < connectionDistance) {
|
||||
// Opacity зависит от расстояния (ближе = ярче)
|
||||
const opacity = (1 - distance / connectionDistance) * 0.4;
|
||||
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(this.nodes[i].x, this.nodes[i].y);
|
||||
this.ctx.lineTo(this.nodes[j].x, this.nodes[j].y);
|
||||
this.ctx.strokeStyle = colors.connection + opacity + ')';
|
||||
this.ctx.lineWidth = 1;
|
||||
this.ctx.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
animate() {
|
||||
if (!this.canvas || !this.ctx) return;
|
||||
|
||||
// Очистка canvas
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
// Рисуем связи первыми (за узлами)
|
||||
this.drawConnections();
|
||||
|
||||
// Обновление и отрисовка узлов
|
||||
this.nodes.forEach(node => {
|
||||
node.update();
|
||||
node.draw(this.ctx);
|
||||
});
|
||||
|
||||
// Следующий кадр
|
||||
this.animationId = requestAnimationFrame(() => this.animate());
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.animationId) {
|
||||
cancelAnimationFrame(this.animationId);
|
||||
this.animationId = null;
|
||||
}
|
||||
if (this.resizeTimeout) {
|
||||
clearTimeout(this.resizeTimeout);
|
||||
}
|
||||
this.nodes = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Автоматическая инициализация при загрузке
|
||||
function initBackground() {
|
||||
// Проверяем что DOM загружен
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', startAnimation);
|
||||
} else {
|
||||
startAnimation();
|
||||
}
|
||||
|
||||
function startAnimation() {
|
||||
// Ждем полной загрузки страницы
|
||||
if (document.readyState === 'complete') {
|
||||
createAnimation();
|
||||
} else {
|
||||
window.addEventListener('load', createAnimation);
|
||||
}
|
||||
}
|
||||
|
||||
function createAnimation() {
|
||||
setTimeout(() => {
|
||||
// Создаем анимацию
|
||||
window.phantomBackground = new DHTNetworkAnimation('phantomBackground');
|
||||
|
||||
// Отслеживание изменений DOM (с throttling)
|
||||
if (window.phantomBackground) {
|
||||
let mutationTimeout;
|
||||
const observer = new MutationObserver(() => {
|
||||
if (mutationTimeout) clearTimeout(mutationTimeout);
|
||||
mutationTimeout = setTimeout(() => {
|
||||
if (window.phantomBackground && typeof window.phantomBackground.resize === 'function') {
|
||||
window.phantomBackground.resize();
|
||||
}
|
||||
}, 1000); // Задержка 1 секунда для стабильности
|
||||
});
|
||||
|
||||
// Наблюдаем за изменениями структуры страницы
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: false
|
||||
});
|
||||
}
|
||||
|
||||
console.log('✨ DHT Network Background initialized');
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
// Запуск
|
||||
initBackground();
|
||||
|
||||
})();
|
||||
533
website/js/main.js
Normal file
533
website/js/main.js
Normal file
@@ -0,0 +1,533 @@
|
||||
/**
|
||||
* 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);
|
||||
Reference in New Issue
Block a user