Initial commit: KH3 Group Limited website with enhanced animations and slide+zoom transitions

This commit is contained in:
George Birikorang 2025-08-22 10:38:10 -04:00
commit ecebd00021
23 changed files with 5879 additions and 0 deletions

392
js/main.js Normal file
View file

@ -0,0 +1,392 @@
// Main JavaScript for FBS Website
document.addEventListener("DOMContentLoaded", function () {
// Initialize all functionality
initNavigation();
initScrollAnimations();
initSmoothScrolling();
initSlider();
initCustomCursor();
initScrollProgress();
initLoadingScreen();
initTextReveal();
initParallax();
});
// Navigation functionality
function initNavigation() {
const menuToggle = document.getElementById("menuToggle");
const navOverlay = document.getElementById("navOverlay");
const navClose = document.getElementById("navClose");
const navLinks = document.querySelectorAll(".nav-link");
// Open navigation
menuToggle.addEventListener("click", function () {
navOverlay.classList.add("active");
document.body.style.overflow = "hidden";
});
// Close navigation
navClose.addEventListener("click", function () {
navOverlay.classList.remove("active");
document.body.style.overflow = "";
});
// Close navigation when clicking outside
navOverlay.addEventListener("click", function (e) {
if (e.target === navOverlay) {
navOverlay.classList.remove("active");
document.body.style.overflow = "";
}
});
// Close navigation when clicking on a link
navLinks.forEach((link) => {
link.addEventListener("click", function () {
navOverlay.classList.remove("active");
document.body.style.overflow = "";
});
});
// Close navigation with Escape key
document.addEventListener("keydown", function (e) {
if (e.key === "Escape" && navOverlay.classList.contains("active")) {
navOverlay.classList.remove("active");
document.body.style.overflow = "";
}
});
}
// 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);

130
js/projects.js Normal file
View file

@ -0,0 +1,130 @@
// Projects Page JavaScript
document.addEventListener("DOMContentLoaded", function () {
const pills = document.querySelectorAll("[data-filter]");
const grid = document.querySelector("#projectsGrid");
let projectsData = [];
// Load projects data
async function loadProjects() {
try {
const response = await fetch("/data/projects.json");
projectsData = await response.json();
renderProjects("All");
} catch (error) {
console.error("Error loading projects:", error);
grid.innerHTML =
'<p class="error-message">Error loading projects. Please try again later.</p>';
}
}
// Render projects based on category
function renderProjects(category = "All") {
const filteredProjects =
category === "All"
? projectsData
: projectsData.filter((project) => project.category === category);
grid.classList.add("fade-out");
setTimeout(() => {
grid.innerHTML = filteredProjects
.map(
(project) => `
<div class="project-card" onclick="window.location.href='project-${project.slug}.html'">
<div class="project-card-image" style="background-image: url('${project.coverImage}')">
<div class="project-card-overlay"></div>
</div>
<div class="project-card-content">
<div class="project-card-category">${project.category}</div>
<h3 class="project-card-title">${project.title}</h3>
<p class="project-card-location">${project.location}</p>
<a href="project-${project.slug}.html" class="project-card-link">View Project</a>
</div>
</div>
`
)
.join("");
grid.classList.remove("fade-out");
grid.classList.add("fade-in");
setTimeout(() => {
grid.classList.remove("fade-in");
}, 180);
}, 120);
}
// Handle filter pill clicks
pills.forEach((pill) => {
pill.addEventListener("click", (e) => {
e.preventDefault();
const category = pill.dataset.filter;
// Update pill states
pills.forEach((p) => {
p.setAttribute("aria-pressed", p === pill ? "true" : "false");
});
// Update URL
const url = new URL(location);
if (category === "All") {
url.searchParams.delete("category");
} else {
url.searchParams.set("category", category);
}
history.pushState({}, "", url);
// Render filtered projects
renderProjects(category);
});
});
// Handle browser back/forward
window.addEventListener("popstate", () => {
const category = new URL(location).searchParams.get("category") || "All";
const activePill = document.querySelector(`[data-filter="${category}"]`);
if (activePill) {
pills.forEach((p) => {
p.setAttribute("aria-pressed", p === activePill ? "true" : "false");
});
renderProjects(category);
}
});
// Initialize with URL category if present
const initialCategory =
new URL(location).searchParams.get("category") || "All";
const initialPill = document.querySelector(
`[data-filter="${initialCategory}"]`
);
if (initialPill) {
pills.forEach((p) => {
p.setAttribute("aria-pressed", p === initialPill ? "true" : "false");
});
}
// Load projects on page load
loadProjects();
});
// Keyboard navigation for filter pills
document.addEventListener("keydown", function (e) {
const activePill = document.querySelector('.pill[aria-pressed="true"]');
const pills = Array.from(document.querySelectorAll(".pill"));
const currentIndex = pills.indexOf(activePill);
if (e.key === "ArrowRight" || e.key === "ArrowDown") {
e.preventDefault();
const nextIndex = (currentIndex + 1) % pills.length;
pills[nextIndex].click();
pills[nextIndex].focus();
} else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
e.preventDefault();
const prevIndex = currentIndex === 0 ? pills.length - 1 : currentIndex - 1;
pills[prevIndex].click();
pills[prevIndex].focus();
}
});