Files
Phantom/landing/src/js/main.js
Phantom Release 87820c0e58 feat: extract landing to separate service with Docker + status badge
- landing/ structure with src/, Dockerfile, nginx.conf
- docker-compose.landing.yml on port 8080:80
- status.js with build status badge (CI, commit, issues)
- api/status.json fallback
- Glassmorphism cyberpunk styling matching existing design
2026-05-18 18:48:41 +01:00

533 lines
19 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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);