Files
Websites/Profice WebSite/scripts/tech-onepager.js
2026-03-04 09:43:38 +01:00

657 lines
23 KiB
JavaScript

/**
* 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 - removed
});
}
}
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);
// Show feedback - removed
});
}
}
}
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', () => {
// Show feedback - removed
// Scroll to interaction section
document.getElementById('interaction')?.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
});
}
if (chatBtn) {
chatBtn.addEventListener('click', () => {
// Show feedback - removed
// 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) {
const href = this.getAttribute('href');
if (!href || href === '#') return;
e.preventDefault();
const target = document.querySelector(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');
});