kh3_website/js/main.js
George Birikorang ffad4eda8a
All checks were successful
continuous-integration/drone/push Build is passing
fix: fixes nav system
2025-08-28 21:37:16 -04:00

446 lines
12 KiB
JavaScript

// Main JavaScript for FBS Website
document.addEventListener("DOMContentLoaded", function () {
// Initialize all functionality
initNavigation();
initScrollAnimations();
initSmoothScrolling();
initSlider();
initCustomCursor();
initScrollProgress();
initLoadingScreen();
initTextReveal();
initParallax();
});
// Navigation functionality - Updated to match current implementation
function initNavigation() {
const menuToggle = document.getElementById("menuToggle");
const navMenu = document.getElementById("navMenu");
const menuGrid = document.getElementById("menuGrid");
const navLinks = document.querySelectorAll(".nav-link");
// Only initialize if elements exist
if (menuToggle && navMenu && menuGrid) {
let isMenuOpen = false;
let closeBtnEl = null;
menuToggle.addEventListener("click", () => {
if (isMenuOpen) {
// Close menu
navMenu.classList.add("hidden");
if (closeBtnEl) {
closeBtnEl.remove();
closeBtnEl = null;
}
isMenuOpen = false;
} else {
// Open menu
navMenu.classList.remove("hidden");
// Keep the grid icon as is, don't transform to X
menuGrid.innerHTML = `
<span class="w-1 h-1 bg-white rounded-full"></span>
<span class="w-1 h-1 bg-white rounded-full"></span>
<span class="w-1 h-1 bg-white rounded-full"></span>
<span class="w-1 h-1 bg-white rounded-full"></span>
<span class="w-1 h-1 bg-white rounded-full"></span>
<span class="w-1 h-1 bg-white rounded-full"></span>
<span class="w-1 h-1 bg-white rounded-full"></span>
<span class="w-1 h-1 bg-white rounded-full"></span>
<span class="w-1 h-1 bg-white rounded-full"></span>
`;
// Place red X close button at the bottom of the menu panel
closeBtnEl = document.createElement("button");
closeBtnEl.setAttribute("aria-label", "Close navigation");
closeBtnEl.className =
"absolute bottom-12 left-6 text-kh3-red hover:text-white transition-colors";
closeBtnEl.innerHTML = `
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
`;
navMenu.appendChild(closeBtnEl);
closeBtnEl.addEventListener("click", () => {
navMenu.classList.add("hidden");
if (closeBtnEl) {
closeBtnEl.remove();
closeBtnEl = null;
}
isMenuOpen = false;
});
isMenuOpen = true;
}
});
// Close menu when clicking outside
document.addEventListener("click", (e) => {
if (!menuToggle.contains(e.target) && !navMenu.contains(e.target)) {
navMenu.classList.add("hidden");
if (closeBtnEl) {
closeBtnEl.remove();
closeBtnEl = null;
}
isMenuOpen = false;
}
});
// Close navigation when clicking on a link
navLinks.forEach((link) => {
link.addEventListener("click", function () {
navMenu.classList.add("hidden");
if (closeBtnEl) {
closeBtnEl.remove();
closeBtnEl = null;
}
isMenuOpen = false;
});
});
// Close navigation with Escape key
document.addEventListener("keydown", function (e) {
if (e.key === "Escape" && !navMenu.classList.contains("hidden")) {
navMenu.classList.add("hidden");
if (closeBtnEl) {
closeBtnEl.remove();
closeBtnEl = null;
}
isMenuOpen = false;
}
});
}
}
// Scroll-triggered animations
function initScrollAnimations() {
const observerOptions = {
threshold: 0.1,
rootMargin: "0px 0px -50px 0px",
};
const observer = new IntersectionObserver(function (entries) {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add("animated");
}
});
}, observerOptions);
// Observe all elements with animation classes
const animatedElements = document.querySelectorAll(
".animate-on-scroll, .animate-on-scroll-left, .animate-on-scroll-right, .animate-on-scroll-scale"
);
animatedElements.forEach((el) => {
observer.observe(el);
});
}
// Smooth scrolling for anchor links
function initSmoothScrolling() {
const links = document.querySelectorAll('a[href^="#"]');
links.forEach((link) => {
link.addEventListener("click", function (e) {
e.preventDefault();
const targetId = this.getAttribute("href");
const targetElement = document.querySelector(targetId);
if (targetElement) {
const offsetTop = targetElement.offsetTop;
window.scrollTo({
top: offsetTop,
behavior: "smooth",
});
}
});
});
}
// Global scroll to section function
function scrollToSection(sectionId) {
const section = document.getElementById(sectionId);
if (section) {
section.scrollIntoView({
behavior: "smooth",
block: "start",
});
}
}
// Slider functionality
function initSlider() {
const sliderDots = document.querySelectorAll(".slider-dot");
let currentSlide = 0;
sliderDots.forEach((dot, index) => {
dot.addEventListener("click", function () {
// Remove active class from all dots
sliderDots.forEach((d) => d.classList.remove("active"));
// Add active class to clicked dot
this.classList.add("active");
// Update current slide
currentSlide = index;
// Here you would typically change the background image
// For now, we'll just add a visual effect
const heroImage = document.querySelector(".hero-image");
heroImage.style.transform = `scale(1.05)`;
setTimeout(() => {
heroImage.style.transform = "scale(1)";
}, 300);
});
});
// Auto-slide functionality (optional)
setInterval(() => {
currentSlide = (currentSlide + 1) % sliderDots.length;
sliderDots.forEach((dot, index) => {
dot.classList.toggle("active", index === currentSlide);
});
}, 5000);
}
// Custom cursor
function initCustomCursor() {
// Only enable on desktop
if (window.innerWidth <= 768) return;
const cursor = document.createElement("div");
cursor.className = "custom-cursor";
document.body.appendChild(cursor);
document.addEventListener("mousemove", function (e) {
cursor.style.left = e.clientX + "px";
cursor.style.top = e.clientY + "px";
});
// Add hover effect for interactive elements
const interactiveElements = document.querySelectorAll(
"a, button, .cta-button, .service-card"
);
interactiveElements.forEach((el) => {
el.addEventListener("mouseenter", function () {
cursor.classList.add("hover");
});
el.addEventListener("mouseleave", function () {
cursor.classList.remove("hover");
});
});
}
// Scroll progress indicator
function initScrollProgress() {
const progressBar = document.createElement("div");
progressBar.className = "scroll-progress";
document.body.appendChild(progressBar);
window.addEventListener("scroll", function () {
const scrollTop = window.pageYOffset;
const docHeight = document.body.scrollHeight - window.innerHeight;
const scrollPercent = (scrollTop / docHeight) * 100;
progressBar.style.width = scrollPercent + "%";
});
}
// Loading screen
function initLoadingScreen() {
const loading = document.createElement("div");
loading.className = "loading";
loading.innerHTML = '<div class="loading-spinner"></div>';
document.body.appendChild(loading);
// Hide loading screen after page loads
window.addEventListener("load", function () {
setTimeout(() => {
loading.classList.add("hidden");
setTimeout(() => {
loading.remove();
}, 500);
}, 1000);
});
}
// Text reveal animations
function initTextReveal() {
const textElements = document.querySelectorAll(
".main-headline, .section-title"
);
textElements.forEach((element) => {
const text = element.textContent;
element.innerHTML = "";
element.classList.add("text-reveal");
text.split("").forEach((char) => {
const span = document.createElement("span");
span.textContent = char === " " ? "\u00A0" : char;
element.appendChild(span);
});
});
// Trigger text reveal on scroll
const textObserver = new IntersectionObserver(
function (entries) {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add("revealed");
}
});
},
{ threshold: 0.5 }
);
document.querySelectorAll(".text-reveal").forEach((el) => {
textObserver.observe(el);
});
}
// Parallax effects
function initParallax() {
const parallaxElements = document.querySelectorAll(
".hero-image, .vertical-line"
);
window.addEventListener("scroll", function () {
const scrolled = window.pageYOffset;
parallaxElements.forEach((element) => {
const speed = element.dataset.speed || 0.5;
const yPos = -(scrolled * speed);
element.style.transform = `translateY(${yPos}px)`;
});
});
}
// Utility functions
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Handle window resize
window.addEventListener(
"resize",
debounce(function () {
// Reinitialize cursor on resize
const cursor = document.querySelector(".custom-cursor");
if (cursor && window.innerWidth <= 768) {
cursor.remove();
} else if (!cursor && window.innerWidth > 768) {
initCustomCursor();
}
}, 250)
);
// Add ripple effect to buttons
document.addEventListener("click", function (e) {
if (
e.target.classList.contains("cta-button") ||
e.target.closest(".cta-button")
) {
const button = e.target.classList.contains("cta-button")
? e.target
: e.target.closest(".cta-button");
button.classList.add("ripple");
setTimeout(() => {
button.classList.remove("ripple");
}, 600);
}
});
// Form handling (for contact forms)
function handleFormSubmit(form) {
const formData = new FormData(form);
const submitButton = form.querySelector('button[type="submit"]');
const originalText = submitButton.textContent;
// Show loading state
submitButton.textContent = "Sending...";
submitButton.disabled = true;
// Simulate form submission (replace with actual endpoint)
setTimeout(() => {
submitButton.textContent = "Sent!";
submitButton.style.backgroundColor = "var(--color-primary)";
setTimeout(() => {
submitButton.textContent = originalText;
submitButton.disabled = false;
submitButton.style.backgroundColor = "";
form.reset();
}, 2000);
}, 1500);
}
// Initialize form handlers
document.addEventListener("DOMContentLoaded", function () {
const forms = document.querySelectorAll("form");
forms.forEach((form) => {
form.addEventListener("submit", function (e) {
e.preventDefault();
handleFormSubmit(this);
});
});
});
// Add some interactive effects to service cards
document.addEventListener("DOMContentLoaded", function () {
const serviceCards = document.querySelectorAll(".service-card");
serviceCards.forEach((card) => {
card.addEventListener("mouseenter", function () {
this.style.transform = "translateY(-10px) scale(1.02)";
});
card.addEventListener("mouseleave", function () {
this.style.transform = "translateY(0) scale(1)";
});
});
});
// Keyboard navigation support
document.addEventListener("keydown", function (e) {
// Tab navigation for accessibility
if (e.key === "Tab") {
document.body.classList.add("keyboard-navigation");
}
});
document.addEventListener("mousedown", function () {
document.body.classList.remove("keyboard-navigation");
});
// Performance optimization: Lazy load images
function initLazyLoading() {
const images = document.querySelectorAll("img[data-src]");
const imageObserver = new IntersectionObserver(function (entries) {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove("lazy");
imageObserver.unobserve(img);
}
});
});
images.forEach((img) => imageObserver.observe(img));
}
// Initialize lazy loading
document.addEventListener("DOMContentLoaded", initLazyLoading);