Add CSS, JS and asset files (without videos)
Dieser Commit ist enthalten in:
303
js/animations-enhanced.js
Normale Datei
303
js/animations-enhanced.js
Normale Datei
@ -0,0 +1,303 @@
|
||||
/**
|
||||
* Enhanced Animations and Interactions
|
||||
* Premium effects for modern web experience
|
||||
*/
|
||||
|
||||
const EnhancedAnimations = {
|
||||
init() {
|
||||
this.initScrollAnimations();
|
||||
this.initParallaxEffects();
|
||||
this.initMagneticButtons();
|
||||
this.initTextAnimations();
|
||||
this.initCardTilt();
|
||||
this.initSmoothScroll();
|
||||
this.initCursorEffects();
|
||||
this.initNumberCounters();
|
||||
this.initRevealOnScroll();
|
||||
this.initNavbarEffects();
|
||||
},
|
||||
|
||||
// Smooth scroll with easing
|
||||
initSmoothScroll() {
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(this.getAttribute('href'));
|
||||
if (target) {
|
||||
const offset = 100;
|
||||
const targetPosition = target.offsetTop - offset;
|
||||
window.scrollTo({
|
||||
top: targetPosition,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// Parallax scrolling effects
|
||||
initParallaxEffects() {
|
||||
const parallaxElements = document.querySelectorAll('.parallax');
|
||||
let ticking = false;
|
||||
|
||||
function updateParallax() {
|
||||
const scrolled = window.pageYOffset;
|
||||
|
||||
parallaxElements.forEach(element => {
|
||||
const speed = element.dataset.speed || 0.5;
|
||||
const yPos = -(scrolled * speed);
|
||||
element.style.transform = `translateY(${yPos}px)`;
|
||||
});
|
||||
|
||||
ticking = false;
|
||||
}
|
||||
|
||||
function requestTick() {
|
||||
if (!ticking) {
|
||||
window.requestAnimationFrame(updateParallax);
|
||||
ticking = true;
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', requestTick);
|
||||
},
|
||||
|
||||
// Magnetic button effects
|
||||
initMagneticButtons() {
|
||||
const magneticButtons = document.querySelectorAll('.primary-button, .secondary-button, .cta-button');
|
||||
|
||||
magneticButtons.forEach(button => {
|
||||
button.addEventListener('mousemove', (e) => {
|
||||
const rect = button.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left - rect.width / 2;
|
||||
const y = e.clientY - rect.top - rect.height / 2;
|
||||
|
||||
button.style.transform = `translate(${x * 0.2}px, ${y * 0.2}px) scale(1.05)`;
|
||||
});
|
||||
|
||||
button.addEventListener('mouseleave', () => {
|
||||
button.style.transform = '';
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// Advanced text animations
|
||||
initTextAnimations() {
|
||||
// Typewriter effect for hero title - DISABLED to prevent duplication
|
||||
// The title already has CSS animations applied
|
||||
|
||||
/* Commented out to fix duplication issue
|
||||
const heroTitle = document.querySelector('.main-title');
|
||||
if (heroTitle && !heroTitle.dataset.animated) {
|
||||
heroTitle.dataset.animated = 'true';
|
||||
const text = heroTitle.textContent;
|
||||
heroTitle.textContent = '';
|
||||
heroTitle.style.opacity = '1';
|
||||
|
||||
let index = 0;
|
||||
const typeWriter = () => {
|
||||
if (index < text.length) {
|
||||
heroTitle.textContent += text.charAt(index);
|
||||
index++;
|
||||
setTimeout(typeWriter, 50);
|
||||
}
|
||||
};
|
||||
|
||||
// Start typewriter after a short delay
|
||||
setTimeout(typeWriter, 500);
|
||||
}
|
||||
*/
|
||||
|
||||
// Word-by-word reveal for hero text
|
||||
const heroText = document.querySelector('.hero-text');
|
||||
if (heroText) {
|
||||
const words = heroText.textContent.split(' ');
|
||||
heroText.innerHTML = words.map(word =>
|
||||
`<span class="word-reveal" style="opacity: 0; display: inline-block; transform: translateY(20px); transition: all 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);">${word}</span>`
|
||||
).join(' ');
|
||||
|
||||
const wordSpans = heroText.querySelectorAll('.word-reveal');
|
||||
wordSpans.forEach((word, index) => {
|
||||
setTimeout(() => {
|
||||
word.style.opacity = '1';
|
||||
word.style.transform = 'translateY(0)';
|
||||
}, 1000 + index * 100);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 3D card tilt effect
|
||||
initCardTilt() {
|
||||
const cards = document.querySelectorAll('.tool-card, .value-card, .why-card');
|
||||
|
||||
cards.forEach(card => {
|
||||
card.addEventListener('mousemove', (e) => {
|
||||
const rect = card.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const y = e.clientY - rect.top;
|
||||
|
||||
const centerX = rect.width / 2;
|
||||
const centerY = rect.height / 2;
|
||||
|
||||
const rotateX = (y - centerY) / 10;
|
||||
const rotateY = (centerX - x) / 10;
|
||||
|
||||
card.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) scale(1.02)`;
|
||||
});
|
||||
|
||||
card.addEventListener('mouseleave', () => {
|
||||
card.style.transform = '';
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// Custom cursor effects - DISABLED
|
||||
initCursorEffects() {
|
||||
// Cursor removed as requested
|
||||
return;
|
||||
},
|
||||
|
||||
// Animated number counters
|
||||
initNumberCounters() {
|
||||
const counters = document.querySelectorAll('.indicator-value');
|
||||
const animationDuration = 2000;
|
||||
let hasAnimated = false;
|
||||
|
||||
const animateCounters = () => {
|
||||
if (hasAnimated) return;
|
||||
|
||||
counters.forEach(counter => {
|
||||
const target = parseFloat(counter.dataset.target);
|
||||
const start = 0;
|
||||
const increment = target / (animationDuration / 16);
|
||||
let current = start;
|
||||
|
||||
const updateCounter = () => {
|
||||
current += increment;
|
||||
if (current < target) {
|
||||
counter.textContent = Math.floor(current);
|
||||
requestAnimationFrame(updateCounter);
|
||||
} else {
|
||||
counter.textContent = target % 1 === 0 ? target : target.toFixed(1);
|
||||
}
|
||||
};
|
||||
|
||||
updateCounter();
|
||||
});
|
||||
|
||||
hasAnimated = true;
|
||||
};
|
||||
|
||||
// Trigger on scroll into view
|
||||
const observerOptions = {
|
||||
threshold: 0.5
|
||||
};
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
animateCounters();
|
||||
}
|
||||
});
|
||||
}, observerOptions);
|
||||
|
||||
const indicatorsSection = document.querySelector('.trust-indicators');
|
||||
if (indicatorsSection) {
|
||||
observer.observe(indicatorsSection);
|
||||
}
|
||||
},
|
||||
|
||||
// Reveal elements on scroll
|
||||
initRevealOnScroll() {
|
||||
const revealElements = document.querySelectorAll('.about-panel, .tool-card, .value-card, .why-card, .competency-item');
|
||||
|
||||
revealElements.forEach((element, index) => {
|
||||
element.style.opacity = '0';
|
||||
element.style.transform = 'translateY(50px)';
|
||||
element.style.transition = 'all 0.8s cubic-bezier(0.4, 0, 0.2, 1)';
|
||||
});
|
||||
|
||||
const revealOnScroll = () => {
|
||||
const windowHeight = window.innerHeight;
|
||||
|
||||
revealElements.forEach((element, index) => {
|
||||
const elementTop = element.getBoundingClientRect().top;
|
||||
const elementVisible = 100;
|
||||
|
||||
if (elementTop < windowHeight - elementVisible) {
|
||||
setTimeout(() => {
|
||||
element.style.opacity = '1';
|
||||
element.style.transform = 'translateY(0)';
|
||||
}, index * 50);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', revealOnScroll);
|
||||
revealOnScroll(); // Check on initial load
|
||||
},
|
||||
|
||||
// Scroll-based animations
|
||||
initScrollAnimations() {
|
||||
let lastScrollY = window.scrollY;
|
||||
let ticking = false;
|
||||
|
||||
function updateScrollAnimations() {
|
||||
const scrollY = window.scrollY;
|
||||
const scrollDirection = scrollY > lastScrollY ? 'down' : 'up';
|
||||
|
||||
// Hero parallax
|
||||
const hero = document.querySelector('.hero-content');
|
||||
if (hero) {
|
||||
hero.style.transform = `translateY(${scrollY * 0.5}px)`;
|
||||
hero.style.opacity = 1 - (scrollY / 800);
|
||||
}
|
||||
|
||||
// Video parallax
|
||||
const heroVideos = document.querySelector('.hero-video-container');
|
||||
if (heroVideos) {
|
||||
heroVideos.style.transform = `translateY(${scrollY * 0.3}px) scale(${1 + scrollY * 0.0003})`;
|
||||
}
|
||||
|
||||
lastScrollY = scrollY;
|
||||
ticking = false;
|
||||
}
|
||||
|
||||
function requestTick() {
|
||||
if (!ticking) {
|
||||
window.requestAnimationFrame(updateScrollAnimations);
|
||||
ticking = true;
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', requestTick);
|
||||
},
|
||||
|
||||
// Enhanced navbar effects
|
||||
initNavbarEffects() {
|
||||
const navbar = document.querySelector('.navbar');
|
||||
let lastScrollY = window.scrollY;
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
const scrollY = window.scrollY;
|
||||
|
||||
if (scrollY > 50) {
|
||||
navbar.classList.add('scrolled');
|
||||
} else {
|
||||
navbar.classList.remove('scrolled');
|
||||
}
|
||||
|
||||
// Keep navbar always visible
|
||||
navbar.style.transform = 'translateY(0)';
|
||||
|
||||
lastScrollY = scrollY;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize when DOM is ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => EnhancedAnimations.init());
|
||||
} else {
|
||||
EnhancedAnimations.init();
|
||||
}
|
||||
403
js/animations.js
Normale Datei
403
js/animations.js
Normale Datei
@ -0,0 +1,403 @@
|
||||
/**
|
||||
* Animation module for IntelSight website
|
||||
* Contains all animation logic and visual effects
|
||||
*/
|
||||
|
||||
// Particle Animation System
|
||||
const ParticleAnimation = {
|
||||
canvas: null,
|
||||
ctx: null,
|
||||
particles: [],
|
||||
|
||||
/**
|
||||
* Initialize particle animation
|
||||
*/
|
||||
init() {
|
||||
this.canvas = document.querySelector(SELECTORS.PARTICLE_CANVAS);
|
||||
if (!this.canvas) return;
|
||||
|
||||
this.ctx = this.canvas.getContext('2d');
|
||||
this.resizeCanvas();
|
||||
this.createParticles();
|
||||
this.animate();
|
||||
|
||||
// Handle window resize
|
||||
window.addEventListener('resize', () => this.resizeCanvas());
|
||||
},
|
||||
|
||||
/**
|
||||
* Resize canvas to window size
|
||||
*/
|
||||
resizeCanvas() {
|
||||
this.canvas.width = window.innerWidth;
|
||||
this.canvas.height = window.innerHeight;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create particle objects
|
||||
*/
|
||||
createParticles() {
|
||||
this.particles = [];
|
||||
for (let i = 0; i < CONFIG.ANIMATION.PARTICLE_COUNT; i++) {
|
||||
this.particles.push(new Particle(this.canvas));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Main animation loop
|
||||
*/
|
||||
animate() {
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
// Update and draw particles
|
||||
this.particles.forEach(particle => {
|
||||
particle.update(this.canvas);
|
||||
particle.draw(this.ctx);
|
||||
});
|
||||
|
||||
// Draw connections between particles
|
||||
this.drawConnections();
|
||||
|
||||
requestAnimationFrame(() => this.animate());
|
||||
},
|
||||
|
||||
/**
|
||||
* Draw connections between nearby particles
|
||||
*/
|
||||
drawConnections() {
|
||||
for (let a = 0; a < this.particles.length; a++) {
|
||||
for (let b = a + 1; b < this.particles.length; b++) {
|
||||
const distance = Math.sqrt(
|
||||
Math.pow(this.particles[a].x - this.particles[b].x, 2) +
|
||||
Math.pow(this.particles[a].y - this.particles[b].y, 2)
|
||||
);
|
||||
|
||||
if (distance < CONFIG.ANIMATION.CONNECTION_DISTANCE) {
|
||||
const opacity = 0.15 * (1 - distance / CONFIG.ANIMATION.CONNECTION_DISTANCE);
|
||||
// Use darker blue for better visibility on light background
|
||||
this.ctx.strokeStyle = `rgba(15, 114, 181, ${opacity})`;
|
||||
this.ctx.lineWidth = 1;
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(this.particles[a].x, this.particles[a].y);
|
||||
this.ctx.lineTo(this.particles[b].x, this.particles[b].y);
|
||||
this.ctx.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Particle class for animation
|
||||
*/
|
||||
class Particle {
|
||||
constructor(canvas) {
|
||||
this.x = Math.random() * canvas.width;
|
||||
this.y = Math.random() * canvas.height;
|
||||
this.size = Math.random() * (CONFIG.ANIMATION.PARTICLE_SIZE_MAX - CONFIG.ANIMATION.PARTICLE_SIZE_MIN) + CONFIG.ANIMATION.PARTICLE_SIZE_MIN;
|
||||
this.speedX = (Math.random() - 0.5) * CONFIG.ANIMATION.PARTICLE_SPEED;
|
||||
this.speedY = (Math.random() - 0.5) * CONFIG.ANIMATION.PARTICLE_SPEED;
|
||||
this.opacity = Math.random() * 0.5 + 0.2;
|
||||
}
|
||||
|
||||
update(canvas) {
|
||||
this.x += this.speedX;
|
||||
this.y += this.speedY;
|
||||
|
||||
// Wrap around screen edges
|
||||
if (this.x > canvas.width) this.x = 0;
|
||||
else if (this.x < 0) this.x = canvas.width;
|
||||
|
||||
if (this.y > canvas.height) this.y = 0;
|
||||
else if (this.y < 0) this.y = canvas.height;
|
||||
}
|
||||
|
||||
draw(ctx) {
|
||||
// Use darker blue for better visibility on light background
|
||||
ctx.fillStyle = `rgba(15, 114, 181, ${this.opacity * 0.7})`;
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
// Counter Animation
|
||||
const CounterAnimation = {
|
||||
/**
|
||||
* Animate all counter elements
|
||||
*/
|
||||
animateAll() {
|
||||
const counters = document.querySelectorAll(SELECTORS.INDICATOR_VALUE);
|
||||
counters.forEach(counter => this.animateCounter(counter));
|
||||
},
|
||||
|
||||
/**
|
||||
* Animate a single counter
|
||||
* @param {HTMLElement} counter - Counter element to animate
|
||||
*/
|
||||
animateCounter(counter) {
|
||||
const target = parseFloat(counter.getAttribute(DATA_ATTRS.TARGET));
|
||||
const increment = target / CONFIG.ANIMATION.COUNTER_SPEED;
|
||||
let current = 0;
|
||||
|
||||
const updateCounter = () => {
|
||||
current += increment;
|
||||
|
||||
if (current < target) {
|
||||
counter.innerText = Math.ceil(current);
|
||||
setTimeout(updateCounter, CONFIG.TIMING.COUNTER_UPDATE_INTERVAL);
|
||||
} else {
|
||||
// Set final value with proper formatting
|
||||
if (target === CONFIG.TRUST_INDICATORS.AVAILABILITY) {
|
||||
counter.innerText = target + '%';
|
||||
} else if (target === CONFIG.TRUST_INDICATORS.AUTHORITIES_COUNT) {
|
||||
counter.innerText = target + '+';
|
||||
} else if (target === CONFIG.TRUST_INDICATORS.SUPPORT_HOURS) {
|
||||
counter.innerText = target + '/7';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
updateCounter();
|
||||
}
|
||||
};
|
||||
|
||||
// Scroll Animations
|
||||
const ScrollAnimations = {
|
||||
scrollIndicator: null,
|
||||
|
||||
/**
|
||||
* Initialize scroll-based animations
|
||||
*/
|
||||
init() {
|
||||
this.scrollIndicator = document.querySelector(SELECTORS.SCROLL_INDICATOR);
|
||||
this.setupScrollIndicator();
|
||||
this.setupIntersectionObserver();
|
||||
},
|
||||
|
||||
/**
|
||||
* Setup scroll indicator behavior
|
||||
*/
|
||||
setupScrollIndicator() {
|
||||
if (!this.scrollIndicator) return;
|
||||
|
||||
// Click to scroll to about section
|
||||
this.scrollIndicator.addEventListener('click', () => {
|
||||
const aboutSection = document.querySelector('#about');
|
||||
if (aboutSection) {
|
||||
aboutSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
});
|
||||
|
||||
// Hide/show based on scroll position
|
||||
let scrollTimeout;
|
||||
window.addEventListener('scroll', () => {
|
||||
const hero = document.querySelector(SELECTORS.HERO);
|
||||
|
||||
if (window.scrollY > CONFIG.ANIMATION.SCROLL_THRESHOLD) {
|
||||
if (hero) hero.classList.add(CLASSES.SCROLLED);
|
||||
if (this.scrollIndicator) this.scrollIndicator.style.opacity = '0';
|
||||
} else {
|
||||
if (hero) hero.classList.remove(CLASSES.SCROLLED);
|
||||
if (this.scrollIndicator) this.scrollIndicator.style.opacity = '1';
|
||||
}
|
||||
|
||||
clearTimeout(scrollTimeout);
|
||||
scrollTimeout = setTimeout(() => {
|
||||
if (window.scrollY > CONFIG.ANIMATION.SCROLL_THRESHOLD && this.scrollIndicator) {
|
||||
this.scrollIndicator.style.display = 'none';
|
||||
} else if (this.scrollIndicator) {
|
||||
this.scrollIndicator.style.display = 'flex';
|
||||
}
|
||||
}, CONFIG.TIMING.SCROLL_HIDE_DELAY);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Setup intersection observer for scroll-triggered animations
|
||||
*/
|
||||
setupIntersectionObserver() {
|
||||
const observerOptions = {
|
||||
threshold: CONFIG.OBSERVER.THRESHOLD,
|
||||
rootMargin: CONFIG.OBSERVER.ROOT_MARGIN
|
||||
};
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
// Trust indicators animation
|
||||
if (entry.target.classList.contains('trust-indicators')) {
|
||||
CounterAnimation.animateAll();
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
|
||||
// Timeline animation
|
||||
if (entry.target.classList.contains('timeline')) {
|
||||
const items = entry.target.querySelectorAll('.timeline-item');
|
||||
items.forEach((item, index) => {
|
||||
setTimeout(() => {
|
||||
item.classList.add(CLASSES.VISIBLE);
|
||||
}, index * 300);
|
||||
});
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
|
||||
// Feature nodes animation
|
||||
if (entry.target.classList.contains('feature-nodes')) {
|
||||
const nodes = entry.target.querySelectorAll('.node');
|
||||
nodes.forEach((node, index) => {
|
||||
setTimeout(() => {
|
||||
node.style.opacity = '1';
|
||||
node.style.transform = 'translateY(0)';
|
||||
}, index * 150);
|
||||
});
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, observerOptions);
|
||||
|
||||
// Observe elements
|
||||
const trustIndicators = document.querySelector(SELECTORS.TRUST_INDICATORS);
|
||||
if (trustIndicators) {
|
||||
trustIndicators.style.opacity = '0';
|
||||
observer.observe(trustIndicators);
|
||||
}
|
||||
|
||||
const timeline = document.querySelector('.timeline');
|
||||
if (timeline) observer.observe(timeline);
|
||||
|
||||
const featureNodes = document.querySelector('.feature-nodes');
|
||||
if (featureNodes) {
|
||||
document.querySelectorAll('.node').forEach(node => {
|
||||
node.style.opacity = '0';
|
||||
node.style.transform = 'translateY(30px)';
|
||||
node.style.transition = 'all 0.6s ease';
|
||||
});
|
||||
observer.observe(featureNodes);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Glitch Effect
|
||||
const GlitchEffect = {
|
||||
/**
|
||||
* Apply glitch effect to element on hover
|
||||
* @param {HTMLElement} element - Element to apply effect to
|
||||
*/
|
||||
apply(element) {
|
||||
if (!element) return;
|
||||
|
||||
let glitchInterval;
|
||||
element.addEventListener('mouseenter', () => {
|
||||
let count = 0;
|
||||
glitchInterval = setInterval(() => {
|
||||
element.style.textShadow = `
|
||||
${Math.random() * 5}px ${Math.random() * 5}px 0 rgba(0, 212, 255, 0.5),
|
||||
${Math.random() * -5}px ${Math.random() * 5}px 0 rgba(255, 0, 128, 0.5)
|
||||
`;
|
||||
count++;
|
||||
if (count > CONFIG.ANIMATION.GLITCH_ITERATIONS) {
|
||||
clearInterval(glitchInterval);
|
||||
element.style.textShadow = 'none';
|
||||
}
|
||||
}, CONFIG.ANIMATION.GLITCH_INTERVAL);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Interactive Elements
|
||||
const InteractiveElements = {
|
||||
/**
|
||||
* Initialize all interactive element animations
|
||||
*/
|
||||
init() {
|
||||
this.setupNodeHoverEffects();
|
||||
this.setupWidgetHoverEffects();
|
||||
this.setupInteractiveIcon();
|
||||
},
|
||||
|
||||
/**
|
||||
* Setup hover effects for node elements
|
||||
*/
|
||||
setupNodeHoverEffects() {
|
||||
document.querySelectorAll('.node').forEach(node => {
|
||||
node.addEventListener('mouseenter', function() {
|
||||
const icon = this.querySelector('.node-icon');
|
||||
if (icon) icon.style.transform = 'scale(1.2) rotate(5deg)';
|
||||
});
|
||||
|
||||
node.addEventListener('mouseleave', function() {
|
||||
const icon = this.querySelector('.node-icon');
|
||||
if (icon) icon.style.transform = 'scale(1) rotate(0deg)';
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Setup hover effects for widget elements
|
||||
*/
|
||||
setupWidgetHoverEffects() {
|
||||
document.querySelectorAll('.widget').forEach(widget => {
|
||||
widget.addEventListener('mouseenter', function() {
|
||||
this.style.boxShadow = '0 5px 20px rgba(0, 212, 255, 0.3)';
|
||||
});
|
||||
|
||||
widget.addEventListener('mouseleave', function() {
|
||||
this.style.boxShadow = 'none';
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Setup 3D interactive icon effect
|
||||
*/
|
||||
setupInteractiveIcon() {
|
||||
const icon = document.querySelector(SELECTORS.INTERACTIVE_ICON);
|
||||
if (!icon) return;
|
||||
|
||||
document.addEventListener('mousemove', (e) => {
|
||||
const rect = icon.getBoundingClientRect();
|
||||
const centerX = rect.left + rect.width / 2;
|
||||
const centerY = rect.top + rect.height / 2;
|
||||
|
||||
const mouseX = (e.clientX - centerX) / 20;
|
||||
const mouseY = (e.clientY - centerY) / 20;
|
||||
|
||||
icon.style.transform = `perspective(1000px) rotateY(${mouseX}deg) rotateX(${-mouseY}deg)`;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize all animations
|
||||
const Animations = {
|
||||
/**
|
||||
* Initialize all animation systems
|
||||
*/
|
||||
init() {
|
||||
// Core animations
|
||||
ParticleAnimation.init();
|
||||
ScrollAnimations.init();
|
||||
InteractiveElements.init();
|
||||
|
||||
// Apply glitch effect to main title
|
||||
const mainTitle = document.querySelector('.main-title');
|
||||
if (mainTitle) {
|
||||
GlitchEffect.apply(mainTitle);
|
||||
}
|
||||
|
||||
// Page load animations
|
||||
window.addEventListener('load', () => {
|
||||
document.body.classList.add(CLASSES.LOADED);
|
||||
|
||||
// Fade in hero content
|
||||
setTimeout(() => {
|
||||
const heroContent = document.querySelector(SELECTORS.HERO_CONTENT);
|
||||
if (heroContent) {
|
||||
heroContent.style.opacity = '1';
|
||||
heroContent.style.transform = 'translateY(0)';
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
};
|
||||
499
js/components.js
Normale Datei
499
js/components.js
Normale Datei
@ -0,0 +1,499 @@
|
||||
/**
|
||||
* UI Components module for IntelSight website
|
||||
* Contains all interactive UI component logic
|
||||
*/
|
||||
|
||||
// Language Toggle Component
|
||||
const LanguageToggle = {
|
||||
element: null,
|
||||
|
||||
/**
|
||||
* Initialize language toggle
|
||||
*/
|
||||
init() {
|
||||
this.element = document.querySelector(SELECTORS.LANG_TOGGLE);
|
||||
if (!this.element) return;
|
||||
|
||||
this.element.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.toggle();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle between languages
|
||||
*/
|
||||
toggle() {
|
||||
const newLanguage = getCurrentLanguage() === 'de' ? 'en' : 'de';
|
||||
switchLanguage(newLanguage);
|
||||
|
||||
// Update expand button text after language change
|
||||
ProductShowcase.updateExpandButtonText();
|
||||
}
|
||||
};
|
||||
|
||||
// Navigation Component
|
||||
const Navigation = {
|
||||
navbar: null,
|
||||
|
||||
/**
|
||||
* Initialize navigation component
|
||||
*/
|
||||
init() {
|
||||
this.navbar = document.querySelector(SELECTORS.NAVBAR);
|
||||
this.setupSmoothScrolling();
|
||||
this.setupMobileMenu();
|
||||
},
|
||||
|
||||
/**
|
||||
* Setup smooth scrolling for anchor links
|
||||
*/
|
||||
setupSmoothScrolling() {
|
||||
document.querySelectorAll(SELECTORS.SMOOTH_LINKS).forEach(anchor => {
|
||||
anchor.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
const targetId = this.getAttribute('href');
|
||||
const target = document.querySelector(targetId);
|
||||
|
||||
if (target) {
|
||||
target.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Setup mobile menu functionality
|
||||
*/
|
||||
setupMobileMenu() {
|
||||
// Mobile menu logic would go here if needed
|
||||
// Currently not implemented as per YAGNI principle
|
||||
}
|
||||
};
|
||||
|
||||
// About Section Tabs
|
||||
const AboutTabs = {
|
||||
tabs: null,
|
||||
panels: null,
|
||||
|
||||
/**
|
||||
* Initialize about section tabs
|
||||
*/
|
||||
init() {
|
||||
this.tabs = document.querySelectorAll(SELECTORS.ABOUT_TABS);
|
||||
this.panels = document.querySelectorAll(SELECTORS.ABOUT_PANELS);
|
||||
|
||||
if (!this.tabs.length) return;
|
||||
|
||||
this.tabs.forEach(tab => {
|
||||
tab.addEventListener('click', () => this.switchTab(tab));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Switch to selected tab
|
||||
* @param {HTMLElement} selectedTab - Tab element that was clicked
|
||||
*/
|
||||
switchTab(selectedTab) {
|
||||
const targetPanelId = selectedTab.getAttribute(DATA_ATTRS.TAB);
|
||||
|
||||
// Remove active class from all tabs and panels
|
||||
this.tabs.forEach(tab => tab.classList.remove(CLASSES.ACTIVE));
|
||||
this.panels.forEach(panel => panel.classList.remove(CLASSES.ACTIVE));
|
||||
|
||||
// Add active class to selected tab and corresponding panel
|
||||
selectedTab.classList.add(CLASSES.ACTIVE);
|
||||
const targetPanel = document.getElementById(targetPanelId);
|
||||
if (targetPanel) {
|
||||
targetPanel.classList.add(CLASSES.ACTIVE);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Product Showcase Component
|
||||
const ProductShowcase = {
|
||||
expandButton: null,
|
||||
toolsGrid: null,
|
||||
|
||||
/**
|
||||
* Initialize product showcase
|
||||
*/
|
||||
init() {
|
||||
this.expandButton = document.querySelector(SELECTORS.EXPAND_BUTTON);
|
||||
this.toolsGrid = document.querySelector(SELECTORS.TOOLS_GRID);
|
||||
|
||||
if (!this.expandButton || !this.toolsGrid) return;
|
||||
|
||||
this.expandButton.addEventListener('click', () => this.toggleExpand());
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle expand/collapse state
|
||||
*/
|
||||
toggleExpand() {
|
||||
const isExpanded = this.expandButton.getAttribute(DATA_ATTRS.EXPANDED) === 'true';
|
||||
|
||||
if (isExpanded) {
|
||||
this.collapse();
|
||||
} else {
|
||||
this.expand();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Expand the tools grid
|
||||
*/
|
||||
expand() {
|
||||
this.toolsGrid.classList.remove(CLASSES.COLLAPSED);
|
||||
this.expandButton.setAttribute(DATA_ATTRS.EXPANDED, 'true');
|
||||
this.updateExpandButtonText();
|
||||
},
|
||||
|
||||
/**
|
||||
* Collapse the tools grid
|
||||
*/
|
||||
collapse() {
|
||||
this.toolsGrid.classList.add(CLASSES.COLLAPSED);
|
||||
this.expandButton.setAttribute(DATA_ATTRS.EXPANDED, 'false');
|
||||
this.updateExpandButtonText();
|
||||
},
|
||||
|
||||
/**
|
||||
* Update expand button text based on state
|
||||
*/
|
||||
updateExpandButtonText() {
|
||||
const expandText = this.expandButton?.querySelector('.expand-text');
|
||||
if (!expandText) return;
|
||||
|
||||
const isExpanded = this.expandButton.getAttribute(DATA_ATTRS.EXPANDED) === 'true';
|
||||
expandText.textContent = getTranslation(isExpanded ? 'hideDetails' : 'expandDetails');
|
||||
}
|
||||
};
|
||||
|
||||
// Login Modal Component
|
||||
const LoginModal = {
|
||||
modalElement: null,
|
||||
modalStyles: null,
|
||||
|
||||
/**
|
||||
* Show login modal
|
||||
*/
|
||||
show() {
|
||||
this.createModal();
|
||||
this.attachEventListeners();
|
||||
},
|
||||
|
||||
/**
|
||||
* Create modal HTML and styles
|
||||
*/
|
||||
createModal() {
|
||||
// Create modal element
|
||||
this.modalElement = document.createElement('div');
|
||||
this.modalElement.className = 'login-modal';
|
||||
this.modalElement.innerHTML = this.getModalHTML();
|
||||
document.body.appendChild(this.modalElement);
|
||||
|
||||
// Add modal styles if not already added
|
||||
if (!this.modalStyles) {
|
||||
this.addModalStyles();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get modal HTML content
|
||||
* @returns {string} Modal HTML
|
||||
*/
|
||||
getModalHTML() {
|
||||
const t = getTranslation;
|
||||
return `
|
||||
<div class="modal-content">
|
||||
<button class="modal-close">×</button>
|
||||
<div class="modal-header">
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="lock-icon">
|
||||
<rect x="5" y="11" width="14" height="10" rx="2" stroke="currentColor" stroke-width="2"/>
|
||||
<path d="M7 11V7C7 4.23858 9.23858 2 12 2C14.7614 2 17 4.23858 17 7V11" stroke="currentColor" stroke-width="2"/>
|
||||
<circle cx="12" cy="16" r="1" fill="currentColor"/>
|
||||
</svg>
|
||||
<h3>${t('authRequired')}</h3>
|
||||
</div>
|
||||
<p>${t('authDescription')}</p>
|
||||
<form id="loginForm">
|
||||
<div class="form-group">
|
||||
<label for="auth-password">${t('accessCode')}</label>
|
||||
<input type="password" id="auth-password" placeholder="${t('accessCodePlaceholder')}" required autofocus>
|
||||
</div>
|
||||
<button type="submit" class="primary-button">${t('grantAccess')}</button>
|
||||
</form>
|
||||
<p class="auth-note">${t('noAccess')} <a href="mailto:info@intelsight.de">${t('contactUs')}</a></p>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add modal styles to document
|
||||
*/
|
||||
addModalStyles() {
|
||||
this.modalStyles = document.createElement('style');
|
||||
this.modalStyles.textContent = `
|
||||
.login-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
backdrop-filter: blur(10px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10000;
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
.modal-content {
|
||||
background: #ffffff;
|
||||
border-radius: 12px;
|
||||
padding: 2.5rem;
|
||||
max-width: 400px;
|
||||
width: 90%;
|
||||
position: relative;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.modal-header {
|
||||
text-align: center;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.modal-header .lock-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
color: #0066cc;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.modal-close {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
background: none;
|
||||
border: none;
|
||||
color: #666;
|
||||
font-size: 2rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.modal-close:hover {
|
||||
background: #f0f0f0;
|
||||
color: #333;
|
||||
}
|
||||
.modal-content h3 {
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.modal-content p {
|
||||
color: #666;
|
||||
margin-bottom: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
.modal-content .form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.modal-content label {
|
||||
display: block;
|
||||
color: #333;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.modal-content input {
|
||||
width: 100%;
|
||||
padding: 0.875rem;
|
||||
background: #f8f8f8;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
color: #333;
|
||||
font-size: 1rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.modal-content input:focus {
|
||||
outline: none;
|
||||
border-color: #0066cc;
|
||||
background: #fff;
|
||||
}
|
||||
.modal-content input::placeholder {
|
||||
color: #999;
|
||||
}
|
||||
.modal-content .primary-button {
|
||||
width: 100%;
|
||||
padding: 0.875rem;
|
||||
background: #0066cc;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.modal-content .primary-button:hover {
|
||||
background: #0052a3;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(0, 102, 204, 0.3);
|
||||
}
|
||||
.auth-note {
|
||||
text-align: center;
|
||||
margin-top: 1.5rem;
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
}
|
||||
.auth-note a {
|
||||
color: #0066cc;
|
||||
text-decoration: none;
|
||||
}
|
||||
.auth-note a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(this.modalStyles);
|
||||
},
|
||||
|
||||
/**
|
||||
* Attach event listeners to modal
|
||||
*/
|
||||
attachEventListeners() {
|
||||
// Close button
|
||||
const closeBtn = this.modalElement.querySelector('.modal-close');
|
||||
closeBtn.addEventListener('click', () => this.close());
|
||||
|
||||
// Form submission
|
||||
const form = this.modalElement.querySelector('#loginForm');
|
||||
form.addEventListener('submit', (e) => this.handleSubmit(e));
|
||||
|
||||
// Click outside to close
|
||||
this.modalElement.addEventListener('click', (e) => {
|
||||
if (e.target === this.modalElement) {
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle form submission
|
||||
* @param {Event} e - Submit event
|
||||
*/
|
||||
handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
const password = document.getElementById('auth-password').value;
|
||||
|
||||
// Check password (temporarily hardcoded as requested)
|
||||
if (password === '123456') {
|
||||
sessionStorage.setItem(CONFIG.AUTH.SESSION_KEY, 'true');
|
||||
this.close();
|
||||
window.location.href = CONFIG.AUTH.REDIRECT_PAGE;
|
||||
} else {
|
||||
alert(getTranslation('wrongCode'));
|
||||
document.getElementById('auth-password').value = '';
|
||||
document.getElementById('auth-password').focus();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Close and remove modal
|
||||
*/
|
||||
close() {
|
||||
if (this.modalElement) {
|
||||
this.modalElement.remove();
|
||||
this.modalElement = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Contact Form Component
|
||||
const ContactForm = {
|
||||
form: null,
|
||||
|
||||
/**
|
||||
* Initialize contact form
|
||||
*/
|
||||
init() {
|
||||
this.form = document.querySelector(SELECTORS.CONTACT_FORM);
|
||||
if (!this.form) return;
|
||||
|
||||
this.form.addEventListener('submit', (e) => this.handleSubmit(e));
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle form submission
|
||||
* @param {Event} e - Submit event
|
||||
*/
|
||||
handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Get form data
|
||||
const formData = new FormData(this.form);
|
||||
const data = Object.fromEntries(formData.entries());
|
||||
|
||||
// In production, this would send data to server
|
||||
console.log('Form submission:', data);
|
||||
|
||||
// Show success message
|
||||
alert(getTranslation('contactFormSuccess'));
|
||||
this.form.reset();
|
||||
}
|
||||
};
|
||||
|
||||
// Demo Request Handler
|
||||
const DemoRequest = {
|
||||
/**
|
||||
* Initialize demo request buttons
|
||||
*/
|
||||
init() {
|
||||
document.querySelectorAll('.primary-button, .secondary-button, .cta-button').forEach(button => {
|
||||
if (button.textContent.toLowerCase().includes('demo')) {
|
||||
button.addEventListener('click', (e) => this.handleDemoRequest(e));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle demo request
|
||||
* @param {Event} e - Click event
|
||||
*/
|
||||
handleDemoRequest(e) {
|
||||
e.preventDefault();
|
||||
alert(getTranslation('demoRequestAlert'));
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize all components
|
||||
const Components = {
|
||||
/**
|
||||
* Initialize all UI components
|
||||
*/
|
||||
init() {
|
||||
LanguageToggle.init();
|
||||
Navigation.init();
|
||||
AboutTabs.init();
|
||||
ProductShowcase.init();
|
||||
ContactForm.init();
|
||||
DemoRequest.init();
|
||||
}
|
||||
};
|
||||
|
||||
// Make showLoginModal globally available for onclick attribute
|
||||
window.showLoginModal = function() {
|
||||
LoginModal.show();
|
||||
};
|
||||
|
||||
// Make closeLoginModal globally available for onclick attribute
|
||||
window.closeLoginModal = function() {
|
||||
LoginModal.close();
|
||||
};
|
||||
156
js/config.js
Normale Datei
156
js/config.js
Normale Datei
@ -0,0 +1,156 @@
|
||||
/**
|
||||
* Central configuration file for IntelSight website
|
||||
* Contains all constants, settings and selectors
|
||||
*/
|
||||
|
||||
// Application Configuration
|
||||
const CONFIG = {
|
||||
// Animation Settings
|
||||
ANIMATION: {
|
||||
PARTICLE_COUNT: 100,
|
||||
PARTICLE_SPEED: 3,
|
||||
PARTICLE_SIZE_MIN: 1,
|
||||
PARTICLE_SIZE_MAX: 3,
|
||||
CONNECTION_DISTANCE: 100,
|
||||
COUNTER_SPEED: 200,
|
||||
SCROLL_THRESHOLD: 50,
|
||||
FADE_DURATION: 500,
|
||||
GLITCH_ITERATIONS: 5,
|
||||
GLITCH_INTERVAL: 50
|
||||
},
|
||||
|
||||
// Hero Video Settings
|
||||
HERO_VIDEOS: {
|
||||
ROTATION_INTERVAL: 12000, // 12 seconds per video (slower like Palantir)
|
||||
FADE_DURATION: 3000, // 3 second fade transition (much slower)
|
||||
VIDEO_SOURCES: [
|
||||
'assets/videos/hero-tech-pattern.mp4',
|
||||
'assets/videos/hero-data-flow.mp4',
|
||||
'assets/videos/hero-network-viz.mp4',
|
||||
'assets/videos/hero-code-abstract.mp4'
|
||||
]
|
||||
},
|
||||
|
||||
// Language Settings
|
||||
I18N: {
|
||||
DEFAULT_LANGUAGE: 'de',
|
||||
SUPPORTED_LANGUAGES: ['de', 'en'],
|
||||
STORAGE_KEY: 'intelsight_language'
|
||||
},
|
||||
|
||||
// Trust Indicators Target Values
|
||||
TRUST_INDICATORS: {
|
||||
AVAILABILITY: 99.9,
|
||||
AUTHORITIES_COUNT: 500,
|
||||
SUPPORT_HOURS: 24
|
||||
},
|
||||
|
||||
// Intersection Observer Settings
|
||||
OBSERVER: {
|
||||
THRESHOLD: 0.3,
|
||||
ROOT_MARGIN: '0px'
|
||||
},
|
||||
|
||||
// Authentication Settings
|
||||
AUTH: {
|
||||
SESSION_KEY: 'accountForgerAuth',
|
||||
REDIRECT_PAGE: 'accountforger-video.html'
|
||||
},
|
||||
|
||||
// Timeouts and Intervals
|
||||
TIMING: {
|
||||
SCROLL_HIDE_DELAY: 500,
|
||||
COUNTER_UPDATE_INTERVAL: 10,
|
||||
MAP_POINT_SPAWN_INTERVAL: 5000,
|
||||
RESPONSE_TIMER_UPDATE: 2000,
|
||||
LIVE_COUNTER_UPDATE: 3000
|
||||
}
|
||||
};
|
||||
|
||||
// DOM Selectors
|
||||
const SELECTORS = {
|
||||
// Navigation
|
||||
NAVBAR: '.navbar',
|
||||
NAV_MENU: '.nav-menu',
|
||||
LANG_TOGGLE: '.lang-toggle',
|
||||
|
||||
// Hero Section
|
||||
HERO: '.hero',
|
||||
HERO_CONTENT: '.hero-content',
|
||||
HERO_VIDEO: '.hero-video',
|
||||
PARTICLE_CANVAS: '#particleCanvas',
|
||||
SCROLL_INDICATOR: '.scroll-indicator',
|
||||
|
||||
// Trust Indicators
|
||||
TRUST_INDICATORS: '.trust-indicators',
|
||||
INDICATOR_VALUE: '.indicator-value',
|
||||
|
||||
// About Section
|
||||
ABOUT_TABS: '.about-tab',
|
||||
ABOUT_PANELS: '.about-panel',
|
||||
|
||||
// Products Section
|
||||
EXPAND_BUTTON: '.expand-button',
|
||||
TOOLS_GRID: '.tools-grid',
|
||||
TOOL_CARDS: '.tool-card',
|
||||
|
||||
// Modals
|
||||
LOGIN_MODAL: '.login-modal',
|
||||
MODAL_CLOSE: '.modal-close',
|
||||
|
||||
// Forms
|
||||
CONTACT_FORM: '#contactForm',
|
||||
LOGIN_FORM: '#loginForm',
|
||||
|
||||
// Animation Elements
|
||||
INTERACTIVE_ICON: '#interactiveIcon',
|
||||
NEURAL_CANVAS: '#neuralCanvas',
|
||||
DATA_PARTICLES: '#dataParticles',
|
||||
LIVE_COUNTER: '#liveCounter',
|
||||
RESPONSE_TIMER: '#responseTimer',
|
||||
MAP_POINTS: '#mapPoints',
|
||||
|
||||
// Generic
|
||||
TRANSLATABLE: '[data-translate]',
|
||||
SMOOTH_LINKS: 'a[href^="#"]',
|
||||
SKIP_NAV: '.skip-nav'
|
||||
};
|
||||
|
||||
// CSS Classes
|
||||
const CLASSES = {
|
||||
ACTIVE: 'active',
|
||||
SCROLLED: 'scrolled',
|
||||
COLLAPSED: 'collapsed',
|
||||
VISIBLE: 'visible',
|
||||
LOADED: 'loaded',
|
||||
EXPANDED: 'expanded',
|
||||
HIDDEN: 'hidden',
|
||||
|
||||
// Animation Classes
|
||||
FADE_IN: 'fade-in',
|
||||
FADE_OUT: 'fade-out',
|
||||
SLIDE_UP: 'slide-up',
|
||||
SLIDE_DOWN: 'slide-down',
|
||||
|
||||
// Component Classes
|
||||
PARTICLE: 'particle',
|
||||
DATA_PARTICLE: 'data-particle',
|
||||
MAP_POINT: 'map-point',
|
||||
NODE: 'node',
|
||||
WIDGET: 'widget',
|
||||
TAB: 'tab',
|
||||
PANEL: 'panel'
|
||||
};
|
||||
|
||||
// Data Attributes
|
||||
const DATA_ATTRS = {
|
||||
TRANSLATE: 'data-translate',
|
||||
TAB: 'data-tab',
|
||||
EXPANDED: 'data-expanded',
|
||||
LANG: 'data-lang',
|
||||
TARGET: 'data-target',
|
||||
TOOL: 'data-tool'
|
||||
};
|
||||
|
||||
// Export for use in other modules (if using module system)
|
||||
// For now, these are global constants available to all scripts
|
||||
209
js/hero-videos.js
Normale Datei
209
js/hero-videos.js
Normale Datei
@ -0,0 +1,209 @@
|
||||
/**
|
||||
* Hero Video Rotation System
|
||||
* Manages rotating background videos in hero section
|
||||
*/
|
||||
|
||||
const HeroVideoRotation = {
|
||||
videos: [],
|
||||
currentIndex: 0,
|
||||
rotationInterval: null,
|
||||
isTransitioning: false,
|
||||
|
||||
/**
|
||||
* Initialize the video rotation system
|
||||
*/
|
||||
init() {
|
||||
// Get all video elements
|
||||
this.videos = document.querySelectorAll('.hero-video');
|
||||
|
||||
if (!this.videos.length) return;
|
||||
|
||||
// Setup event listeners
|
||||
this.setupEventListeners();
|
||||
|
||||
// Start rotation
|
||||
this.startRotation();
|
||||
|
||||
// Ensure first video is playing
|
||||
this.playVideo(0);
|
||||
},
|
||||
|
||||
/**
|
||||
* Setup event listeners for videos
|
||||
*/
|
||||
setupEventListeners() {
|
||||
// Indicators removed - no click handlers needed
|
||||
|
||||
// Pause rotation on hover (optional)
|
||||
const heroSection = document.querySelector('.hero');
|
||||
if (heroSection) {
|
||||
heroSection.addEventListener('mouseenter', () => {
|
||||
// Optional: pause rotation on hover
|
||||
// this.stopRotation();
|
||||
});
|
||||
|
||||
heroSection.addEventListener('mouseleave', () => {
|
||||
// Optional: resume rotation
|
||||
// this.startRotation();
|
||||
});
|
||||
}
|
||||
|
||||
// Handle video load errors gracefully
|
||||
this.videos.forEach((video, index) => {
|
||||
video.addEventListener('error', () => {
|
||||
console.warn(`Video ${index} failed to load, skipping...`);
|
||||
// If current video fails, move to next
|
||||
if (index === this.currentIndex) {
|
||||
this.nextVideo();
|
||||
}
|
||||
});
|
||||
|
||||
// Ensure videos are ready to play
|
||||
video.addEventListener('loadeddata', () => {
|
||||
console.log(`Video ${index} loaded successfully`);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Start automatic rotation
|
||||
*/
|
||||
startRotation() {
|
||||
// Clear any existing interval
|
||||
this.stopRotation();
|
||||
|
||||
// Set new interval
|
||||
this.rotationInterval = setInterval(() => {
|
||||
this.nextVideo();
|
||||
}, CONFIG.HERO_VIDEOS.ROTATION_INTERVAL);
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop automatic rotation
|
||||
*/
|
||||
stopRotation() {
|
||||
if (this.rotationInterval) {
|
||||
clearInterval(this.rotationInterval);
|
||||
this.rotationInterval = null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Switch to next video
|
||||
*/
|
||||
nextVideo() {
|
||||
const nextIndex = (this.currentIndex + 1) % this.videos.length;
|
||||
this.switchToVideo(nextIndex);
|
||||
},
|
||||
|
||||
/**
|
||||
* Switch to previous video
|
||||
*/
|
||||
previousVideo() {
|
||||
const prevIndex = (this.currentIndex - 1 + this.videos.length) % this.videos.length;
|
||||
this.switchToVideo(prevIndex);
|
||||
},
|
||||
|
||||
/**
|
||||
* Switch to specific video by index
|
||||
* @param {number} index - Video index to switch to
|
||||
*/
|
||||
switchToVideo(index) {
|
||||
if (this.isTransitioning || index === this.currentIndex) return;
|
||||
|
||||
this.isTransitioning = true;
|
||||
|
||||
const currentVideo = this.videos[this.currentIndex];
|
||||
const nextVideo = this.videos[index];
|
||||
|
||||
// Indicators removed - no update needed
|
||||
|
||||
// Prepare next video
|
||||
this.prepareVideo(nextVideo);
|
||||
|
||||
// Fade out current video
|
||||
currentVideo.classList.add('fading-out');
|
||||
|
||||
// After half the fade duration, start fading in the next video
|
||||
setTimeout(() => {
|
||||
nextVideo.classList.add('active');
|
||||
nextVideo.classList.remove('fading-out');
|
||||
|
||||
// Play next video
|
||||
this.playVideo(index);
|
||||
}, CONFIG.HERO_VIDEOS.FADE_DURATION / 2);
|
||||
|
||||
// Complete transition
|
||||
setTimeout(() => {
|
||||
currentVideo.classList.remove('active', 'fading-out');
|
||||
this.currentIndex = index;
|
||||
this.isTransitioning = false;
|
||||
}, CONFIG.HERO_VIDEOS.FADE_DURATION);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Prepare video for playback
|
||||
* @param {HTMLVideoElement} video - Video element to prepare
|
||||
*/
|
||||
prepareVideo(video) {
|
||||
// Reset video to beginning
|
||||
video.currentTime = 0;
|
||||
|
||||
// Ensure video is ready to play
|
||||
const playPromise = video.play();
|
||||
if (playPromise !== undefined) {
|
||||
playPromise.catch(error => {
|
||||
console.warn('Video autoplay was prevented:', error);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Play specific video
|
||||
* @param {number} index - Index of video to play
|
||||
*/
|
||||
playVideo(index) {
|
||||
const video = this.videos[index];
|
||||
if (video) {
|
||||
const playPromise = video.play();
|
||||
if (playPromise !== undefined) {
|
||||
playPromise.catch(error => {
|
||||
console.warn(`Could not play video ${index}:`, error);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Pause all videos
|
||||
*/
|
||||
pauseAllVideos() {
|
||||
this.videos.forEach(video => {
|
||||
video.pause();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle page visibility change (pause when tab is not visible)
|
||||
*/
|
||||
handleVisibilityChange() {
|
||||
if (document.hidden) {
|
||||
this.stopRotation();
|
||||
this.pauseAllVideos();
|
||||
} else {
|
||||
this.playVideo(this.currentIndex);
|
||||
this.startRotation();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize when DOM is ready
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
HeroVideoRotation.init();
|
||||
});
|
||||
|
||||
// Handle page visibility API
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
HeroVideoRotation.handleVisibilityChange();
|
||||
});
|
||||
277
js/main.js
Normale Datei
277
js/main.js
Normale Datei
@ -0,0 +1,277 @@
|
||||
/**
|
||||
* Main application entry point for IntelSight website
|
||||
* Initializes all modules and coordinates application startup
|
||||
*/
|
||||
|
||||
/**
|
||||
* Toggle tools grid visibility
|
||||
*/
|
||||
function toggleTools(button) {
|
||||
// Find the tools grid within the same product card
|
||||
const productCard = button.closest('.product-card');
|
||||
const toolsGrid = productCard.querySelector('.tools-grid');
|
||||
|
||||
if (toolsGrid) {
|
||||
const isExpanded = toolsGrid.classList.contains('expanded');
|
||||
const currentLang = getCurrentLanguage ? getCurrentLanguage() : 'de';
|
||||
|
||||
if (isExpanded) {
|
||||
toolsGrid.classList.remove('expanded');
|
||||
toolsGrid.classList.add('collapsed');
|
||||
button.setAttribute('data-expanded', 'false');
|
||||
button.querySelector('span').textContent = currentLang === 'de' ? 'Details anzeigen' : 'Show Details';
|
||||
} else {
|
||||
// Force browser reflow before adding expanded class
|
||||
toolsGrid.style.display = 'grid';
|
||||
void toolsGrid.offsetHeight; // Trigger reflow
|
||||
|
||||
toolsGrid.classList.remove('collapsed');
|
||||
toolsGrid.classList.add('expanded');
|
||||
button.setAttribute('data-expanded', 'true');
|
||||
button.querySelector('span').textContent = currentLang === 'de' ? 'Details verbergen' : 'Hide Details';
|
||||
|
||||
// Ensure all tool cards are visible
|
||||
setTimeout(() => {
|
||||
const toolCards = toolsGrid.querySelectorAll('.tool-card');
|
||||
toolCards.forEach((card, index) => {
|
||||
card.style.opacity = '1';
|
||||
card.style.transform = 'translateY(0)';
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Application initialization
|
||||
*/
|
||||
const App = {
|
||||
/**
|
||||
* Initialize the entire application
|
||||
*/
|
||||
init() {
|
||||
// Check DOM ready state
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => this.start());
|
||||
} else {
|
||||
// DOM is already ready
|
||||
this.start();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Start the application after DOM is ready
|
||||
*/
|
||||
start() {
|
||||
console.log('IntelSight Website Initializing...');
|
||||
|
||||
// Initialize modules in correct order
|
||||
try {
|
||||
// 1. Initialize translations first (needed by components)
|
||||
initTranslations();
|
||||
console.log('✓ Translations initialized');
|
||||
|
||||
// 2. Initialize UI components
|
||||
Components.init();
|
||||
console.log('✓ Components initialized');
|
||||
|
||||
// 3. Initialize animations
|
||||
Animations.init();
|
||||
console.log('✓ Animations initialized');
|
||||
|
||||
// 4. Setup error handling
|
||||
this.setupErrorHandling();
|
||||
|
||||
// 5. Setup performance monitoring
|
||||
this.setupPerformanceMonitoring();
|
||||
|
||||
console.log('IntelSight Website Ready!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize application:', error);
|
||||
this.handleInitError(error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Setup global error handling
|
||||
*/
|
||||
setupErrorHandling() {
|
||||
window.addEventListener('error', (event) => {
|
||||
console.error('Global error:', event.error);
|
||||
// In production, this would send errors to monitoring service
|
||||
});
|
||||
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
console.error('Unhandled promise rejection:', event.reason);
|
||||
// In production, this would send errors to monitoring service
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Setup performance monitoring
|
||||
*/
|
||||
setupPerformanceMonitoring() {
|
||||
// Monitor page load performance
|
||||
window.addEventListener('load', () => {
|
||||
if (window.performance && window.performance.timing) {
|
||||
const timing = window.performance.timing;
|
||||
const loadTime = timing.loadEventEnd - timing.navigationStart;
|
||||
console.log(`Page load time: ${loadTime}ms`);
|
||||
|
||||
// Log other performance metrics
|
||||
const metrics = {
|
||||
domContentLoaded: timing.domContentLoadedEventEnd - timing.navigationStart,
|
||||
domComplete: timing.domComplete - timing.navigationStart,
|
||||
firstPaint: this.getFirstPaintTime()
|
||||
};
|
||||
|
||||
console.log('Performance metrics:', metrics);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get first paint time if available
|
||||
* @returns {number|null} First paint time in milliseconds
|
||||
*/
|
||||
getFirstPaintTime() {
|
||||
if (window.performance && window.performance.getEntriesByType) {
|
||||
const paintEntries = window.performance.getEntriesByType('paint');
|
||||
const firstPaint = paintEntries.find(entry => entry.name === 'first-paint');
|
||||
return firstPaint ? Math.round(firstPaint.startTime) : null;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle initialization errors
|
||||
* @param {Error} error - The error that occurred
|
||||
*/
|
||||
handleInitError(error) {
|
||||
// Create a fallback error message for users
|
||||
const errorContainer = document.createElement('div');
|
||||
errorContainer.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: #ff4444;
|
||||
color: white;
|
||||
padding: 15px 20px;
|
||||
border-radius: 5px;
|
||||
z-index: 10000;
|
||||
max-width: 300px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
||||
`;
|
||||
errorContainer.textContent = 'Ein Fehler ist aufgetreten. Bitte laden Sie die Seite neu.';
|
||||
document.body.appendChild(errorContainer);
|
||||
|
||||
// Auto-remove after 5 seconds
|
||||
setTimeout(() => {
|
||||
errorContainer.remove();
|
||||
}, 5000);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility functions
|
||||
*/
|
||||
const Utils = {
|
||||
/**
|
||||
* Debounce function to limit function calls
|
||||
* @param {Function} func - Function to debounce
|
||||
* @param {number} wait - Wait time in milliseconds
|
||||
* @returns {Function} Debounced function
|
||||
*/
|
||||
debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Throttle function to limit function calls
|
||||
* @param {Function} func - Function to throttle
|
||||
* @param {number} limit - Time limit in milliseconds
|
||||
* @returns {Function} Throttled function
|
||||
*/
|
||||
throttle(func, limit) {
|
||||
let inThrottle;
|
||||
return function(...args) {
|
||||
if (!inThrottle) {
|
||||
func.apply(this, args);
|
||||
inThrottle = true;
|
||||
setTimeout(() => inThrottle = false, limit);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if element is in viewport
|
||||
* @param {HTMLElement} element - Element to check
|
||||
* @returns {boolean} True if element is in viewport
|
||||
*/
|
||||
isInViewport(element) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
return (
|
||||
rect.top >= 0 &&
|
||||
rect.left >= 0 &&
|
||||
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
||||
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Load script dynamically
|
||||
* @param {string} src - Script source URL
|
||||
* @returns {Promise} Promise that resolves when script is loaded
|
||||
*/
|
||||
loadScript(src) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script');
|
||||
script.src = src;
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get cookie value by name
|
||||
* @param {string} name - Cookie name
|
||||
* @returns {string|null} Cookie value or null
|
||||
*/
|
||||
getCookie(name) {
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
if (parts.length === 2) {
|
||||
return parts.pop().split(';').shift();
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set cookie
|
||||
* @param {string} name - Cookie name
|
||||
* @param {string} value - Cookie value
|
||||
* @param {number} days - Days until expiration
|
||||
*/
|
||||
setCookie(name, value, days) {
|
||||
const date = new Date();
|
||||
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||
const expires = `expires=${date.toUTCString()}`;
|
||||
document.cookie = `${name}=${value};${expires};path=/`;
|
||||
}
|
||||
};
|
||||
|
||||
// Make Utils globally available if needed
|
||||
window.Utils = Utils;
|
||||
|
||||
// Start the application
|
||||
App.init();
|
||||
188
js/protection.js
Normale Datei
188
js/protection.js
Normale Datei
@ -0,0 +1,188 @@
|
||||
/**
|
||||
* Website Protection Script
|
||||
* Prevents copying, downloading, and inspection of website content
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// Disable right-click context menu
|
||||
document.addEventListener('contextmenu', function(e) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
|
||||
// Disable text selection
|
||||
document.addEventListener('selectstart', function(e) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
|
||||
// Disable drag
|
||||
document.addEventListener('dragstart', function(e) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
|
||||
// Disable copy
|
||||
document.addEventListener('copy', function(e) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
|
||||
// Disable cut
|
||||
document.addEventListener('cut', function(e) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
|
||||
// Disable paste
|
||||
document.addEventListener('paste', function(e) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
|
||||
// Disable print
|
||||
window.addEventListener('beforeprint', function(e) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
|
||||
// Detect and block DevTools
|
||||
let devtools = {open: false, orientation: null};
|
||||
const threshold = 160;
|
||||
const emitEvent = (state, orientation) => {
|
||||
if (state) {
|
||||
console.clear();
|
||||
document.body.style.display = 'none';
|
||||
document.body.innerHTML = '<div style="display:flex;justify-content:center;align-items:center;height:100vh;background:#000;color:#fff;font-family:Inter,sans-serif;"><h1>Zugriff verweigert</h1></div>';
|
||||
}
|
||||
};
|
||||
|
||||
setInterval(() => {
|
||||
if (window.outerHeight - window.innerHeight > threshold ||
|
||||
window.outerWidth - window.innerWidth > threshold) {
|
||||
if (!devtools.open) {
|
||||
emitEvent(true, null);
|
||||
devtools.open = true;
|
||||
}
|
||||
} else {
|
||||
if (devtools.open) {
|
||||
emitEvent(false, null);
|
||||
devtools.open = false;
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
|
||||
// Disable F12, Ctrl+Shift+I, Ctrl+Shift+J, Ctrl+U, Ctrl+S
|
||||
document.addEventListener('keydown', function(e) {
|
||||
// F12
|
||||
if (e.keyCode === 123) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
// Ctrl+Shift+I (DevTools)
|
||||
if (e.ctrlKey && e.shiftKey && e.keyCode === 73) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
// Ctrl+Shift+J (Console)
|
||||
if (e.ctrlKey && e.shiftKey && e.keyCode === 74) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
// Ctrl+U (View Source)
|
||||
if (e.ctrlKey && e.keyCode === 85) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
// Ctrl+S (Save)
|
||||
if (e.ctrlKey && e.keyCode === 83) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
// Ctrl+A (Select All)
|
||||
if (e.ctrlKey && e.keyCode === 65) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
// Ctrl+C (Copy)
|
||||
if (e.ctrlKey && e.keyCode === 67) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
// Ctrl+X (Cut)
|
||||
if (e.ctrlKey && e.keyCode === 88) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
// Ctrl+V (Paste)
|
||||
if (e.ctrlKey && e.keyCode === 86) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Disable image dragging
|
||||
const images = document.getElementsByTagName('img');
|
||||
for (let i = 0; i < images.length; i++) {
|
||||
images[i].addEventListener('dragstart', function(e) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
images[i].addEventListener('mousedown', function(e) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
// Clear console periodically
|
||||
setInterval(function() {
|
||||
console.clear();
|
||||
console.log('%cSicherheitswarnung!', 'color: red; font-size: 30px; font-weight: bold;');
|
||||
console.log('%cDiese Browser-Funktion ist für Entwickler vorgesehen. Unbefugter Zugriff ist untersagt.', 'color: red; font-size: 16px;');
|
||||
}, 1000);
|
||||
|
||||
// Detect console.log override attempts
|
||||
const originalLog = console.log;
|
||||
console.log = function() {
|
||||
originalLog.apply(console, arguments);
|
||||
console.clear();
|
||||
};
|
||||
|
||||
// Disable text highlighting with CSS
|
||||
const style = document.createElement('style');
|
||||
style.innerHTML = `
|
||||
* {
|
||||
-webkit-user-select: none !important;
|
||||
-moz-user-select: none !important;
|
||||
-ms-user-select: none !important;
|
||||
user-select: none !important;
|
||||
-webkit-touch-callout: none !important;
|
||||
-webkit-user-drag: none !important;
|
||||
}
|
||||
|
||||
img {
|
||||
pointer-events: none !important;
|
||||
-webkit-user-drag: none !important;
|
||||
-khtml-user-drag: none !important;
|
||||
-moz-user-drag: none !important;
|
||||
-o-user-drag: none !important;
|
||||
user-drag: none !important;
|
||||
}
|
||||
|
||||
@media print {
|
||||
body {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
// Protection against automated tools
|
||||
if (window.automation || window.callPhantom || window._phantom || window.__nightmare || window.WebDriverException || document.__webdriver_evaluate || document.__selenium_evaluate || document.__webdriver_script_function || document.__webdriver_script_func || document.__webdriver_script_fn || document.__fxdriver_evaluate || document.__driver_unwrapped || document.__webdriver_unwrapped || document.__driver_evaluate || document.__selenium_unwrapped || document.__fxdriver_unwrapped) {
|
||||
document.body.style.display = 'none';
|
||||
document.body.innerHTML = '';
|
||||
}
|
||||
|
||||
})();
|
||||
130
js/section-transitions.js
Normale Datei
130
js/section-transitions.js
Normale Datei
@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Section Transitions & Effects
|
||||
* Modern animations for section dividers
|
||||
*/
|
||||
|
||||
const SectionTransitions = {
|
||||
init() {
|
||||
this.initParticleBridge();
|
||||
this.initScrollReveal();
|
||||
this.initWaveAnimation();
|
||||
this.initParallaxDividers();
|
||||
},
|
||||
|
||||
// Animated particles between sections
|
||||
initParticleBridge() {
|
||||
const bridge = document.getElementById('particleBridge');
|
||||
if (!bridge) return;
|
||||
|
||||
// Create floating particles
|
||||
for (let i = 0; i < 30; i++) {
|
||||
const particle = document.createElement('div');
|
||||
particle.className = 'particle';
|
||||
particle.style.left = Math.random() * 100 + '%';
|
||||
particle.style.animationDelay = Math.random() * 5 + 's';
|
||||
particle.style.animationDuration = (5 + Math.random() * 5) + 's';
|
||||
particle.style.animation = `floatParticle ${5 + Math.random() * 5}s linear infinite`;
|
||||
bridge.appendChild(particle);
|
||||
}
|
||||
},
|
||||
|
||||
// Reveal sections on scroll
|
||||
initScrollReveal() {
|
||||
const sections = document.querySelectorAll('.fade-section');
|
||||
|
||||
const revealSection = (entries, observer) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('visible');
|
||||
|
||||
// Add shimmer effect on reveal
|
||||
const shimmer = document.createElement('div');
|
||||
shimmer.style.cssText = `
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(15, 114, 181, 0.2), transparent);
|
||||
animation: shimmerPass 1s ease-out forwards;
|
||||
pointer-events: none;
|
||||
z-index: 100;
|
||||
`;
|
||||
entry.target.style.position = 'relative';
|
||||
entry.target.appendChild(shimmer);
|
||||
|
||||
setTimeout(() => shimmer.remove(), 1000);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const observer = new IntersectionObserver(revealSection, {
|
||||
threshold: 0.1,
|
||||
rootMargin: '0px 0px -100px 0px'
|
||||
});
|
||||
|
||||
sections.forEach(section => observer.observe(section));
|
||||
},
|
||||
|
||||
// Animate wave dividers
|
||||
initWaveAnimation() {
|
||||
const waves = document.querySelectorAll('.wave-divider path');
|
||||
|
||||
waves.forEach(wave => {
|
||||
let time = 0;
|
||||
const animateWave = () => {
|
||||
time += 0.02;
|
||||
const points = [];
|
||||
|
||||
for (let i = 0; i <= 10; i++) {
|
||||
const x = (i / 10) * 1200;
|
||||
const y = Math.sin((i / 10) * Math.PI * 2 + time) * 10 + 56;
|
||||
points.push(`${x},${y}`);
|
||||
}
|
||||
|
||||
// Create smooth wave path
|
||||
const d = `M0,56 Q${points[2]} T${points[4]} T${points[6]} T${points[8]} T1200,56 L1200,0 L0,0 Z`;
|
||||
wave.setAttribute('d', d);
|
||||
|
||||
requestAnimationFrame(animateWave);
|
||||
};
|
||||
|
||||
// Start wave animation
|
||||
// animateWave(); // Commented out for performance, uncomment for wave motion
|
||||
});
|
||||
},
|
||||
|
||||
// Parallax effect for dividers
|
||||
initParallaxDividers() {
|
||||
const dividers = document.querySelectorAll('.blob-divider, .gradient-divider, .flow-lines');
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
const scrolled = window.pageYOffset;
|
||||
|
||||
dividers.forEach(divider => {
|
||||
const speed = divider.dataset.speed || 0.5;
|
||||
const yPos = -(scrolled * speed);
|
||||
divider.style.transform = `translateY(${yPos}px)`;
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Add shimmer animation
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@keyframes shimmerPass {
|
||||
to {
|
||||
left: 100%;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
// Initialize when DOM is ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => SectionTransitions.init());
|
||||
} else {
|
||||
SectionTransitions.init();
|
||||
}
|
||||
410
js/translations.js
Normale Datei
410
js/translations.js
Normale Datei
@ -0,0 +1,410 @@
|
||||
/**
|
||||
* Translation system for IntelSight website
|
||||
* Supports German (de) and English (en)
|
||||
*/
|
||||
|
||||
// Translation strings
|
||||
const translations = {
|
||||
de: {
|
||||
// Page meta
|
||||
pageTitle: 'IntelSight - Sicherheit Made in Germany',
|
||||
|
||||
// Navigation
|
||||
skipNav: 'Zum Hauptinhalt springen',
|
||||
navHome: 'Startseite',
|
||||
navAbout: 'Über uns',
|
||||
navProducts: 'Produkte & Lösungen',
|
||||
navContact: 'Kontakt',
|
||||
langSwitch: 'DE | EN',
|
||||
|
||||
// Hero Section
|
||||
heroTitle: 'SICHERHEIT MADE IN GERMANY',
|
||||
heroSubtitle: 'Spezialist für hochsichere, maßgeschneiderte IT-Lösungen für Behörden',
|
||||
|
||||
// Trust Indicators
|
||||
indicatorAvailability: 'Verfügbarkeit',
|
||||
indicatorTrust: 'Behörden vertrauen uns',
|
||||
indicatorSupport: 'Support',
|
||||
|
||||
// Scroll Indicator
|
||||
scrollToExplore: 'Nach unten scrollen',
|
||||
|
||||
// About Section
|
||||
aboutTitle: 'ÜBER UNS',
|
||||
aboutSubtitle: 'Ihr Partner für sichere Behördensoftware',
|
||||
|
||||
// About Tabs
|
||||
tabWhoWeAre: 'Unternehmen',
|
||||
tabMission: 'Mission & Werte',
|
||||
tabCompetencies: 'Kernkompetenzen',
|
||||
tabWhyUs: 'Unser Versprechen',
|
||||
|
||||
// Who We Are
|
||||
whoWeAreTitle: 'Unternehmen',
|
||||
whoWeArePara1: 'IntelSight UG ist Ihr <strong>Spezialist für hochsichere, maßgeschneiderte IT-Lösungen</strong> aus Nordrhein-Westfalen. Wir entwickeln innovative Software speziell für staatliche Sicherheits- und Ermittlungsbehörden.',
|
||||
whoWeArePara2: 'Unser Ansatz vereint modernste Technologie mit einem tiefen Verständnis für die besonderen Anforderungen von Behörden. Dabei steht die Balance zwischen Sicherheit, Effizienz und rechtskonformer Umsetzung im Mittelpunkt unserer Arbeit.',
|
||||
locationBadge: 'Nordrhein-Westfalen, Deutschland',
|
||||
nrwLabel: 'Nordrhein-Westfalen',
|
||||
headquartersLabel: 'Unser Standort: Langenfeld',
|
||||
|
||||
// Mission & Values
|
||||
missionTitle: 'Unsere Mission',
|
||||
missionStatement: 'Wir schaffen <strong>effiziente, sichere und datenschutzkonforme Lösungen</strong> für moderne Strafverfolgung und Sicherheitsbehörden.',
|
||||
valueIntegrityTitle: 'Integrität',
|
||||
valueIntegrityDesc: 'Höchste ethische Standards in allem was wir tun',
|
||||
valueTransparencyTitle: 'Transparenz',
|
||||
valueTransparencyDesc: 'Offene Kommunikation und nachvollziehbare Prozesse',
|
||||
valueDemocracyTitle: 'Demokratische Prinzipien',
|
||||
valueDemocracyDesc: 'Kooperation nur mit Behörden im Einklang mit der freiheitlich demokratischen Grundordnung',
|
||||
principleNote: '<strong>Unser Ziel:</strong> Technologie, die Recht und Sicherheit stärkt und die freiheitlich demokratische Grundordnung schützt.',
|
||||
|
||||
// Competencies
|
||||
competenciesTitle: 'Unsere Kernkompetenzen',
|
||||
comp1Title: 'Behördenspezifische Software',
|
||||
comp1Desc: 'Entwicklung mit höchsten Sicherheitsstandards, maßgeschneidert für staatliche Anforderungen',
|
||||
comp2Title: 'Intuitive Bedienkonzepte',
|
||||
comp2Desc: 'Benutzerfreundliche Oberflächen trotz komplexer Funktionen für effizientes Arbeiten',
|
||||
comp3Title: 'Langzeit-Support',
|
||||
comp3Desc: 'Kontinuierliche Sicherheitsupdates und zuverlässige Wartung über den gesamten Lebenszyklus',
|
||||
|
||||
// Why Us
|
||||
whyUsTitle: 'Warum IntelSight UG?',
|
||||
why1Title: 'Enge Zusammenarbeit',
|
||||
why1Desc: 'Wir arbeiten Hand in Hand mit unseren Kunden für maßgeschneiderte Lösungen',
|
||||
why2Title: 'Made in Germany',
|
||||
why2Desc: 'Klare, robuste und sichere Software nach deutschen Qualitätsstandards',
|
||||
why3Title: 'Verlässliche Partnerschaft',
|
||||
why3Desc: 'Basierend auf gemeinsamen Werten und langfristigem Vertrauen',
|
||||
why4Title: 'Nachhaltigkeit',
|
||||
why4Desc: 'Fokus auf Sicherheit, Professionalität & zukunftssichere Lösungen',
|
||||
|
||||
// Products Section
|
||||
productsTitle: 'PRODUKTE & LÖSUNGEN',
|
||||
productsSubtitle: 'Professionelle Werkzeuge für moderne Ermittlungsarbeit',
|
||||
|
||||
// Professional Toolbox
|
||||
productToolboxTitle: 'Professional Toolbox',
|
||||
productToolboxDesc: 'Eine leistungsstarke Desktop-Anwendung mit fünf essentiellen Tools für behördliche OSINT-Ermittler und Analysten. Modernes Design, intuitive Bedienung, professionelle Funktionen.',
|
||||
expandDetails: 'Details anzeigen',
|
||||
hideDetails: 'Details verbergen',
|
||||
|
||||
// Tools
|
||||
tool1Title: 'Metadata Analyzer',
|
||||
tool1Feature1: 'Extrahiert versteckte Informationen (EXIF, GPS, Erstellungsdaten)',
|
||||
tool1Feature2: 'Forensische Analyse von Dokumenten & Bildern',
|
||||
tool1Feature3: 'Export als JSON',
|
||||
|
||||
tool2Title: 'Screen Recorder',
|
||||
tool2Feature1: 'Bildschirmaufnahme mit Audio (System & Mikrofon)',
|
||||
tool2Feature2: 'Bereichsauswahl oder Vollbild',
|
||||
tool2Feature3: 'Wählbare Qualitätsstufen',
|
||||
|
||||
tool3Title: 'Video Crawler',
|
||||
tool3Feature1: 'Download von Videos aus 1000+ Plattformen',
|
||||
tool3Feature2: 'Automatischer Untertitel-Download',
|
||||
tool3Feature3: 'Qualitätsauswahl',
|
||||
|
||||
tool4Title: 'Website Crawler',
|
||||
tool4Feature1: 'Archiviert Webseiten offline',
|
||||
tool4Feature2: 'Einstellbare Crawling-Tiefe',
|
||||
tool4Feature3: 'Erhält Originalstruktur inkl. CSS, JS & Medien',
|
||||
|
||||
tool5Title: 'Multimedia Converter',
|
||||
tool5Feature1: 'Konvertierung von Bildern, Videos, Audio',
|
||||
tool5Feature2: 'Batch-Verarbeitung',
|
||||
tool5Feature3: 'Drag & Drop Unterstützung',
|
||||
|
||||
// AccountForger
|
||||
productAccountForgerTitle: 'AccountForger',
|
||||
accessRestricted: 'Zugang nur mit Berechtigung',
|
||||
protectedProductDesc: 'Dieses Produkt ist speziell für autorisierte Behörden entwickelt und erfordert eine Authentifizierung.',
|
||||
loginForAccess: 'Anmelden für Zugriff',
|
||||
|
||||
// Footer
|
||||
footerCompanyTitle: 'IntelSight UG (haftungsbeschränkt)',
|
||||
footerCompanyAddress1: 'Gladbacher Strasse 3-5',
|
||||
footerCompanyAddress2: '40764 Langenfeld',
|
||||
footerNavTitle: 'Navigation',
|
||||
footerNavHome: 'Startseite',
|
||||
footerNavAbout: 'Über uns',
|
||||
footerNavProducts: 'Produkte',
|
||||
footerNavContact: 'Kontakt',
|
||||
|
||||
footerLegalTitle: 'Rechtliches',
|
||||
footerImprint: 'Impressum',
|
||||
footerPrivacy: 'Datenschutz',
|
||||
footerTerms: 'AGB',
|
||||
|
||||
footerContactTitle: 'Kontakt',
|
||||
copyright: '© 2025 IntelSight UG (haftungsbeschränkt). Alle Rechte vorbehalten.',
|
||||
|
||||
// Modal texts
|
||||
authRequired: 'Authentifizierung erforderlich',
|
||||
authDescription: 'Dieser Bereich ist nur für autorisierte Behörden zugänglich.',
|
||||
accessCode: 'Zugangscode',
|
||||
accessCodePlaceholder: 'Bitte Zugangscode eingeben',
|
||||
grantAccess: 'Zugang gewähren',
|
||||
noAccess: 'Noch keinen Zugang?',
|
||||
contactUs: 'Kontaktieren Sie uns',
|
||||
accessGranted: 'Zugang gewährt! AccountForger wird geladen...',
|
||||
wrongCode: 'Falscher Zugangscode. Bitte versuchen Sie es erneut.',
|
||||
demoRequestAlert: 'Demo-Anfrage-Funktion würde hier implementiert werden',
|
||||
contactFormSuccess: 'Vielen Dank für Ihre Nachricht! Wir werden uns schnellstmöglich bei Ihnen melden.'
|
||||
},
|
||||
|
||||
en: {
|
||||
// Page meta
|
||||
pageTitle: 'IntelSight - Security Made in Germany',
|
||||
|
||||
// Navigation
|
||||
skipNav: 'Skip to main content',
|
||||
navHome: 'Home',
|
||||
navAbout: 'About Us',
|
||||
navProducts: 'Products & Solutions',
|
||||
navContact: 'Contact',
|
||||
langSwitch: 'EN | DE',
|
||||
|
||||
// Hero Section
|
||||
heroTitle: 'SECURITY MADE IN GERMANY',
|
||||
heroSubtitle: 'Specialist for highly secure, customized IT solutions for government agencies',
|
||||
|
||||
// Trust Indicators
|
||||
indicatorAvailability: 'Availability',
|
||||
indicatorTrust: 'Government agencies trust us',
|
||||
indicatorSupport: 'Support',
|
||||
|
||||
// Scroll Indicator
|
||||
scrollToExplore: 'Scroll to Explore',
|
||||
|
||||
// About Section
|
||||
aboutTitle: 'About Us',
|
||||
aboutSubtitle: 'Your Partner for Secure Government Software',
|
||||
|
||||
// About Tabs
|
||||
tabWhoWeAre: 'Company',
|
||||
tabMission: 'Mission & Values',
|
||||
tabCompetencies: 'Core Competencies',
|
||||
tabWhyUs: 'Our Promise',
|
||||
|
||||
// Who We Are
|
||||
whoWeAreTitle: 'Company',
|
||||
whoWeArePara1: 'IntelSight UG is your <strong>specialist for highly secure, customized IT solutions</strong> from North Rhine-Westphalia. We develop innovative software specifically for government security and law enforcement agencies.',
|
||||
whoWeArePara2: 'Our approach combines cutting-edge technology with a deep understanding of the special requirements of government agencies. The balance between security, efficiency and legally compliant implementation is at the center of our work.',
|
||||
locationBadge: 'North Rhine-Westphalia, Germany',
|
||||
nrwLabel: 'North Rhine-Westphalia',
|
||||
headquartersLabel: 'Our Location: Langenfeld',
|
||||
|
||||
// Mission & Values
|
||||
missionTitle: 'Our Mission',
|
||||
missionStatement: 'We create <strong>efficient, secure and data protection compliant solutions</strong> for modern law enforcement and security agencies.',
|
||||
valueIntegrityTitle: 'Integrity',
|
||||
valueIntegrityDesc: 'Highest ethical standards in everything we do',
|
||||
valueTransparencyTitle: 'Transparency',
|
||||
valueTransparencyDesc: 'Open communication and comprehensible processes',
|
||||
valueDemocracyTitle: 'Democratic Principles',
|
||||
valueDemocracyDesc: 'Cooperation only with agencies in accordance with the liberal democratic basic order',
|
||||
principleNote: '<strong>Our Goal:</strong> Technology that strengthens law and security and protects the liberal democratic basic order.',
|
||||
|
||||
// Competencies
|
||||
competenciesTitle: 'Our Core Competencies',
|
||||
comp1Title: 'Agency-Specific Software',
|
||||
comp1Desc: 'Development with highest security standards, tailored for government requirements',
|
||||
comp2Title: 'Intuitive Operating Concepts',
|
||||
comp2Desc: 'User-friendly interfaces despite complex functions for efficient work',
|
||||
comp3Title: 'Long-term Support',
|
||||
comp3Desc: 'Continuous security updates and reliable maintenance throughout the entire lifecycle',
|
||||
|
||||
// Why Us
|
||||
whyUsTitle: 'Why IntelSight UG?',
|
||||
why1Title: 'Close Collaboration',
|
||||
why1Desc: 'We work hand in hand with our customers for customized solutions',
|
||||
why2Title: 'Made in Germany',
|
||||
why2Desc: 'Clear, robust and secure software according to German quality standards',
|
||||
why3Title: 'Reliable Partnership',
|
||||
why3Desc: 'Based on shared values and long-term trust',
|
||||
why4Title: 'Sustainability',
|
||||
why4Desc: 'Focus on security, professionalism & future-proof solutions',
|
||||
|
||||
// Products Section
|
||||
productsTitle: 'Products & Solutions',
|
||||
productsSubtitle: 'Professional Tools for Modern Investigation Work',
|
||||
|
||||
// Professional Toolbox
|
||||
productToolboxTitle: 'Professional Toolbox',
|
||||
productToolboxDesc: 'A powerful desktop application with five essential tools for government OSINT investigators and analysts. Modern design, intuitive operation, professional functions.',
|
||||
expandDetails: 'Show Details',
|
||||
hideDetails: 'Hide Details',
|
||||
|
||||
// Tools
|
||||
tool1Title: 'Metadata Analyzer',
|
||||
tool1Feature1: 'Extracts hidden information (EXIF, GPS, creation dates)',
|
||||
tool1Feature2: 'Forensic analysis of documents & images',
|
||||
tool1Feature3: 'Export as JSON',
|
||||
|
||||
tool2Title: 'Screen Recorder',
|
||||
tool2Feature1: 'Screen recording with audio (system & microphone)',
|
||||
tool2Feature2: 'Area selection or full screen',
|
||||
tool2Feature3: 'Selectable quality levels',
|
||||
|
||||
tool3Title: 'Video Crawler',
|
||||
tool3Feature1: 'Download videos from 1000+ platforms',
|
||||
tool3Feature2: 'Automatic subtitle download',
|
||||
tool3Feature3: 'Quality selection',
|
||||
|
||||
tool4Title: 'Website Crawler',
|
||||
tool4Feature1: 'Archives websites offline',
|
||||
tool4Feature2: 'Adjustable crawling depth',
|
||||
tool4Feature3: 'Preserves original structure incl. CSS, JS & media',
|
||||
|
||||
tool5Title: 'Multimedia Converter',
|
||||
tool5Feature1: 'Conversion of images, videos, audio',
|
||||
tool5Feature2: 'Batch processing',
|
||||
tool5Feature3: 'Drag & Drop support',
|
||||
|
||||
// AccountForger
|
||||
productAccountForgerTitle: 'AccountForger',
|
||||
accessRestricted: 'Access by authorization only',
|
||||
protectedProductDesc: 'This product is specifically developed for authorized agencies and requires authentication.',
|
||||
loginForAccess: 'Login for Access',
|
||||
|
||||
// Footer
|
||||
footerCompanyTitle: 'IntelSight UG (haftungsbeschränkt)',
|
||||
footerCompanyAddress1: 'Gladbacher Strasse 3-5',
|
||||
footerCompanyAddress2: '40764 Langenfeld',
|
||||
footerNavTitle: 'Navigation',
|
||||
footerNavHome: 'Home',
|
||||
footerNavAbout: 'About Us',
|
||||
footerNavProducts: 'Products',
|
||||
footerNavContact: 'Contact',
|
||||
|
||||
footerLegalTitle: 'Legal',
|
||||
footerImprint: 'Imprint',
|
||||
footerPrivacy: 'Privacy Policy',
|
||||
footerTerms: 'Terms & Conditions',
|
||||
|
||||
footerContactTitle: 'Contact',
|
||||
copyright: '© 2025 IntelSight UG (haftungsbeschränkt). All rights reserved.',
|
||||
|
||||
// Modal texts
|
||||
authRequired: 'Authentication Required',
|
||||
authDescription: 'This area is only accessible to authorized agencies.',
|
||||
accessCode: 'Access Code',
|
||||
accessCodePlaceholder: 'Please enter access code',
|
||||
grantAccess: 'Grant Access',
|
||||
noAccess: 'No access yet?',
|
||||
contactUs: 'Contact Us',
|
||||
accessGranted: 'Access granted! AccountForger is loading...',
|
||||
wrongCode: 'Wrong access code. Please try again.',
|
||||
demoRequestAlert: 'Demo request function would be implemented here',
|
||||
contactFormSuccess: 'Thank you for your message! We will get back to you as soon as possible.'
|
||||
}
|
||||
};
|
||||
|
||||
// Current language state
|
||||
let currentLanguage = CONFIG.I18N.DEFAULT_LANGUAGE;
|
||||
|
||||
/**
|
||||
* Initialize the translation system
|
||||
*/
|
||||
function initTranslations() {
|
||||
// Try to get saved language from localStorage
|
||||
const savedLanguage = localStorage.getItem(CONFIG.I18N.STORAGE_KEY);
|
||||
if (savedLanguage && CONFIG.I18N.SUPPORTED_LANGUAGES.includes(savedLanguage)) {
|
||||
currentLanguage = savedLanguage;
|
||||
}
|
||||
|
||||
// Apply initial translations
|
||||
applyTranslations(currentLanguage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to a different language
|
||||
* @param {string} language - Language code ('de' or 'en')
|
||||
*/
|
||||
function switchLanguage(language) {
|
||||
if (!CONFIG.I18N.SUPPORTED_LANGUAGES.includes(language)) {
|
||||
console.error(`Language '${language}' is not supported`);
|
||||
return;
|
||||
}
|
||||
|
||||
currentLanguage = language;
|
||||
localStorage.setItem(CONFIG.I18N.STORAGE_KEY, language);
|
||||
applyTranslations(language);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply translations to all elements with data-translate attribute
|
||||
* @param {string} language - Language code to apply
|
||||
*/
|
||||
function applyTranslations(language) {
|
||||
const t = translations[language];
|
||||
|
||||
if (!t) {
|
||||
console.error(`Translations for language '${language}' not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update page title
|
||||
document.title = t.pageTitle;
|
||||
|
||||
// Update HTML lang attribute
|
||||
document.documentElement.lang = language;
|
||||
|
||||
// Update all translatable elements
|
||||
document.querySelectorAll(SELECTORS.TRANSLATABLE).forEach(element => {
|
||||
const key = element.getAttribute(DATA_ATTRS.TRANSLATE);
|
||||
|
||||
if (t[key]) {
|
||||
// Check if content contains HTML tags
|
||||
if (t[key].includes('<strong>') || t[key].includes('<em>')) {
|
||||
element.innerHTML = t[key];
|
||||
} else {
|
||||
element.textContent = t[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update language toggle button
|
||||
const langToggle = document.querySelector(SELECTORS.LANG_TOGGLE);
|
||||
if (langToggle) {
|
||||
langToggle.textContent = t.langSwitch;
|
||||
langToggle.setAttribute(DATA_ATTRS.LANG, language);
|
||||
}
|
||||
|
||||
// Update expand button text if it exists
|
||||
updateExpandButtonText(language);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update expand button text based on current state
|
||||
* @param {string} language - Current language
|
||||
*/
|
||||
function updateExpandButtonText(language) {
|
||||
const expandButton = document.querySelector(SELECTORS.EXPAND_BUTTON);
|
||||
if (expandButton) {
|
||||
const expandText = expandButton.querySelector('.expand-text');
|
||||
const isExpanded = expandButton.getAttribute(DATA_ATTRS.EXPANDED) === 'true';
|
||||
const t = translations[language];
|
||||
|
||||
if (expandText && t) {
|
||||
expandText.textContent = isExpanded ? t.hideDetails : t.expandDetails;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific translation string
|
||||
* @param {string} key - Translation key
|
||||
* @returns {string} Translated text
|
||||
*/
|
||||
function getTranslation(key) {
|
||||
return translations[currentLanguage][key] || key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current language
|
||||
* @returns {string} Current language code
|
||||
*/
|
||||
function getCurrentLanguage() {
|
||||
return currentLanguage;
|
||||
}
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren