600 lines
19 KiB
JavaScript
600 lines
19 KiB
JavaScript
/**
|
|
* Main JavaScript file for argobox.com
|
|
* Handles animations, interactions, and dynamic content
|
|
*/
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Initialize all website functionality
|
|
initNavigation();
|
|
initParticlesAndIcons();
|
|
initRoleAnimation();
|
|
initTerminalTyping();
|
|
initSolutionsCarousel();
|
|
initScrollReveal();
|
|
updateMetrics();
|
|
updateYear();
|
|
|
|
// Initialize form handling
|
|
const contactForm = document.getElementById('contact-form');
|
|
if (contactForm) {
|
|
initFormHandling(contactForm);
|
|
}
|
|
|
|
// Highlight navigation on scroll
|
|
setupScrollSpy();
|
|
|
|
// Setup role switching with improved timing
|
|
setupRoleSwitching();
|
|
|
|
// Other initializations...
|
|
setupFloatingIcons();
|
|
setupParticles();
|
|
setupRevealAnimations();
|
|
initializeSlider();
|
|
|
|
// Contact form handling
|
|
setupContactForm();
|
|
|
|
// Mobile menu toggle
|
|
document.querySelector('.menu-toggle').addEventListener('click', function() {
|
|
document.querySelector('.nav-menu').classList.toggle('active');
|
|
});
|
|
|
|
// Navbar effects on scroll
|
|
window.addEventListener('scroll', function() {
|
|
const navbar = document.querySelector('.navbar');
|
|
|
|
if (window.scrollY > 50) {
|
|
navbar.classList.add('scrolled');
|
|
} else {
|
|
navbar.classList.remove('scrolled');
|
|
}
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Set up navigation functionality - mobile menu and scroll spy
|
|
*/
|
|
function initNavigation() {
|
|
// Mobile menu toggle
|
|
const menuToggle = document.querySelector('.menu-toggle');
|
|
const navMenu = document.querySelector('.nav-menu');
|
|
|
|
if (menuToggle && navMenu) {
|
|
menuToggle.addEventListener('click', function() {
|
|
navMenu.classList.toggle('active');
|
|
menuToggle.setAttribute('aria-expanded',
|
|
menuToggle.getAttribute('aria-expanded') === 'true' ? 'false' : 'true');
|
|
});
|
|
}
|
|
|
|
// Navigation scroll spy
|
|
const sections = document.querySelectorAll('section[id]');
|
|
const navLinks = document.querySelectorAll('.nav-link');
|
|
|
|
function updateActiveNavLink() {
|
|
let scrollPosition = window.scrollY + 100;
|
|
|
|
sections.forEach(section => {
|
|
const sectionTop = section.offsetTop;
|
|
const sectionHeight = section.offsetHeight;
|
|
const sectionId = section.getAttribute('id');
|
|
|
|
if (scrollPosition >= sectionTop && scrollPosition < sectionTop + sectionHeight) {
|
|
navLinks.forEach(link => {
|
|
link.classList.remove('active');
|
|
if (link.getAttribute('href') === `#${sectionId}`) {
|
|
link.classList.add('active');
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
// Navbar style change on scroll
|
|
const navbar = document.querySelector('.navbar');
|
|
|
|
function updateNavbarStyle() {
|
|
if (window.scrollY > 50) {
|
|
navbar?.classList.add('scrolled');
|
|
} else {
|
|
navbar?.classList.remove('scrolled');
|
|
}
|
|
}
|
|
|
|
window.addEventListener('scroll', () => {
|
|
updateActiveNavLink();
|
|
updateNavbarStyle();
|
|
});
|
|
|
|
// Initial call to set correct states
|
|
updateActiveNavLink();
|
|
updateNavbarStyle();
|
|
}
|
|
|
|
/**
|
|
* Create background particles and floating tech icons
|
|
*/
|
|
function initParticlesAndIcons() {
|
|
createBackgroundParticles();
|
|
createFloatingIcons();
|
|
}
|
|
|
|
/**
|
|
* Create animated background particles
|
|
*/
|
|
function createBackgroundParticles() {
|
|
const particlesContainer = document.getElementById('particles-container');
|
|
|
|
if (!particlesContainer) return;
|
|
|
|
particlesContainer.innerHTML = '';
|
|
|
|
for (let i = 0; i < 40; i++) {
|
|
const particle = document.createElement('div');
|
|
particle.classList.add('particle');
|
|
|
|
// Random size, opacity, and position
|
|
const size = Math.random() * 4 + 1;
|
|
const opacity = Math.random() * 0.3 + 0.1;
|
|
|
|
particle.style.width = `${size}px`;
|
|
particle.style.height = `${size}px`;
|
|
particle.style.opacity = opacity;
|
|
|
|
// Position randomly with some clustering toward top areas
|
|
const xPos = Math.random() * 100;
|
|
const yPos = Math.random() * 100;
|
|
|
|
particle.style.left = `${xPos}%`;
|
|
particle.style.top = `${yPos}%`;
|
|
|
|
// Animation properties
|
|
const duration = Math.random() * 20 + 10;
|
|
particle.style.animationDuration = `${duration}s`;
|
|
particle.style.animationDelay = `${Math.random() * -duration}s`;
|
|
|
|
// Add particle animation
|
|
particle.style.animation = `float-particle ${duration}s linear infinite`;
|
|
|
|
particlesContainer.appendChild(particle);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create floating tech icons in the background
|
|
*/
|
|
function createFloatingIcons() {
|
|
const iconContainer = document.getElementById('floating-icons');
|
|
|
|
if (!iconContainer) return;
|
|
|
|
iconContainer.innerHTML = '';
|
|
|
|
// Tech-related unicode symbols and fontawesome classes
|
|
const icons = [
|
|
'⚙️', '💻', '🔒', '🔌', '🌐', '☁️', '📊',
|
|
'fa-server', 'fa-network-wired', 'fa-database',
|
|
'fa-code-branch', 'fa-cloud', 'fa-shield-alt'
|
|
];
|
|
|
|
for (let i = 0; i < 12; i++) {
|
|
const icon = document.createElement('div');
|
|
icon.classList.add('floating-icon');
|
|
|
|
const iconType = icons[Math.floor(Math.random() * icons.length)];
|
|
|
|
// Handle both unicode and font awesome
|
|
if (iconType.startsWith('fa-')) {
|
|
const faIcon = document.createElement('i');
|
|
faIcon.className = `fas ${iconType}`;
|
|
icon.appendChild(faIcon);
|
|
} else {
|
|
icon.textContent = iconType;
|
|
}
|
|
|
|
// Random size and position
|
|
const size = Math.random() * 24 + 16;
|
|
icon.style.fontSize = `${size}px`;
|
|
|
|
// Position
|
|
icon.style.left = `${Math.random() * 100}%`;
|
|
icon.style.bottom = `-50px`;
|
|
|
|
// Animation
|
|
icon.style.animationDuration = `${Math.random() * 30 + 20}s`;
|
|
icon.style.animationDelay = `${Math.random() * 10}s`;
|
|
|
|
iconContainer.appendChild(icon);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize role rotation in the hero section
|
|
*/
|
|
function initRoleAnimation() {
|
|
const roles = document.querySelectorAll('.role');
|
|
const description = document.getElementById('role-description');
|
|
let currentIndex = 0;
|
|
|
|
function updateRole() {
|
|
roles.forEach(role => role.classList.remove('active'));
|
|
roles[currentIndex].classList.add('active');
|
|
description.textContent = roles[currentIndex].getAttribute('data-description');
|
|
description.style.opacity = '0';
|
|
setTimeout(() => {
|
|
description.style.opacity = '1';
|
|
}, 100);
|
|
currentIndex = (currentIndex + 1) % roles.length;
|
|
}
|
|
|
|
updateRole(); // Set initial state
|
|
setInterval(updateRole, 5000); // Switch roles every 5 seconds
|
|
}
|
|
|
|
/**
|
|
* Initialize terminal typing animation in the hero section
|
|
*/
|
|
function initTerminalTyping() {
|
|
const terminalText = document.getElementById('terminal-text');
|
|
if (!terminalText) return;
|
|
|
|
const terminalMessages = [
|
|
"> Ready for deployment...",
|
|
"> Reducing operational costs by 30%",
|
|
"> Improving system reliability to 99.9%",
|
|
"> Accelerating digital transformation",
|
|
"> Enhancing security compliance",
|
|
"> Streamlining IT workflows",
|
|
"> Optimizing infrastructure performance",
|
|
"> Implementing best practices",
|
|
"> Supporting business objectives"
|
|
];
|
|
|
|
let currentMessage = 0;
|
|
|
|
function typeMessage(message, index = 0) {
|
|
if (index < message.length) {
|
|
terminalText.textContent = message.substring(0, index + 1);
|
|
setTimeout(() => typeMessage(message, index + 1), 50 + Math.random() * 50);
|
|
} else {
|
|
// Wait before clearing and typing next message
|
|
setTimeout(clearAndTypeNext, 3000);
|
|
}
|
|
}
|
|
|
|
function clearAndTypeNext() {
|
|
// Clear the current text with a backspace effect
|
|
const currentText = terminalText.textContent;
|
|
|
|
function backspace(length = currentText.length) {
|
|
if (length > 0) {
|
|
terminalText.textContent = currentText.substring(0, length - 1);
|
|
setTimeout(() => backspace(length - 1), 20);
|
|
} else {
|
|
// Move to next message
|
|
currentMessage = (currentMessage + 1) % terminalMessages.length;
|
|
setTimeout(() => typeMessage(terminalMessages[currentMessage]), 500);
|
|
}
|
|
}
|
|
|
|
backspace();
|
|
}
|
|
|
|
// Start the typing animation with the first message
|
|
typeMessage(terminalMessages[0]);
|
|
}
|
|
|
|
/**
|
|
* Initialize the solutions carousel
|
|
*/
|
|
function initSolutionsCarousel() {
|
|
const slides = document.querySelectorAll('.solution-slide');
|
|
const dots = document.querySelectorAll('.slider-dot');
|
|
|
|
if (slides.length === 0 || dots.length === 0) return;
|
|
|
|
let currentSlide = 0;
|
|
let slideInterval;
|
|
|
|
function showSlide(index) {
|
|
// Hide all slides
|
|
slides.forEach(slide => slide.classList.remove('active'));
|
|
dots.forEach(dot => dot.classList.remove('active'));
|
|
|
|
// Show selected slide
|
|
slides[index].classList.add('active');
|
|
dots[index].classList.add('active');
|
|
|
|
currentSlide = index;
|
|
}
|
|
|
|
function nextSlide() {
|
|
const next = (currentSlide + 1) % slides.length;
|
|
showSlide(next);
|
|
}
|
|
|
|
// Add click events to dots
|
|
dots.forEach((dot, index) => {
|
|
dot.addEventListener('click', () => {
|
|
clearInterval(slideInterval);
|
|
showSlide(index);
|
|
|
|
// Restart automatic rotation
|
|
slideInterval = setInterval(nextSlide, 5000);
|
|
});
|
|
});
|
|
|
|
// Start automatic rotation
|
|
slideInterval = setInterval(nextSlide, 5000);
|
|
|
|
// Show first slide initially
|
|
showSlide(0);
|
|
}
|
|
|
|
/**
|
|
* Add scroll reveal animations to elements
|
|
*/
|
|
function initScrollReveal() {
|
|
const revealElements = document.querySelectorAll('.section-header, .service-card, .project-card, .lab-card, .timeline-item, .contact-item');
|
|
|
|
revealElements.forEach(element => {
|
|
element.classList.add('reveal');
|
|
});
|
|
|
|
function checkReveal() {
|
|
revealElements.forEach(element => {
|
|
const elementTop = element.getBoundingClientRect().top;
|
|
const elementVisible = 150;
|
|
|
|
if (elementTop < window.innerHeight - elementVisible) {
|
|
element.classList.add('active');
|
|
}
|
|
});
|
|
}
|
|
|
|
// Initial check
|
|
checkReveal();
|
|
|
|
// Check on scroll
|
|
window.addEventListener('scroll', checkReveal);
|
|
}
|
|
|
|
/**
|
|
* Update metrics values periodically to simulate live data
|
|
*/
|
|
function updateMetrics() {
|
|
const metrics = {
|
|
'CPU Usage': { min: 30, max: 60, element: null },
|
|
'Memory': { min: 45, max: 70, element: null },
|
|
'Storage': { min: 60, max: 75, element: null },
|
|
'Network': { min: 15, max: 40, element: null }
|
|
};
|
|
|
|
// Get all metric elements
|
|
document.querySelectorAll('.metric').forEach(metric => {
|
|
const nameElement = metric.querySelector('.metric-name');
|
|
if (nameElement && metrics[nameElement.textContent]) {
|
|
metrics[nameElement.textContent].element = metric;
|
|
}
|
|
});
|
|
|
|
function updateMetricValues() {
|
|
Object.keys(metrics).forEach(key => {
|
|
const metric = metrics[key];
|
|
if (!metric.element) return;
|
|
|
|
const valueEl = metric.element.querySelector('.metric-value');
|
|
const progressEl = metric.element.querySelector('.metric-progress');
|
|
|
|
if (valueEl && progressEl) {
|
|
const newValue = Math.floor(Math.random() * (metric.max - metric.min)) + metric.min;
|
|
valueEl.textContent = `${newValue}%`;
|
|
progressEl.style.width = `${newValue}%`;
|
|
}
|
|
});
|
|
}
|
|
|
|
// Update metrics every 5 seconds
|
|
setInterval(updateMetricValues, 5000);
|
|
}
|
|
|
|
/**
|
|
* Initialize contact form handling
|
|
*/
|
|
function initFormHandling(form) {
|
|
// Prevent multiple initializations
|
|
if (form.hasAttribute('data-initialized')) return;
|
|
form.setAttribute('data-initialized', 'true');
|
|
|
|
form.addEventListener('submit', async function (e) {
|
|
e.preventDefault();
|
|
|
|
const submitButton = form.querySelector('button[type="submit"]');
|
|
const originalButtonText = submitButton.innerHTML;
|
|
submitButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Sending...';
|
|
submitButton.disabled = true;
|
|
|
|
const notification = document.getElementById('form-notification');
|
|
const notificationIcon = notification.querySelector('i');
|
|
const notificationText = notification.querySelector('.notification-text');
|
|
|
|
// Hide any existing notification with animation
|
|
if (notification.style.display === 'flex') {
|
|
notification.classList.add('hiding');
|
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
notification.classList.remove('hiding');
|
|
notification.style.display = 'none';
|
|
}
|
|
|
|
const formData = {
|
|
name: form.elements['name'].value,
|
|
email: form.elements['email'].value,
|
|
subject: form.elements['subject'].value,
|
|
message: form.elements['message'].value
|
|
};
|
|
|
|
try {
|
|
const res = await fetch("/api/send-email", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json"
|
|
},
|
|
body: JSON.stringify(formData)
|
|
});
|
|
|
|
const data = await res.json();
|
|
|
|
notification.style.display = 'flex';
|
|
|
|
if (res.ok) {
|
|
notification.classList.remove('error');
|
|
notification.classList.add('success');
|
|
notificationIcon.className = 'fas fa-check-circle';
|
|
notificationText.textContent = "Message sent successfully! We'll get back to you soon.";
|
|
form.reset();
|
|
} else {
|
|
notification.classList.remove('success');
|
|
notification.classList.add('error');
|
|
notificationIcon.className = 'fas fa-exclamation-circle';
|
|
notificationText.textContent = data.error + (data.details ? `: ${data.details}` : '');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error("Form submission error:", error);
|
|
notification.style.display = 'flex';
|
|
notification.classList.remove('success');
|
|
notification.classList.add('error');
|
|
notificationIcon.className = 'fas fa-exclamation-circle';
|
|
notificationText.textContent = `Error: ${error.message}`;
|
|
} finally {
|
|
submitButton.innerHTML = originalButtonText;
|
|
submitButton.disabled = false;
|
|
|
|
// Hide notification after 5 seconds with animation
|
|
setTimeout(() => {
|
|
notification.classList.add('hiding');
|
|
setTimeout(() => {
|
|
notification.classList.remove('hiding');
|
|
notification.style.display = 'none';
|
|
}, 300);
|
|
}, 5000);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Update copyright year in the footer
|
|
*/
|
|
function updateYear() {
|
|
const yearElement = document.getElementById('current-year');
|
|
if (yearElement) {
|
|
yearElement.textContent = new Date().getFullYear();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets up scroll spy functionality to highlight nav links
|
|
* as the user scrolls through different sections
|
|
*/
|
|
function setupScrollSpy() {
|
|
const sections = document.querySelectorAll('section[id]');
|
|
const navLinks = document.querySelectorAll('.nav-link');
|
|
|
|
window.addEventListener('scroll', function() {
|
|
let current = '';
|
|
|
|
sections.forEach(section => {
|
|
const sectionTop = section.offsetTop - 100;
|
|
const sectionHeight = section.offsetHeight;
|
|
|
|
if (window.scrollY >= sectionTop && window.scrollY < sectionTop + sectionHeight) {
|
|
current = section.getAttribute('id');
|
|
}
|
|
});
|
|
|
|
navLinks.forEach(link => {
|
|
link.classList.remove('active');
|
|
if (link.getAttribute('href') === `#${current}`) {
|
|
link.classList.add('active');
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Sets up automated role switching with better timing and transitions
|
|
*/
|
|
function setupRoleSwitching() {
|
|
const roles = document.querySelectorAll('.role');
|
|
const roleDescription = document.getElementById('role-description');
|
|
let currentRoleIndex = 0;
|
|
|
|
// Initial role description
|
|
if (roles.length > 0 && roleDescription) {
|
|
roleDescription.textContent = roles[0].getAttribute('data-description');
|
|
roles[0].classList.add('active');
|
|
}
|
|
|
|
// Switch roles every 5 seconds with fade transition
|
|
setInterval(() => {
|
|
// Remove active class from current role
|
|
roles[currentRoleIndex].classList.remove('active');
|
|
|
|
// Move to the next role or back to the first
|
|
currentRoleIndex = (currentRoleIndex + 1) % roles.length;
|
|
|
|
// Add active class to new role
|
|
roles[currentRoleIndex].classList.add('active');
|
|
|
|
// Update the role description with fade effect
|
|
if (roleDescription) {
|
|
roleDescription.style.opacity = '0';
|
|
|
|
setTimeout(() => {
|
|
roleDescription.textContent = roles[currentRoleIndex].getAttribute('data-description');
|
|
roleDescription.style.opacity = '1';
|
|
}, 300);
|
|
}
|
|
}, 5000);
|
|
}
|
|
|
|
// Replace the existing setupFloatingIcons with this improved version
|
|
function setupFloatingIcons() {
|
|
const container = document.getElementById('floating-icons');
|
|
if (!container) return;
|
|
|
|
// Clear existing icons
|
|
container.innerHTML = '';
|
|
|
|
// Tech icons to display
|
|
const icons = [
|
|
'fa-server', 'fa-network-wired', 'fa-shield-alt',
|
|
'fa-cloud', 'fa-code-branch', 'fa-database',
|
|
'fa-laptop-code', 'fa-lock', 'fa-terminal'
|
|
];
|
|
|
|
// Create floating icons
|
|
icons.forEach(icon => {
|
|
const iconElement = document.createElement('div');
|
|
iconElement.className = 'floating-icon';
|
|
iconElement.innerHTML = `<i class="fas ${icon}"></i>`;
|
|
|
|
// Random starting position
|
|
const randomX = Math.floor(Math.random() * 100);
|
|
const randomY = Math.floor(Math.random() * 100);
|
|
const randomDelay = Math.random() * 5;
|
|
const randomDuration = 20 + Math.random() * 10;
|
|
|
|
// Apply styles
|
|
iconElement.style.left = `${randomX}%`;
|
|
iconElement.style.top = `${randomY}%`;
|
|
iconElement.style.animationDelay = `${randomDelay}s`;
|
|
iconElement.style.animationDuration = `${randomDuration}s`;
|
|
|
|
// Add to container
|
|
container.appendChild(iconElement);
|
|
});
|
|
}
|
|
|
|
// More existing code...
|