Initial commit: MSPE website - full site with admin panel, API, and public pages

This commit is contained in:
Krikorios
2026-02-22 02:24:15 +02:00
commit 7a90e63301
106 changed files with 477087 additions and 0 deletions
+472
View File
@@ -0,0 +1,472 @@
/* ========================================
MSPE Ultimate UI Interactions v2.0
Advanced JavaScript for Premium UX
======================================== */
document.addEventListener('DOMContentLoaded', function() {
// Initialize all modules
initCustomCursor();
initParticles();
initScrollProgress();
initMagneticButtons();
initSmoothScroll();
initParallax();
initRevealAnimations();
initTiltEffect();
initRippleEffect();
initTypewriter();
initLazyLoad();
});
/* ==================== Optimized Custom Cursor ==================== */
function initCustomCursor() {
// Custom cursor disabled
return;
// Only on desktop with no reduced motion preference
if (window.innerWidth < 768 || 'ontouchstart' in window || window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
// Add class to body to hide default cursor
document.body.classList.add('custom-cursor-enabled');
const cursor = document.createElement('div');
cursor.classList.add('custom-cursor');
document.body.appendChild(cursor);
const trails = [];
const trailLength = 5; // Reduced from 12
for (let i = 0; i < trailLength; i++) {
const trail = document.createElement('div');
trail.classList.add('cursor-trail');
trail.style.opacity = (1 - i / trailLength) * 0.4;
const size = 6 - i;
trail.style.width = size + 'px';
trail.style.height = size + 'px';
document.body.appendChild(trail);
trails.push({ element: trail, x: 0, y: 0 });
}
let mouseX = 0, mouseY = 0;
let cursorX = 0, cursorY = 0;
let isMoving = false;
let rafId = null;
document.addEventListener('mousemove', (e) => {
mouseX = e.clientX;
mouseY = e.clientY;
if (!isMoving) {
isMoving = true;
rafId = requestAnimationFrame(animateCursor);
}
}, { passive: true });
function animateCursor() {
cursorX += (mouseX - cursorX) * 0.15;
cursorY += (mouseY - cursorY) * 0.15;
cursor.style.transform = `translate3d(${cursorX}px, ${cursorY}px, 0) translate(-50%, -50%)`;
trails.forEach((trail, index) => {
const prev = trails[index - 1] || { x: cursorX, y: cursorY };
trail.x += (prev.x - trail.x) * 0.25;
trail.y += (prev.y - trail.y) * 0.25;
trail.element.style.transform = `translate3d(${trail.x}px, ${trail.y}px, 0)`;
});
// Stop loop when cursor catches up
if (Math.abs(mouseX - cursorX) > 0.5 || Math.abs(mouseY - cursorY) > 0.5) {
rafId = requestAnimationFrame(animateCursor);
} else {
isMoving = false;
}
}
// Delegate hover detection instead of per-element listeners
document.addEventListener('mouseover', (e) => {
if (e.target.closest('a, button, .btn, input, textarea, select, .service-card, .news-card, .portfolio-item, .nav-link')) {
cursor.classList.add('active');
}
}, { passive: true });
document.addEventListener('mouseout', (e) => {
if (e.target.closest('a, button, .btn, input, textarea, select, .service-card, .news-card, .portfolio-item, .nav-link')) {
cursor.classList.remove('active');
}
}, { passive: true });
}
/* ==================== Optimized Particle Background ==================== */
function initParticles() {
// Particles disabled for performance — 20 animated DOM elements cause compositor overhead
return;
const particlesContainer = document.createElement('div');
particlesContainer.classList.add('particles-bg');
document.body.appendChild(particlesContainer);
// Drastically reduced: 20 on desktop, 10 on mobile (was 60/25)
const particleCount = window.innerWidth < 768 ? 10 : 20;
for (let i = 0; i < particleCount; i++) {
const particle = document.createElement('div');
particle.classList.add('particle');
particle.style.left = Math.random() * 100 + '%';
particle.style.animationDelay = Math.random() * 20 + 's';
particle.style.animationDuration = (25 + Math.random() * 15) + 's';
const size = 2 + Math.random() * 2;
particle.style.width = size + 'px';
particle.style.height = size + 'px';
particlesContainer.appendChild(particle);
}
// Geometric shapes removed for performance
}
/* ==================== Enhanced Scroll Progress Indicator ==================== */
function initScrollProgress() {
const progressBar = document.createElement('div');
progressBar.classList.add('scroll-progress');
document.body.appendChild(progressBar);
let ticking = false;
window.addEventListener('scroll', () => {
if (!ticking) {
requestAnimationFrame(() => {
const windowHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;
const scrolled = (window.scrollY / windowHeight) * 100;
progressBar.style.width = scrolled + '%';
ticking = false;
});
ticking = true;
}
});
}
/* ==================== Optimized Magnetic Buttons (disabled for performance) ==================== */
function initMagneticButtons() {
// Magnetic effect disabled — per-element mousemove listeners are expensive
return;
// Only apply to .btn elements, not cards/nav-links (too many listeners)
const magneticElements = document.querySelectorAll('.btn');
magneticElements.forEach(el => {
el.addEventListener('mousemove', (e) => {
const rect = el.getBoundingClientRect();
const x = (e.clientX - rect.left - rect.width / 2) * 0.25;
const y = (e.clientY - rect.top - rect.height / 2) * 0.25;
el.style.transform = `translate3d(${x}px, ${y}px, 0)`;
}, { passive: true });
el.addEventListener('mouseleave', () => {
el.style.transform = '';
}, { passive: true });
});
}
/* ==================== Enhanced Smooth Scroll ==================== */
function initSmoothScroll() {
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
const href = this.getAttribute('href');
if (href === '#') return;
e.preventDefault();
const target = document.querySelector(href);
if (target) {
const headerOffset = 100;
const elementPosition = target.getBoundingClientRect().top;
const offsetPosition = elementPosition + window.pageYOffset - headerOffset;
window.scrollTo({
top: offsetPosition,
behavior: 'smooth'
});
}
});
});
}
/* ==================== Lightweight Parallax (hero only) ==================== */
function initParallax() {
const heroVisual = document.querySelector('.hero-visual');
if (!heroVisual || window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
let ticking = false;
window.addEventListener('scroll', () => {
if (!ticking) {
requestAnimationFrame(() => {
const scrollY = window.scrollY;
if (scrollY < window.innerHeight) {
heroVisual.style.transform = `translate3d(0, ${scrollY * 0.15}px, 0)`;
}
ticking = false;
});
ticking = true;
}
}, { passive: true });
}
/* ==================== Optimized Reveal Animations ==================== */
function initRevealAnimations() {
// Only observe sections, not every individual card (let CSS handle card animations)
const revealElements = document.querySelectorAll('.section');
revealElements.forEach(el => el.classList.add('reveal'));
const revealObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('active');
revealObserver.unobserve(entry.target); // Stop observing once revealed
}
});
}, {
threshold: 0.1,
rootMargin: '0px 0px -30px 0px'
});
revealElements.forEach(el => revealObserver.observe(el));
}
// Counter animation helper
function animateValue(element) {
const parent = element.closest('[data-count]');
if (!parent) return;
const target = parseInt(parent.dataset.count) || 0;
const duration = 2000;
const start = performance.now();
function update(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(easeOutQuart * target);
element.textContent = current;
if (progress < 1) {
requestAnimationFrame(update);
} else {
element.textContent = target;
}
}
requestAnimationFrame(update);
}
/* ==================== Lightweight 3D Tilt (disabled — duplicate of initCardPointerLift in main.js) ==================== */
function initTiltEffect() {
// Disabled — main.js initCardPointerLift already handles card hover effects
return;
// Only service cards - removed from news, portfolio, feature, philosophy cards
const cards = document.querySelectorAll('.service-card');
cards.forEach(card => {
card.addEventListener('mousemove', (e) => {
const rect = card.getBoundingClientRect();
const rotateX = ((e.clientY - rect.top) - rect.height / 2) / 20;
const rotateY = (rect.width / 2 - (e.clientX - rect.left)) / 20;
card.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) translateZ(5px)`;
}, { passive: true });
card.addEventListener('mouseleave', () => {
card.style.transform = '';
}, { passive: true });
});
}
/* ==================== Ripple Effect (buttons only, no global click) ==================== */
function initRippleEffect() {
// Use event delegation for button ripples
document.addEventListener('click', function(e) {
const btn = e.target.closest('.btn');
if (!btn) return;
const rect = btn.getBoundingClientRect();
const ripple = document.createElement('span');
ripple.classList.add('btn-ripple');
ripple.style.left = (e.clientX - rect.left) + 'px';
ripple.style.top = (e.clientY - rect.top) + 'px';
btn.appendChild(ripple);
setTimeout(() => ripple.remove(), 600);
});
// Global click ripple removed for performance
}
/* ==================== Typewriter Effect ==================== */
function initTypewriter() {
const typingElements = document.querySelectorAll('[data-typing]');
typingElements.forEach(el => {
const text = el.dataset.typing || el.textContent;
const speed = parseInt(el.dataset.typingSpeed) || 50;
el.textContent = '';
el.style.borderRight = '2px solid var(--neon-cyan)';
let charIndex = 0;
const typeInterval = setInterval(() => {
if (charIndex < text.length) {
el.textContent += text.charAt(charIndex);
charIndex++;
} else {
clearInterval(typeInterval);
// Blinking cursor effect
el.style.animation = 'blinkCursor 0.8s infinite';
}
}, speed);
});
// Add blink cursor keyframes
if (!document.querySelector('#blinkCursorKeyframes')) {
const style = document.createElement('style');
style.id = 'blinkCursorKeyframes';
style.textContent = `
@keyframes blinkCursor {
0%, 50% { border-color: var(--neon-cyan); }
51%, 100% { border-color: transparent; }
}
`;
document.head.appendChild(style);
}
}
/* Dynamic Background Gradient - REMOVED for performance */
// The continuous rAF loop updating body background-position was a major perf drain
/* ==================== Text Scramble Effect ==================== */
class TextScramble {
constructor(el) {
this.el = el;
this.chars = '!<>-_\\/[]{}—=+*^?#________';
this.update = this.update.bind(this);
}
setText(newText) {
const oldText = this.el.textContent;
const length = Math.max(oldText.length, newText.length);
const promise = new Promise(resolve => this.resolve = resolve);
this.queue = [];
for (let i = 0; i < length; i++) {
const from = oldText[i] || '';
const to = newText[i] || '';
const start = Math.floor(Math.random() * 40);
const end = start + Math.floor(Math.random() * 40);
this.queue.push({ from, to, start, end });
}
cancelAnimationFrame(this.frameRequest);
this.frame = 0;
this.update();
return promise;
}
update() {
let output = '';
let complete = 0;
for (let i = 0, n = this.queue.length; i < n; i++) {
let { from, to, start, end, char } = this.queue[i];
if (this.frame >= end) {
complete++;
output += to;
} else if (this.frame >= start) {
if (!char || Math.random() < 0.28) {
char = this.randomChar();
this.queue[i].char = char;
}
output += `<span style="color: var(--neon-cyan)">${char}</span>`;
} else {
output += from;
}
}
this.el.innerHTML = output;
if (complete === this.queue.length) {
this.resolve();
} else {
this.frameRequest = requestAnimationFrame(this.update);
this.frame++;
}
}
randomChar() {
return this.chars[Math.floor(Math.random() * this.chars.length)];
}
}
// Initialize text scramble on hover for certain elements
document.querySelectorAll('.logo-text').forEach(el => {
const fx = new TextScramble(el);
const originalText = el.textContent;
el.addEventListener('mouseenter', () => {
fx.setText(originalText);
});
});
/* ==================== Lazy Load Images ==================== */
function initLazyLoad() {
const images = document.querySelectorAll('img[data-src]');
const imageObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.add('loaded');
imageObserver.unobserve(img);
}
});
}, {
rootMargin: '50px 0px'
});
images.forEach(img => imageObserver.observe(img));
}
/* ==================== Performance Optimization ==================== */
// Debounce function for expensive operations
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Throttle function for scroll events
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
/* Loading Screen Enhancement - handled by preloader in main.js */
/* ==================== Console Easter Egg ==================== */
console.log('%c🚀 MSPE - Architects of Digital Resilience', 'color: #00f5ff; font-size: 24px; font-weight: bold; text-shadow: 0 0 10px #00f5ff;');
console.log('%c━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', 'color: #b537f2');
console.log('%c💼 Looking for opportunities? We\'re hiring!', 'color: #b537f2; font-size: 14px;');
console.log('%c🔧 Built with passion, precision, and cutting-edge tech', 'color: #ff006e; font-size: 12px;');
console.log('%c━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', 'color: #b537f2');