feat: tambah isi konten keseluruhan
This commit is contained in:
@@ -0,0 +1,364 @@
|
||||
// ===== 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 <span class="arrow">▼</span>';
|
||||
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 <span class="arrow">▼</span>'
|
||||
: 'Lihat Detail <span class="arrow">▼</span>';
|
||||
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));
|
||||
})();
|
||||
Reference in New Issue
Block a user