// ===== LANGUAGE TOGGLE ===== let currentLang = 'id'; function setLangLabel(lang) { const nextLang = lang === 'id' ? 'EN' : 'ID'; const labelDesktop = document.getElementById('lang-label'); const labelMobile = document.getElementById('lang-label-mobile'); if (labelDesktop) labelDesktop.textContent = nextLang; if (labelMobile) labelMobile.textContent = nextLang; } function applyLang(lang) { const pageBody = document.getElementById('page-body'); pageBody.classList.toggle('lang-id', lang === 'id'); pageBody.classList.toggle('lang-en', lang === 'en'); document.documentElement.lang = lang; } function toggleLang() { currentLang = currentLang === 'id' ? 'en' : 'id'; applyLang(currentLang); setLangLabel(currentLang); } document.getElementById('lang-toggle')?.addEventListener('click', toggleLang); document.getElementById('lang-toggle-mobile')?.addEventListener('click', toggleLang); // ===== SCROLL REVEAL ===== function initScrollReveal() { const revealEls = document.querySelectorAll('.reveal'); const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('visible'); observer.unobserve(entry.target); } }); }, { threshold: 0.1 }); revealEls.forEach(el => observer.observe(el)); } initScrollReveal(); // ===== BACK TO TOP ===== function initBackToTop() { const btn = document.getElementById('back-to-top'); if (!btn) return; btn.addEventListener('click', () => window.scrollTo({ top: 0, behavior: 'smooth' })); window.addEventListener('scroll', () => { const isPastThreshold = window.scrollY > 400; btn.classList.toggle('hidden', !isPastThreshold); btn.classList.toggle('flex', isPastThreshold); }); } initBackToTop(); // ===== PRESTASI FILTER ===== function setActiveTabStyle(btn, isActive) { btn.classList.toggle('bg-untan-navy', isActive); btn.classList.toggle('text-white', isActive); btn.classList.toggle('bg-white', !isActive); btn.classList.toggle('text-slate-700', !isActive); btn.classList.toggle('border', !isActive); btn.classList.toggle('border-slate-200',!isActive); } function filterPrestasi(category) { document.querySelectorAll('.prestasi-tab-btn').forEach(btn => { setActiveTabStyle(btn, btn.dataset.tab === category); }); document.querySelectorAll('#prestasi-grid .prestasi-card').forEach(card => { card.style.display = (category === 'all' || card.dataset.category === category) ? '' : 'none'; }); } function initPrestasiFilter() { document.querySelectorAll('.prestasi-tab-btn').forEach(btn => { btn.addEventListener('click', () => filterPrestasi(btn.dataset.tab)); }); } initPrestasiFilter(); // ===== MEGA MENU ===== (function initMegaMenu() { const menuItems = document.querySelectorAll('[data-menu]'); if (!menuItems.length) return; let activeMenuId = null; function closeAllMenus() { document.querySelectorAll('.mega-panel').forEach(p => p.classList.remove('active')); updateButtonStates(null); activeMenuId = null; } function updateButtonStates(openMenuId) { menuItems.forEach(item => { const isActive = item.getAttribute('data-menu') === openMenuId; const btn = item.querySelector('.nav-menu-btn'); const chevron = item.querySelector('.fa-chevron-down'); if (btn) { btn.classList.toggle('active', isActive); btn.setAttribute('aria-expanded', isActive ? 'true' : 'false'); } if (chevron) chevron.style.transform = isActive ? 'rotate(180deg)' : ''; }); } menuItems.forEach(item => { const menuId = item.getAttribute('data-menu'); const btn = item.querySelector('.nav-menu-btn'); if (!btn) return; btn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); if (activeMenuId === menuId) { closeAllMenus(); } else { closeAllMenus(); updateButtonStates(menuId); activeMenuId = menuId; } }); }); document.addEventListener('keydown', e => { if (e.key === 'Escape') closeAllMenus(); }); document.addEventListener('click', e => { if (!e.target.closest('#main-nav') && activeMenuId) closeAllMenus(); }); })(); // ===== MOBILE MENU ===== function initMobileMenu() { const menuBtn = document.getElementById('mobile-menu-btn'); const menu = document.getElementById('mobile-menu'); const closeBtn = document.getElementById('close-mobile-menu'); if (menuBtn && menu) { menuBtn.addEventListener('click', () => { const isOpen = menu.classList.toggle('-translate-x-full') === false; menuBtn.setAttribute('aria-expanded', String(isOpen)); }); } if (closeBtn && menu) { closeBtn.addEventListener('click', () => menu.classList.add('-translate-x-full')); } } initMobileMenu(); // ===== LUCO CHATBOT BUBBLE ===== function initLucoBubble() { const bubble = document.getElementById('luco-bubble'); const closeBtn = document.getElementById('luco-bubble-close'); if (!bubble) return; setTimeout(() => bubble.classList.remove('hidden'), 3000); closeBtn?.addEventListener('click', (e) => { e.stopPropagation(); bubble.classList.add('hidden'); }); } initLucoBubble(); // ===== #ASSIGNMENT-BODY ENHANCEMENTS ===== (function initAssignmentBody() { const body = document.getElementById('assignment-body'); if (!body) return; // --- 1. Scroll-reveal untuk setiap child langsung --- const revealTargets = body.querySelectorAll('h3, h4, p, ul, table, figure'); const revealObserver = new IntersectionObserver((entries) => { entries.forEach((entry, i) => { if (entry.isIntersecting) { setTimeout(() => entry.target.classList.add('visible'), i * 60); revealObserver.unobserve(entry.target); } }); }, { threshold: 0.08 }); revealTargets.forEach(el => { el.classList.add('ab-reveal'); revealObserver.observe(el); }); // --- 2. Collapsible: Alumni & Mitra (sections yang panjang) --- // Wrap konten setelah h3 "Kisah Sukses Alumni" dalam collapsible const allH3 = [...body.querySelectorAll('h3')]; const collapsibleTitles = ['Kisah Sukses Alumni', 'Mitra Strategis', 'Fasilitas Akademik']; allH3.forEach(h3 => { if (!collapsibleTitles.includes(h3.textContent.trim())) return; // Kumpulkan sibling sampai h3 berikutnya const siblings = []; let next = h3.nextElementSibling; while (next && next.tagName !== 'H3') { siblings.push(next); next = next.nextElementSibling; } if (!siblings.length) return; // Buat wrapper collapsible const wrapper = document.createElement('div'); wrapper.className = 'ab-collapsible'; h3.parentNode.insertBefore(wrapper, siblings[0]); siblings.forEach(s => wrapper.appendChild(s)); // Buat tombol toggle const btn = document.createElement('button'); btn.className = 'ab-readmore-btn'; btn.innerHTML = 'Lihat Detail '; btn.setAttribute('aria-expanded', 'false'); h3.insertAdjacentElement('afterend', btn); btn.addEventListener('click', () => { const isOpen = wrapper.classList.toggle('open'); btn.classList.toggle('open', isOpen); btn.innerHTML = isOpen ? 'Sembunyikan ' : 'Lihat Detail '; btn.setAttribute('aria-expanded', String(isOpen)); }); }); // --- 3. Table row highlight on click --- body.querySelectorAll('tbody tr').forEach(row => { row.style.cursor = 'pointer'; row.addEventListener('click', () => { body.querySelectorAll('tbody tr.selected').forEach(r => r.classList.remove('selected')); row.classList.add('selected'); row.style.background = '#dbeafe'; row.style.fontWeight = '600'; setTimeout(() => { row.style.background = ''; row.style.fontWeight = ''; row.classList.remove('selected'); }, 1800); }); }); // --- 4. Callout block: inject setelah paragraf karir --- const pEls = [...body.querySelectorAll('p')]; const kariPara = pEls.find(p => p.textContent.includes('Rp 5–12 Juta')); if (kariPara) { const callout = document.createElement('div'); callout.className = 'ab-callout ab-reveal'; callout.textContent = '87% lulusan bekerja di bidang IT sesuai kompetensi, dengan rata-rata waktu tunggu kerja kurang dari 3 bulan.'; kariPara.insertAdjacentElement('afterend', callout); revealObserver.observe(callout); } // --- 5. Tambahkan section label di atas setiap h3 --- const sectionMap = { 'Mengenal Program Studi': 'Tentang Prodi', 'Visi': 'Identitas', 'Misi': 'Identitas', 'Tonggak Sejarah': 'Sejarah', 'dalam Angka': 'Data & Statistik', 'Pencapaian': 'Prestasi', 'Study Club': 'Komunitas', 'Prospek Karir': 'Karir', 'Kisah Sukses': 'Alumni', 'Fasilitas': 'Infrastruktur', 'Mitra Strategis': 'Kerjasama', 'Informasi Kontak': 'Kontak', }; allH3.forEach(h3 => { const key = Object.keys(sectionMap).find(k => h3.textContent.includes(k)); if (!key) return; const label = document.createElement('div'); label.className = 'ab-section-label'; label.textContent = sectionMap[key]; h3.insertAdjacentElement('beforebegin', label); }); })(); // ===================================================== // JELLYFISH.CORP — MOCKUP SCRIPTS // ===================================================== // ===== PRESTASI FILTER (new jf-* classes) ===== (function initPrestasiFilterJF() { const tabs = document.querySelectorAll('.jf-tab-btn'); const cards = document.querySelectorAll('#prestasi-grid .jf-prestasi-card'); if (!tabs.length) return; tabs.forEach(tab => { tab.addEventListener('click', () => { tabs.forEach(t => t.classList.remove('jf-tab-active')); tab.classList.add('jf-tab-active'); const filter = tab.dataset.filter; cards.forEach(card => { const show = filter === 'all' || card.dataset.cat === filter; card.style.display = show ? '' : 'none'; if (show) { card.style.animation = 'jfFadeIn 0.35s ease forwards'; } }); }); }); })(); // ===== SCROLL REVEAL for jf-* cards ===== (function initJFScrollReveal() { const targets = document.querySelectorAll( '.jf-stat-card, .jf-stat-row-card, .jf-prestasi-card, ' + '.jf-sc-card, .jf-karir-card, .jf-alumni-card, ' + '.jf-infra-card, .jf-mitra-card, .jf-life-card, ' + '.jf-tl-item, .jf-visi-card, .jf-kontak-item' ); targets.forEach(el => { el.style.opacity = '0'; el.style.transform = 'translateY(24px)'; el.style.transition = 'opacity 0.55s ease, transform 0.55s ease'; }); const obs = new IntersectionObserver((entries) => { entries.forEach((entry, i) => { if (entry.isIntersecting) { setTimeout(() => { entry.target.style.opacity = '1'; entry.target.style.transform = 'translateY(0)'; }, i * 70); obs.unobserve(entry.target); } }); }, { threshold: 0.08 }); targets.forEach(el => obs.observe(el)); })(); // ===== ANIMATED COUNTER for jf-stat-num ===== (function initJFCounters() { const nums = document.querySelectorAll('.jf-stat-num'); const obs = new IntersectionObserver((entries) => { entries.forEach(entry => { if (!entry.isIntersecting) return; const el = entry.target; const raw = el.textContent.trim(); const num = parseFloat(raw.replace(/[^0-9.]/g, '')); if (isNaN(num) || num === 0) return; const suffix = raw.replace(/[0-9.,]/g, ''); const prefix = raw.startsWith('<') ? '<' : ''; const cleanSuffix = prefix ? suffix : suffix; let start = 0; const duration = 1400; const step = 16; const increment = num / (duration / step); const timer = setInterval(() => { start += increment; if (start >= num) { start = num; clearInterval(timer); } const display = num % 1 === 0 ? Math.floor(start).toLocaleString('id-ID') : start.toFixed(0); el.textContent = prefix + display + cleanSuffix; }, step); obs.unobserve(el); }); }, { threshold: 0.5 }); nums.forEach(el => obs.observe(el)); })();