/** * Tech-Onepager JavaScript - Interactive Elements and Animations * System being built aesthetic with smooth micro-animations */ document.addEventListener('DOMContentLoaded', function() { // ===== GLOBAL VARIABLES ===== let currentTooltip = null; // ===== SYSTEM GRAPHIC ANIMATIONS ===== const systemGraphic = document.getElementById('systemGraphic'); const connections = document.getElementById('connections'); const dataPoints = document.getElementById('dataPoints'); if (systemGraphic && connections) { initializeSystemGraphic(); } function initializeSystemGraphic() { const nodes = systemGraphic.querySelectorAll('.node'); const centralNode = systemGraphic.querySelector('.central-node'); // Draw connection lines after layout is complete requestAnimationFrame(() => { requestAnimationFrame(drawConnections); }); // Also redraw after a short delay for CSS transitions setTimeout(drawConnections, 300); setTimeout(drawConnections, 600); setTimeout(drawConnections, 1000); // Redraw on resize with multiple delayed calls to handle CSS transitions let resizeTimeout; let resizeRAF; window.addEventListener('resize', function() { // Cancel any pending animation frame if (resizeRAF) cancelAnimationFrame(resizeRAF); clearTimeout(resizeTimeout); // Immediate redraw resizeRAF = requestAnimationFrame(drawConnections); // Delayed redraws to catch CSS transition completion resizeTimeout = setTimeout(() => { drawConnections(); setTimeout(drawConnections, 200); setTimeout(drawConnections, 400); }, 100); }); // Redraw on orientation change (mobile) window.addEventListener('orientationchange', function() { setTimeout(drawConnections, 300); setTimeout(drawConnections, 600); setTimeout(drawConnections, 1000); }); // Also listen for transitionend on the system graphic systemGraphic.addEventListener('transitionend', function() { requestAnimationFrame(drawConnections); }); // Initialize tooltip system initializeTooltips(); // Add node interactions nodes.forEach(node => { node.addEventListener('mouseenter', function() { activateConnection(this); }); node.addEventListener('mouseleave', function() { deactivateConnections(); }); // Special handling for central node if (node.classList.contains('central-node')) { node.addEventListener('click', function() { triggerCentralNode(this); }); } }); // Scroll-based activation setupScrollActivation(); } // ===== TOOLTIP SYSTEM ===== function initializeTooltips() { const nodes = systemGraphic.querySelectorAll('.node[data-tooltip]'); nodes.forEach(node => { let hoverTimeout; node.addEventListener('mouseenter', function() { const tooltip = this.getAttribute('data-tooltip'); if (!tooltip) return; // Clear any existing timeout clearTimeout(hoverTimeout); // Set 1-second delay before showing tooltip hoverTimeout = setTimeout(() => { showTooltip(this, tooltip); }, 1000); }); node.addEventListener('mouseleave', function() { // Clear the timeout if mouse leaves before 1 second clearTimeout(hoverTimeout); // Hide any visible tooltip hideTooltip(); }); }); } function showTooltip(node, text) { // Remove any existing tooltip hideTooltip(); // Create tooltip element const tooltip = document.createElement('div'); tooltip.className = 'node-tooltip'; tooltip.textContent = text; // Position the tooltip const nodeRect = node.getBoundingClientRect(); const systemGraphicRect = systemGraphic.getBoundingClientRect(); // Calculate position relative to system graphic let left = nodeRect.left - systemGraphicRect.left + (nodeRect.width / 2) - 140; // Center horizontally let top = nodeRect.top - systemGraphicRect.top - 60; // Position above node // Adjust if tooltip goes outside bounds if (left < 10) left = 10; if (left + 280 > systemGraphicRect.width - 10) left = systemGraphicRect.width - 290; if (top < 10) top = nodeRect.top - systemGraphicRect.top + nodeRect.height + 10; // Show below if not enough space above tooltip.style.left = left + 'px'; tooltip.style.top = top + 'px'; // Add to system graphic systemGraphic.appendChild(tooltip); // Trigger show animation setTimeout(() => { tooltip.classList.add('show'); }, 10); currentTooltip = tooltip; } function hideTooltip() { if (currentTooltip) { currentTooltip.classList.remove('show'); setTimeout(() => { if (currentTooltip && currentTooltip.parentNode) { currentTooltip.parentNode.removeChild(currentTooltip); } currentTooltip = null; }, 300); } } // Cleanup function function cleanupTooltips() { hideTooltip(); } // Add cleanup on page unload window.addEventListener('beforeunload', cleanupTooltips); function drawConnections() { const centralNode = systemGraphic.querySelector('.central-node'); const otherNodes = systemGraphic.querySelectorAll('.node:not(.central-node)'); if (!centralNode || !systemGraphic || !connections) return; // Get the computed transform scale of the system graphic const computedStyle = window.getComputedStyle(systemGraphic); const transform = computedStyle.transform; let scale = 1; if (transform && transform !== 'none') { const matrix = new DOMMatrix(transform); scale = matrix.a; // Get the X scale factor } const graphicRect = systemGraphic.getBoundingClientRect(); const centralRect = centralNode.getBoundingClientRect(); // Calculate center position relative to the graphic container, accounting for scale const centerX = (centralRect.left - graphicRect.left + centralRect.width / 2) / scale; const centerY = (centralRect.top - graphicRect.top + centralRect.height / 2) / scale; // Set SVG viewBox to match container (unscaled dimensions) const viewBoxWidth = graphicRect.width / scale; const viewBoxHeight = graphicRect.height / scale; connections.setAttribute('viewBox', `0 0 ${viewBoxWidth} ${viewBoxHeight}`); connections.style.width = '100%'; connections.style.height = '100%'; connections.innerHTML = ''; otherNodes.forEach(node => { const nodeRect = node.getBoundingClientRect(); // Calculate node center position relative to the graphic container, accounting for scale const nodeX = (nodeRect.left - graphicRect.left + nodeRect.width / 2) / scale; const nodeY = (nodeRect.top - graphicRect.top + nodeRect.height / 2) / scale; // Create dashed line for better visibility const line = document.createElementNS('http://www.w3.org/2000/svg', 'line'); line.setAttribute('x1', centerX); line.setAttribute('y1', centerY); line.setAttribute('x2', nodeX); line.setAttribute('y2', nodeY); line.setAttribute('class', 'connection-line'); line.setAttribute('data-target', node.dataset.node); connections.appendChild(line); }); } function triggerCentralNode(node) { // Add triggered class for growth animation node.classList.add('triggered'); // Activate all connections const lines = connections.querySelectorAll('.connection-line'); lines.forEach((line, index) => { setTimeout(() => { line.classList.add('active'); }, index * 100); }); // Remove triggered class after animation setTimeout(() => { node.classList.remove('triggered'); // Deactivate connections setTimeout(() => { lines.forEach(line => line.classList.remove('active')); }, 500); }, 2000); } function activateConnection(node) { const targetNode = node.dataset.node; const line = connections.querySelector(`[data-target="${targetNode}"]`); if (line) { line.classList.add('active'); } } function deactivateConnections() { const lines = connections.querySelectorAll('.connection-line'); lines.forEach(line => line.classList.remove('active')); } function setupScrollActivation() { const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const lines = connections.querySelectorAll('.connection-line'); lines.forEach((line, index) => { setTimeout(() => { line.classList.add('active'); setTimeout(() => { line.classList.remove('active'); }, 2000); }, index * 200); }); } }); }, { threshold: 0.5 }); observer.observe(systemGraphic); } // ===== PROCESS LINE ANIMATION ===== const processLine = document.getElementById('processLine'); const processSteps = document.querySelectorAll('.process-step'); const processConnectors = document.querySelectorAll('.process-connector'); const stepDetails = document.querySelectorAll('.step-detail'); if (processLine) { setupProcessAnimation(); } function setupProcessAnimation() { const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { animateProcessLine(); observer.unobserve(entry.target); } }); }, { threshold: 0.3 }); observer.observe(processLine); } function animateProcessLine() { // Animate connectors sequentially processConnectors.forEach((connector, index) => { setTimeout(() => { connector.classList.add('active'); }, 500 + (index * 500)); }); // Activate steps sequentially processSteps.forEach((step, index) => { setTimeout(() => { step.classList.add('active'); activateStepDetail(index + 1); }, 200 + (index * 500)); }); } function activateStepDetail(stepNumber) { const detail = document.querySelector(`[data-step-detail="${stepNumber}"]`); if (detail) { setTimeout(() => { detail.classList.add('active'); }, 300); } } // Step click interactions processSteps.forEach((step, index) => { step.addEventListener('click', () => { // Reset all steps processSteps.forEach(s => s.classList.remove('active')); processConnectors.forEach(c => c.classList.remove('active')); stepDetails.forEach(d => d.classList.remove('active')); // Activate up to clicked step for (let i = 0; i <= index; i++) { processSteps[i].classList.add('active'); activateStepDetail(i + 1); if (i < processConnectors.length) { processConnectors[i].classList.add('active'); } } }); }); // ===== INTERACTION CARD ANIMATIONS ===== const phoneInteraction = document.getElementById('phoneInteraction'); const chatInteraction = document.getElementById('chatInteraction'); if (phoneInteraction) { setupPhoneInteraction(); } if (chatInteraction) { setupChatInteraction(); } function setupPhoneInteraction() { const microphone = phoneInteraction.querySelector('.microphone'); const pulseRing = phoneInteraction.querySelector('.pulse-ring'); const micIcon = phoneInteraction.querySelector('.mic-icon'); if (microphone && pulseRing && micIcon) { microphone.addEventListener('mouseenter', () => { pulseRing.style.animation = 'pulse 0.8s infinite'; micIcon.style.transform = 'scale(1.1)'; }); microphone.addEventListener('mouseleave', () => { pulseRing.style.animation = 'pulse 2s infinite'; micIcon.style.transform = 'scale(1)'; }); microphone.addEventListener('click', () => { // Enhanced click animation microphone.style.transform = 'scale(0.9)'; micIcon.style.transform = 'scale(0.8)'; // Create ripple effect const ripple = document.createElement('div'); ripple.style.cssText = ` position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 20px; height: 20px; background: rgba(255, 255, 255, 0.5); border-radius: 50%; animation: rippleEffect 0.6s ease-out; pointer-events: none; `; microphone.appendChild(ripple); setTimeout(() => { microphone.style.transform = 'scale(1.15)'; micIcon.style.transform = 'scale(1.1)'; setTimeout(() => { microphone.style.transform = 'scale(1)'; micIcon.style.transform = 'scale(1)'; ripple.remove(); }, 200); }, 100); // Show feedback showInteractionFeedback('KI Telefon wird verbunden...'); }); } } function setupChatInteraction() { const chatWindow = chatInteraction.querySelector('.chat-window'); const typingIndicator = chatInteraction.querySelector('.typing-indicator'); const chatHeader = chatInteraction.querySelector('.chat-header'); if (chatWindow && typingIndicator && chatHeader) { chatInteraction.addEventListener('mouseenter', () => { // Enhanced typing animation const spans = typingIndicator.querySelectorAll('span'); spans.forEach((span, index) => { span.style.animation = 'typing 1.2s infinite ease-in-out'; span.style.animationDelay = `${-0.32 + (index * 0.16)}s`; span.style.background = 'var(--accent-teal)'; }); // Animate chat window chatWindow.style.transform = 'scale(1.02)'; chatHeader.style.background = 'linear-gradient(135deg, var(--accent-green), var(--accent-teal))'; }); chatInteraction.addEventListener('mouseleave', () => { const spans = typingIndicator.querySelectorAll('span'); spans.forEach(span => { span.style.background = 'var(--primary-mid)'; }); chatWindow.style.transform = 'scale(1)'; chatHeader.style.background = 'linear-gradient(135deg, var(--accent-teal), var(--accent-green))'; }); const chatBtn = chatInteraction.querySelector('.interaction-btn.secondary'); if (chatBtn) { chatBtn.addEventListener('click', () => { // Enhanced click animation chatWindow.style.transform = 'scale(0.95)'; setTimeout(() => { chatWindow.style.transform = 'scale(1.05)'; setTimeout(() => { chatWindow.style.transform = 'scale(1)'; }, 200); }, 100); showInteractionFeedback('KI Chat wird gestartet...'); }); } } } function showInteractionFeedback(message) { // Create temporary feedback element const feedback = document.createElement('div'); feedback.style.cssText = ` position: fixed; top: 100px; left: 50%; transform: translateX(-50%); background: var(--accent-teal); color: white; padding: 16px 24px; border-radius: 8px; font-weight: 600; z-index: 10000; box-shadow: 0 8px 30px rgba(38, 166, 154, 0.4); animation: slideDown 0.3s ease; `; feedback.textContent = message; document.body.appendChild(feedback); setTimeout(() => { feedback.style.animation = 'slideUp 0.3s ease'; setTimeout(() => { feedback.remove(); }, 300); }, 2000); } // ===== HERO BUTTON INTERACTIONS ===== const kiPhoneBtn = document.getElementById('kiPhoneBtn'); const chatBtn = document.getElementById('chatBtn'); if (kiPhoneBtn) { kiPhoneBtn.addEventListener('click', () => { showInteractionFeedback('KI Telefon wird verbunden...'); // Scroll to interaction section document.getElementById('interaction')?.scrollIntoView({ behavior: 'smooth', block: 'center' }); }); } if (chatBtn) { chatBtn.addEventListener('click', () => { showInteractionFeedback('KI Chat wird gestartet...'); // Scroll to interaction section document.getElementById('interaction')?.scrollIntoView({ behavior: 'smooth', block: 'center' }); }); } // ===== SYSTEM CARD HOVER EFFECTS ===== const systemCards = document.querySelectorAll('.system-card'); systemCards.forEach(card => { card.addEventListener('mouseenter', function() { // Add subtle glow effect this.style.boxShadow = '0 25px 50px rgba(38, 166, 154, 0.2)'; // Animate internal components const flowItems = this.querySelectorAll('.flow-item, .phone-icon, .ki-processor, .crm-output'); flowItems.forEach((item, index) => { setTimeout(() => { item.style.transform = 'translateY(-2px)'; setTimeout(() => { item.style.transform = 'translateY(0)'; }, 200); }, index * 100); }); }); card.addEventListener('mouseleave', function() { this.style.boxShadow = '0 12px 30px rgba(79, 71, 71, 0.1)'; }); }); // ===== DATA CARD ANIMATIONS ===== const dataCards = document.querySelectorAll('.data-card'); const dataCardObserver = new IntersectionObserver((entries) => { entries.forEach((entry, index) => { if (entry.isIntersecting) { setTimeout(() => { entry.target.style.animation = 'fadeInUp 0.6s ease forwards'; }, index * 100); dataCardObserver.unobserve(entry.target); } }); }, { threshold: 0.1 }); dataCards.forEach(card => { dataCardObserver.observe(card); }); // ===== SMOOTH SCROLL FOR INTERNAL LINKS ===== document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function (e) { e.preventDefault(); const target = document.querySelector(this.getAttribute('href')); if (target) { target.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }); }); // ===== UTILITY ANIMATIONS ===== // Add CSS animations dynamically const style = document.createElement('style'); style.textContent = ` @keyframes slideDown { from { opacity: 0; transform: translate(-50%, -20px); } to { opacity: 1; transform: translate(-50%, 0); } } @keyframes slideUp { from { opacity: 1; transform: translate(-50%, 0); } to { opacity: 0; transform: translate(-50%, -20px); } } @keyframes fadeInUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } } @keyframes rippleEffect { from { width: 20px; height: 20px; opacity: 0.5; } to { width: 100px; height: 100px; opacity: 0; } } @keyframes shimmer { 0% { transform: translateX(-100%) translateY(-100%) rotate(45deg); opacity: 0; } 50% { opacity: 1; } 100% { transform: translateX(100%) translateY(100%) rotate(45deg); opacity: 0; } } `; document.head.appendChild(style); // ===== PERFORMANCE OPTIMIZATION ===== // Throttle scroll events let ticking = false; function requestTick() { if (!ticking) { requestAnimationFrame(updateScrollEffects); ticking = true; setTimeout(() => { ticking = false; }, 16); } } function updateScrollEffects() { // Add scroll-based effects here if needed // Parallax, fade-ins, etc. } window.addEventListener('scroll', requestTick, { passive: true }); // ===== INITIALIZATION COMPLETE ===== console.log('Tech-Onepager initialized successfully'); });