mirror of
https://git.kh3group.com/georgebiri/kh3_website.git
synced 2026-07-02 06:23:44 +00:00
445 lines
12 KiB
JavaScript
445 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) {
|
|
console.log("Navigation elements found:", {
|
|
menuToggle,
|
|
navMenu,
|
|
menuGrid,
|
|
});
|
|
let isMenuOpen = false;
|
|
const menuPetal = document.getElementById("menuPetal");
|
|
console.log("menuPetal element:", menuPetal);
|
|
const menuWrap = document.getElementById("menuWrap");
|
|
|
|
// Force a known initial state: closed menu shows closed petal
|
|
if (menuWrap) menuWrap.classList.remove("open");
|
|
try {
|
|
if (menuGrid) menuGrid.style.opacity = "1";
|
|
if (menuPetal) menuPetal.style.opacity = "0";
|
|
} catch (e) {}
|
|
|
|
menuToggle.addEventListener("click", () => {
|
|
console.log("Menu clicked! Current state:", isMenuOpen);
|
|
console.log("menuPetal element:", menuPetal);
|
|
console.log("navMenu element:", navMenu);
|
|
|
|
if (isMenuOpen) {
|
|
// Close menu
|
|
console.log("Closing menu...");
|
|
navMenu.classList.add("hidden");
|
|
// Show closed petal, hide open petal
|
|
if (menuPetal) {
|
|
menuGrid.style.opacity = "1";
|
|
menuPetal.style.opacity = "0";
|
|
console.log("Showing closed petal, hiding open petal");
|
|
}
|
|
isMenuOpen = false;
|
|
} else {
|
|
// Open menu
|
|
console.log("Opening menu...");
|
|
navMenu.classList.remove("hidden");
|
|
// Hide closed petal, show open petal
|
|
if (menuPetal) {
|
|
menuGrid.style.opacity = "0";
|
|
menuPetal.style.opacity = "1";
|
|
console.log("Hiding closed petal, showing open petal");
|
|
}
|
|
isMenuOpen = true;
|
|
}
|
|
});
|
|
|
|
// Close menu when clicking outside
|
|
document.addEventListener("click", (e) => {
|
|
if (!menuToggle.contains(e.target) && !navMenu.contains(e.target)) {
|
|
navMenu.classList.add("hidden");
|
|
// Show closed petal, hide open petal
|
|
if (menuPetal) {
|
|
menuGrid.style.opacity = "1";
|
|
menuPetal.style.opacity = "0";
|
|
}
|
|
isMenuOpen = false;
|
|
}
|
|
});
|
|
|
|
// Close navigation when clicking on a link
|
|
navLinks.forEach((link) => {
|
|
link.addEventListener("click", function () {
|
|
navMenu.classList.add("hidden");
|
|
// Show closed petal, hide open petal
|
|
if (menuPetal) {
|
|
menuGrid.style.opacity = "1";
|
|
menuPetal.style.opacity = "0";
|
|
}
|
|
isMenuOpen = false;
|
|
});
|
|
});
|
|
|
|
// Close navigation with Escape key
|
|
document.addEventListener("keydown", function (e) {
|
|
if (e.key === "Escape" && !navMenu.classList.contains("hidden")) {
|
|
navMenu.classList.add("hidden");
|
|
// Show closed petal, hide open petal
|
|
if (menuPetal) {
|
|
menuGrid.style.opacity = "1";
|
|
menuPetal.style.opacity = "0";
|
|
}
|
|
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 = "#B03037"; // kh3-red color
|
|
|
|
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);
|