forked from izu/student-web-if-development-kit
173 lines
6.3 KiB
JavaScript
173 lines
6.3 KiB
JavaScript
// =====================================================
|
|
// main.js — Inisialisasi Utama
|
|
// Sticky Navbar, Hamburger Menu, Smooth Scroll, AOS, Active Nav
|
|
// =====================================================
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
// ─── AOS (Animate on Scroll) ────────────────────────
|
|
AOS.init({
|
|
duration: 700,
|
|
easing: 'ease-out-cubic',
|
|
once: true,
|
|
offset: 50,
|
|
});
|
|
|
|
|
|
// ─── Sticky Navbar ──────────────────────────────────
|
|
// Navbar sudah fixed di CSS; tidak perlu mengubah warna karena
|
|
// warna primary sudah menjadi identitas halaman ini.
|
|
// Namun kita tambahkan shadow saat scroll untuk depth effect.
|
|
|
|
// PERBAIKAN: Menyesuaikan ID dengan yang ada di index.html (nav-wrapper)
|
|
const navbar = document.getElementById('nav-wrapper') || document.getElementById('navbar');
|
|
|
|
const handleScroll = () => {
|
|
if (!navbar) return; // PERBAIKAN: Mencegah error jika elemen tidak ada di halaman
|
|
|
|
if (window.scrollY > 10) {
|
|
navbar.style.boxShadow = '0 4px 20px rgba(0,0,0,0.25)';
|
|
} else {
|
|
navbar.style.boxShadow = 'none';
|
|
}
|
|
};
|
|
|
|
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
handleScroll(); // initial call
|
|
|
|
|
|
// ─── Ticker Visibility (Muncul setelah Hero & Stats) ─
|
|
const ticker = document.querySelector('.ticker-wrapper');
|
|
const statsSection = document.getElementById('statistics');
|
|
|
|
const handleTickerVisibility = () => {
|
|
if (!ticker || !statsSection) return;
|
|
|
|
// Ticker muncul setelah melewati batas bawah elemen statistik
|
|
const statsBottom = statsSection.offsetTop + statsSection.offsetHeight;
|
|
|
|
if (window.scrollY > statsBottom) {
|
|
ticker.classList.add('show-ticker');
|
|
} else {
|
|
ticker.classList.remove('show-ticker');
|
|
}
|
|
};
|
|
|
|
window.addEventListener('scroll', handleTickerVisibility, { passive: true });
|
|
// Call once on load with a slight delay to ensure layout is ready
|
|
setTimeout(handleTickerVisibility, 100);
|
|
|
|
|
|
// ─── Hamburger Menu (Mobile) ─────────────────────────
|
|
const mobileBtn = document.getElementById('mobile-menu-button');
|
|
const mobileMenu = document.getElementById('mobile-menu');
|
|
const closeMobileBtn = document.getElementById('close-mobile-menu');
|
|
|
|
const closeMobileMenu = () => {
|
|
mobileMenu?.classList.add('-translate-x-full');
|
|
mobileBtn?.setAttribute('aria-expanded', 'false');
|
|
};
|
|
|
|
// openMobileMenu handled by inline script in HTML; this covers close-on-scroll
|
|
closeMobileBtn?.addEventListener('click', closeMobileMenu);
|
|
|
|
|
|
// ─── Smooth Scroll untuk anchor links ──────────────
|
|
// Sesuai SDD: CSS scroll-behavior + JS fallback dengan offset
|
|
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
|
anchor.addEventListener('click', e => {
|
|
const href = anchor.getAttribute('href');
|
|
const target = document.querySelector(href);
|
|
if (!target) return;
|
|
|
|
e.preventDefault();
|
|
|
|
// PERBAIKAN: Mencegah error saat membaca offsetHeight jika navbar null
|
|
const navHeight = navbar ? navbar.offsetHeight : 110;
|
|
const top = target.getBoundingClientRect().top + window.scrollY - navHeight - 4;
|
|
window.scrollTo({ top, behavior: 'smooth' });
|
|
|
|
// Tutup mobile menu jika sedang terbuka
|
|
closeMobileMenu();
|
|
});
|
|
});
|
|
|
|
|
|
// ─── Active Nav Link via IntersectionObserver ──────
|
|
// Sesuai SDD fitur #4: highlight menu sesuai seksi yang terlihat
|
|
const sections = document.querySelectorAll('section[id]');
|
|
const navLinks = document.querySelectorAll('a.nav-link');
|
|
|
|
const sectionObserver = new IntersectionObserver(
|
|
entries => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
navLinks.forEach(link => {
|
|
link.classList.remove('active');
|
|
if (link.getAttribute('href') === `#${entry.target.id}`) {
|
|
link.classList.add('active');
|
|
}
|
|
});
|
|
}
|
|
});
|
|
},
|
|
{ rootMargin: '-30% 0px -65% 0px' }
|
|
);
|
|
|
|
sections.forEach(s => sectionObserver.observe(s));
|
|
|
|
|
|
// ─── Back to Top visibility ────────────────────────
|
|
const backToTop = document.getElementById('back-to-top');
|
|
if (backToTop) {
|
|
window.addEventListener('scroll', () => {
|
|
if (window.scrollY > 400) {
|
|
backToTop.classList.remove('opacity-0', 'pointer-events-none');
|
|
backToTop.classList.add('opacity-100');
|
|
} else {
|
|
backToTop.classList.add('opacity-0', 'pointer-events-none');
|
|
backToTop.classList.remove('opacity-100');
|
|
}
|
|
}, { passive: true });
|
|
|
|
backToTop.addEventListener('click', () => {
|
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
});
|
|
}
|
|
|
|
// ─── Contact Us Modal (Hubungi Kami) ────────────────
|
|
const btnOpenContact = document.getElementById('btn-open-contact');
|
|
const contactOverlay = document.getElementById('contact-overlay');
|
|
const contactModal = document.getElementById('contact-modal');
|
|
const contactClose = document.getElementById('contact-close');
|
|
const contactBackdrop = document.getElementById('contact-backdrop');
|
|
|
|
const openContactModal = () => {
|
|
if (!contactOverlay) return;
|
|
contactOverlay.classList.remove('hidden');
|
|
contactOverlay.classList.add('flex');
|
|
// Animasi muncul
|
|
setTimeout(() => {
|
|
contactOverlay.classList.remove('opacity-0');
|
|
contactModal.classList.remove('scale-95');
|
|
contactModal.classList.add('scale-100');
|
|
}, 10);
|
|
};
|
|
|
|
const closeContactModal = () => {
|
|
if (!contactOverlay) return;
|
|
contactOverlay.classList.add('opacity-0');
|
|
contactModal.classList.remove('scale-100');
|
|
contactModal.classList.add('scale-95');
|
|
// Tunggu animasi selesai baru di-hide
|
|
setTimeout(() => {
|
|
contactOverlay.classList.remove('flex');
|
|
contactOverlay.classList.add('hidden');
|
|
}, 300);
|
|
};
|
|
|
|
if (btnOpenContact) btnOpenContact.addEventListener('click', openContactModal);
|
|
if (contactClose) contactClose.addEventListener('click', closeContactModal);
|
|
if (contactBackdrop) contactBackdrop.addEventListener('click', closeContactModal);
|
|
|
|
}); // end DOMContentLoaded
|