Update bilingual scholarship content
This commit is contained in:
@@ -579,16 +579,14 @@ const translations = {
|
||||
ape_hero_desc: 'Temukan berbagai peluang beasiswa untuk mendukung perjalanan akademik dan penelitian Anda. Program Studi Informatika UNTAN berkomitmen membantu mahasiswa meraih prestasi terbaik.',
|
||||
ape_hero_cta: 'Lihat Daftar Beasiswa',
|
||||
ape_stats_total: 'Total Mahasiswa Penerima<br>Beasiswa Semester Ini',
|
||||
ape_stats_gov: 'Total Penerima Beasiswa<br>Pembiayaan Pemerintah',
|
||||
ape_stats_private: 'Total Penerima Beasiswa<br>Pembiayaan Swasta',
|
||||
ape_stats_s1: 'Total Penerima Beasiswa<br>Khusus S1',
|
||||
ape_stats_studi: 'Total Penerima Beasiswa<br>Studi Lanjut',
|
||||
ape_dir_label: 'Daftar Beasiswa',
|
||||
ape_dir_title: 'Daftar Beasiswa',
|
||||
ape_dir_desc: 'Temukan beragam beasiswa dari berbagai sumber resmi yang tersedia untuk mahasiswa aktif, alumni, dan calon mahasiswa Program Studi Informatika UNTAN.',
|
||||
ape_dir_guide: 'Terima Bantuan Beasiswa?',
|
||||
ape_filter_open: 'Sedang Buka',
|
||||
ape_filter_closed: 'Ditutup',
|
||||
ape_filter_gov: 'Pemerintah',
|
||||
ape_filter_private: 'Swasta',
|
||||
ape_filter_s1: 'Khusus S1',
|
||||
ape_filter_studi: 'Studi Lanjut',
|
||||
ape_search_placeholder: 'Cari Nama Beasiswa...',
|
||||
ape_testimonial_title: 'Apa Kata Mereka?',
|
||||
ape_testimonial_desc: 'Cerita inspiratif dari para penerima beasiswa Program Studi Informatika UNTAN',
|
||||
@@ -1172,16 +1170,14 @@ const translations = {
|
||||
ape_hero_desc: 'Discover various scholarship opportunities to support your academic and research journey. The Informatics Study Program at UNTAN is committed to helping students achieve excellence.',
|
||||
ape_hero_cta: 'View Scholarships',
|
||||
ape_stats_total: 'Total Scholarship<br>Recipients This Semester',
|
||||
ape_stats_gov: 'Total Government<br>Scholarship Recipients',
|
||||
ape_stats_private: 'Total Private<br>Scholarship Recipients',
|
||||
ape_stats_s1: 'Total Undergraduate<br>Scholarship Recipients',
|
||||
ape_stats_studi: 'Total Postgraduate<br>Scholarship Recipients',
|
||||
ape_dir_label: 'Scholarship Directory',
|
||||
ape_dir_title: 'Scholarship Directory',
|
||||
ape_dir_desc: 'Find various scholarships from official sources available for active students, alumni, and prospective students of the UNTAN Informatics Study Program.',
|
||||
ape_dir_guide: 'Need Scholarship Aid?',
|
||||
ape_filter_open: 'Open',
|
||||
ape_filter_closed: 'Closed',
|
||||
ape_filter_gov: 'Government',
|
||||
ape_filter_private: 'Private',
|
||||
ape_filter_s1: 'Undergraduate',
|
||||
ape_filter_studi: 'Postgraduate',
|
||||
ape_search_placeholder: 'Search Scholarship Name...',
|
||||
ape_testimonial_title: 'What They Say?',
|
||||
ape_testimonial_desc: 'Inspiring stories from scholarship recipients at UNTAN Informatics Study Program',
|
||||
|
||||
@@ -1,53 +1,86 @@
|
||||
// =====================================================
|
||||
// carousel.js — Swiper.js Carousel Testimoni
|
||||
// Touch-friendly, auto-play, responsive breakpoints
|
||||
// + DUKUNGAN MULTI-BAHASA (ID/EN) Terintegrasi
|
||||
// =====================================================
|
||||
|
||||
let testimoniSwiper = null;
|
||||
let allTestimoniData = []; // Simpan data di memori agar tidak perlu loading ulang saat ganti bahasa
|
||||
|
||||
// 1. Variabel penampung bahasa yang terhubung otomatis dengan tombol ID/EN
|
||||
let activeCarouselLang = localStorage.getItem('if_untan_lang') || document.documentElement.lang || 'id';
|
||||
|
||||
// 2. Tangkap event perubahan bahasa secara langsung dari localization.js
|
||||
document.addEventListener('languageChanged', (e) => {
|
||||
// Update bahasa
|
||||
if (e.detail && e.detail.lang) {
|
||||
activeCarouselLang = e.detail.lang;
|
||||
} else if (typeof window.getLang === 'function') {
|
||||
activeCarouselLang = window.getLang();
|
||||
} else {
|
||||
activeCarouselLang = document.documentElement.lang || 'id';
|
||||
}
|
||||
|
||||
// Jika data sudah ada, langsung cetak ulang testimoninya
|
||||
if (allTestimoniData && allTestimoniData.length > 0) {
|
||||
renderTestimoniSlides(allTestimoniData);
|
||||
initSwiper(allTestimoniData.length); // Swiper harus di-restart agar animasinya tidak rusak
|
||||
}
|
||||
});
|
||||
|
||||
/** Render slide testimoni ke dalam swiper-wrapper */
|
||||
function renderTestimoniSlides(data) {
|
||||
const wrapper = document.getElementById('testimonial-wrapper');
|
||||
if (!wrapper) return;
|
||||
|
||||
const lang = activeCarouselLang; // Gunakan bahasa terbaru
|
||||
|
||||
wrapper.innerHTML = data.map(t => `
|
||||
<div class="swiper-slide" style="height:auto;">
|
||||
<div class="testimonial-card">
|
||||
<img
|
||||
src="${t.foto}"
|
||||
alt="Foto ${t.nama}"
|
||||
class="testimonial-avatar"
|
||||
loading="lazy"
|
||||
onerror="
|
||||
this.style.display='none';
|
||||
this.nextElementSibling.style.display='flex';
|
||||
"
|
||||
>
|
||||
<div
|
||||
class="testimonial-avatar"
|
||||
style="
|
||||
display:none;
|
||||
background:linear-gradient(135deg,#003150,#4a6bc5);
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
color:white;
|
||||
font-size:22px;
|
||||
font-weight:700;
|
||||
"
|
||||
>
|
||||
${t.nama.charAt(0).toUpperCase()}
|
||||
wrapper.innerHTML = data.map(t => {
|
||||
// Logika Pintar: Cek mode bahasa. Jika 'en' dan teks terjemahannya ada, pakai bahasa Inggris.
|
||||
// Jika tidak, tetap tampilkan teks bahasa Indonesia (sebagai cadangan).
|
||||
const beasiswaName = (lang === 'en' && t.nama_beasiswa_en) ? t.nama_beasiswa_en : t.nama_beasiswa;
|
||||
const quote = (lang === 'en' && t.kutipan_en) ? t.kutipan_en : t.kutipan;
|
||||
|
||||
return `
|
||||
<div class="swiper-slide" style="height:auto;">
|
||||
<div class="testimonial-card">
|
||||
<img
|
||||
src="${t.foto}"
|
||||
alt="Foto ${t.nama}"
|
||||
class="testimonial-avatar"
|
||||
loading="lazy"
|
||||
onerror="
|
||||
this.style.display='none';
|
||||
this.nextElementSibling.style.display='flex';
|
||||
"
|
||||
>
|
||||
<div
|
||||
class="testimonial-avatar"
|
||||
style="
|
||||
display:none;
|
||||
background:linear-gradient(135deg,#003150,#4a6bc5);
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
color:white;
|
||||
font-size:22px;
|
||||
font-weight:700;
|
||||
"
|
||||
>
|
||||
${t.nama.charAt(0).toUpperCase()}
|
||||
</div>
|
||||
|
||||
<div class="testimonial-name">${t.nama}</div>
|
||||
<div class="testimonial-beasiswa">${beasiswaName}</div>
|
||||
<p class="testimonial-quote">"${quote}"</p>
|
||||
</div>
|
||||
|
||||
<div class="testimonial-name">${t.nama}</div>
|
||||
<div class="testimonial-beasiswa">${t.nama_beasiswa}</div>
|
||||
<p class="testimonial-quote">"${t.kutipan}"</p>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
/** Inisialisasi Swiper setelah slide di-render */
|
||||
function initSwiper(count) {
|
||||
// Bersihkan swiper lama jika ada (penting agar tidak menumpuk saat ganti bahasa)
|
||||
if (testimoniSwiper) {
|
||||
testimoniSwiper.destroy(true, true);
|
||||
testimoniSwiper = null;
|
||||
@@ -72,9 +105,10 @@ function initSwiper(count) {
|
||||
640: { slidesPerView: 2 },
|
||||
1024: { slidesPerView: 3 },
|
||||
},
|
||||
// Dukungan Aksesibilitas Dinamis (VoiceOver / Screen Reader)
|
||||
a11y: {
|
||||
prevSlideMessage: 'Slide sebelumnya',
|
||||
nextSlideMessage: 'Slide berikutnya',
|
||||
prevSlideMessage: activeCarouselLang === 'en' ? 'Previous slide' : 'Slide sebelumnya',
|
||||
nextSlideMessage: activeCarouselLang === 'en' ? 'Next slide' : 'Slide berikutnya',
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -88,22 +122,19 @@ async function initCarousel() {
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
|
||||
const data = await res.json();
|
||||
allTestimoniData = data; // Simpan ke variabel global
|
||||
|
||||
if (!data || data.length === 0) {
|
||||
// Sembunyikan seksi testimoni jika data kosong (sesuai UC-07 Alt Flow)
|
||||
section?.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
renderTestimoniSlides(data);
|
||||
|
||||
// Init Swiper setelah DOM di-render
|
||||
requestAnimationFrame(() => initSwiper(data.length));
|
||||
|
||||
} catch {
|
||||
// Jika gagal fetch, sembunyikan seksi (graceful degradation)
|
||||
section?.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', initCarousel);
|
||||
document.addEventListener('DOMContentLoaded', initCarousel);
|
||||
@@ -1,70 +1,113 @@
|
||||
// =====================================================
|
||||
// filter.js — Fetch JSON, Render Kartu, Filter Tab, Pencarian, Paginasi
|
||||
// Filter dan search berjalan KUMULATIF (sesuai PROJECT_CONTEXT rule #6)
|
||||
// Filter dan search berjalan KUMULATIF
|
||||
// + DUKUNGAN MULTI-BAHASA (ID/EN) Terintegrasi
|
||||
// =====================================================
|
||||
|
||||
const ITEMS_PER_PAGE = 6; // 2 baris × 3 kolom desktop
|
||||
const ITEMS_PER_PAGE = 6;
|
||||
|
||||
let allData = []; // Semua data beasiswa dari JSON
|
||||
let allData = [];
|
||||
let activeFilter = 'semua';
|
||||
let searchKeyword = '';
|
||||
let currentPage = 1;
|
||||
|
||||
// Format tanggal
|
||||
// 1. Variabel penampung bahasa yang terhubung otomatis dengan tombol ID/EN
|
||||
let activeLang = localStorage.getItem('if_untan_lang') || document.documentElement.lang || 'id';
|
||||
|
||||
// 2. Tangkap event perubahan bahasa secara langsung dari localization.js
|
||||
document.addEventListener('languageChanged', (e) => {
|
||||
if (e.detail && e.detail.lang) {
|
||||
activeLang = e.detail.lang; // Perbarui bahasa!
|
||||
} else {
|
||||
activeLang = document.documentElement.lang || 'id';
|
||||
}
|
||||
|
||||
// Jika data sudah ada, langsung cetak ulang kartunya
|
||||
if (allData.length > 0) {
|
||||
renderCards();
|
||||
}
|
||||
});
|
||||
|
||||
// 3. Format tanggal cerdas berdasarkan bahasa
|
||||
function formatDate(str) {
|
||||
if (!str) return '—';
|
||||
const d = new Date(str);
|
||||
if (isNaN(d.getTime())) return str;
|
||||
return d.toLocaleDateString('id-ID', {
|
||||
|
||||
return d.toLocaleDateString(activeLang === 'en' ? 'en-US' : 'id-ID', {
|
||||
day: 'numeric', month: 'long', year: 'numeric'
|
||||
});
|
||||
}
|
||||
|
||||
// Sanitasi string untuk mencegah XSS pada search
|
||||
// 4. Kamus mini untuk teks dinamis dari JSON (Negara & Kategori)
|
||||
function translateDynamic(text, lang) {
|
||||
if (!text || lang === 'id') return text;
|
||||
|
||||
const dict = {
|
||||
'Amerika Serikat': 'United States',
|
||||
'Swiss': 'Switzerland',
|
||||
'Pemerintah': 'Government',
|
||||
'Swasta': 'Private',
|
||||
};
|
||||
|
||||
return dict[text] || text;
|
||||
}
|
||||
|
||||
function sanitize(str) {
|
||||
return str.replace(/[<>&"']/g, c =>
|
||||
({ '<': '<', '>': '>', '&': '&', '"': '"', "'": ''' }[c])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Buat elemen kartu beasiswa
|
||||
* Gunakan textContent/setAttribute — hindari langsung innerHTML dari user input
|
||||
*/
|
||||
function createCard(bsw) {
|
||||
// Tentukan badge & warna tombol status
|
||||
const lang = activeLang; // Selalu gunakan bahasa aktif terbaru
|
||||
|
||||
// Terjemahan Tombol Status
|
||||
let statusClass = 'status-btn-ditutup';
|
||||
let statusLabel = 'Ditutup';
|
||||
let statusLabel = lang === 'en' ? 'Closed' : 'Ditutup';
|
||||
|
||||
const statusLower = (bsw.status || '').toLowerCase();
|
||||
if (statusLower === 'buka' || statusLower === 'dibuka') {
|
||||
statusClass = 'status-btn-buka';
|
||||
statusLabel = 'Pendaftaran Dibuka';
|
||||
statusLabel = lang === 'en' ? 'Registration Open' : 'Pendaftaran Dibuka';
|
||||
} else if (statusLower.includes('segera')) {
|
||||
statusClass = 'status-btn-segera';
|
||||
statusLabel = 'Segera Dibuka';
|
||||
statusLabel = lang === 'en' ? 'Opening Soon' : 'Segera Dibuka';
|
||||
}
|
||||
|
||||
// Ambil pills dari tags lokal: ["jenjang", "kategori/swasta/pemerintah", "negara", "tipe_pendanaan", ...]
|
||||
// --- KAMUS MINI UNTUK KARTU ---
|
||||
const textMulai = lang === 'en' ? 'Start' : 'Mulai';
|
||||
const textDeadline = lang === 'en' ? 'Deadline' : 'Tenggat';
|
||||
const textSelengkapnya = lang === 'en' ? 'Read More' : 'Selengkapnya';
|
||||
|
||||
// Ambil pills (tags) dan terjemahkan negaranya
|
||||
const tags = Array.isArray(bsw.tags) ? bsw.tags : [];
|
||||
const jenjang = tags[0] || '';
|
||||
|
||||
// Negara: cari tag yang bukan jenjang (S1/S2/S3), bukan kategori sumber (Swasta/Pemerintah),
|
||||
// dan bukan tipe pendanaan (Fully/Partially Funded)
|
||||
const skipPatterns = /^(S1|S2|S3|D3|D4|Swasta|Pemerintah|Fully Funded|Partially Funded|Beasiswa Penuh|Beasiswa Sebagian|Prestasi)$/i;
|
||||
const negara = tags.slice(1).find(t => !skipPatterns.test(t)) || '';
|
||||
const rawNegara = tags.slice(1).find(t => !skipPatterns.test(t)) || '';
|
||||
const negara = translateDynamic(rawNegara, lang);
|
||||
|
||||
// Tipe pendanaan untuk subtitle — ambil dari kategori field atau dari deskripsi_singkat
|
||||
// Terjemahan Subtitle (Kategori Beasiswa)
|
||||
const tipe = bsw.kategori || '';
|
||||
const rawTipe = tipe.replace(/^beasiswa\s+/i, '');
|
||||
let subtitle = '';
|
||||
if (rawTipe) {
|
||||
if (lang === 'en') {
|
||||
const tipeEn = translateDynamic(rawTipe, lang);
|
||||
subtitle = `${tipeEn} Scholarship`;
|
||||
} else {
|
||||
subtitle = `Beasiswa ${rawTipe}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Ambil nama dari versi Inggris jika ada
|
||||
const namaBsw = (lang === 'en' && bsw.nama_en) ? bsw.nama_en : bsw.nama;
|
||||
|
||||
const pillsHtml = [jenjang, negara]
|
||||
.filter(Boolean)
|
||||
.map((t, i) => `<span class="card-pill ${i === 0 ? 'card-pill-level' : ''}">${t}</span>`)
|
||||
.join('');
|
||||
|
||||
// Subtitle: hindari duplikasi "Beasiswa Beasiswa ..."
|
||||
const rawTipe = tipe.replace(/^beasiswa\s+/i, '');
|
||||
const subtitle = rawTipe ? `Beasiswa ${rawTipe}` : '';
|
||||
|
||||
const article = document.createElement('article');
|
||||
article.className = 'scholarship-card fade-in-up';
|
||||
article.setAttribute('role', 'listitem');
|
||||
@@ -72,17 +115,17 @@ function createCard(bsw) {
|
||||
|
||||
article.innerHTML = `
|
||||
<div class="card-body">
|
||||
<h3 class="card-title">${bsw.nama}</h3>
|
||||
<h3 class="card-title">${namaBsw}</h3>
|
||||
${subtitle ? `<p class="card-subtitle">${subtitle}</p>` : ''}
|
||||
${pillsHtml ? `<div class="card-pills">${pillsHtml}</div>` : ''}
|
||||
<hr class="card-divider">
|
||||
<div class="card-dates">
|
||||
<div class="card-date-col">
|
||||
<span class="card-date-label">Mulai</span>
|
||||
<span class="card-date-label">${textMulai}</span>
|
||||
<span class="card-date-value">${formatDate(bsw.tanggal_buka)}</span>
|
||||
</div>
|
||||
<div class="card-date-col">
|
||||
<span class="card-date-label">Deadline</span>
|
||||
<span class="card-date-label">${textDeadline}</span>
|
||||
<span class="card-date-value">${formatDate(bsw.tanggal_tutup)}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -90,70 +133,67 @@ function createCard(bsw) {
|
||||
<button
|
||||
class="btn-selengkapnya"
|
||||
onclick="openPopup('${bsw.id}')"
|
||||
aria-label="Lihat detail beasiswa ${bsw.nama}"
|
||||
>Selengkapnya</button>
|
||||
aria-label="Lihat detail beasiswa ${namaBsw}"
|
||||
>${textSelengkapnya}</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return article;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter data KUMULATIF: filter tab DAN search harus terpenuhi keduanya
|
||||
*/
|
||||
function getFiltered() {
|
||||
const kw = searchKeyword.toLowerCase();
|
||||
|
||||
return allData.filter(bsw => {
|
||||
// Cocok dengan filter tab
|
||||
const matchFilter =
|
||||
activeFilter === 'semua' ||
|
||||
bsw.status === activeFilter ||
|
||||
bsw.kategori === activeFilter;
|
||||
|
||||
// Logika Filter Tab S1 & Studi Lanjut
|
||||
let matchFilter = false;
|
||||
const tags = bsw.tags || [];
|
||||
|
||||
// Cocok dengan pencarian (nama atau deskripsi atau tags)
|
||||
const matchSearch =
|
||||
!kw ||
|
||||
bsw.nama.toLowerCase().includes(kw) ||
|
||||
bsw.deskripsi_singkat.toLowerCase().includes(kw) ||
|
||||
(bsw.tags || []).some(t => t.toLowerCase().includes(kw));
|
||||
if (activeFilter === 'semua') {
|
||||
matchFilter = true;
|
||||
} else if (activeFilter === 's1') {
|
||||
matchFilter = tags.some(t => t.includes('S1'));
|
||||
} else if (activeFilter === 'studi_lanjut') {
|
||||
matchFilter = tags.some(t => t.includes('S2') || t.includes('S3'));
|
||||
}
|
||||
|
||||
// Logika Pencarian Search Bar
|
||||
const matchSearch = !kw ||
|
||||
bsw.nama.toLowerCase().includes(kw) ||
|
||||
bsw.deskripsi_singkat.toLowerCase().includes(kw) ||
|
||||
tags.some(t => t.toLowerCase().includes(kw));
|
||||
|
||||
return matchFilter && matchSearch;
|
||||
});
|
||||
}
|
||||
|
||||
/** Render kartu + paginasi ke DOM */
|
||||
function renderCards() {
|
||||
const grid = document.getElementById('beasiswa-grid');
|
||||
const pagCont = document.getElementById('pagination');
|
||||
const filtered = getFiltered();
|
||||
const lang = activeLang; // Gunakan bahasa terbaru
|
||||
|
||||
// Bersihkan grid
|
||||
grid.innerHTML = '';
|
||||
|
||||
if (filtered.length === 0) {
|
||||
const emptyTitle = lang === 'en' ? 'No scholarships found' : 'Tidak ada beasiswa ditemukan';
|
||||
const emptyDesc = lang === 'en' ? 'Try adjusting your filters or search keywords' : 'Coba ubah filter atau kata kunci pencarian Anda';
|
||||
|
||||
grid.innerHTML = `
|
||||
<div class="empty-state" style="grid-column:1/-1">
|
||||
<i class="fas fa-search-minus"></i>
|
||||
<p style="font-size:15px;font-weight:700;color:#94a3b8;margin-bottom:6px;">
|
||||
Tidak ada beasiswa ditemukan
|
||||
</p>
|
||||
<p style="font-size:13px;color:#cbd5e1;">
|
||||
Coba ubah filter atau kata kunci pencarian Anda
|
||||
</p>
|
||||
<p style="font-size:15px;font-weight:700;color:#94a3b8;margin-bottom:6px;">${emptyTitle}</p>
|
||||
<p style="font-size:13px;color:#cbd5e1;">${emptyDesc}</p>
|
||||
</div>`;
|
||||
pagCont.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// Hitung paginasi
|
||||
const totalPages = Math.ceil(filtered.length / ITEMS_PER_PAGE);
|
||||
if (currentPage > totalPages) currentPage = totalPages;
|
||||
|
||||
const start = (currentPage - 1) * ITEMS_PER_PAGE;
|
||||
const items = filtered.slice(start, start + ITEMS_PER_PAGE);
|
||||
|
||||
// Render kartu dengan delay animasi berurutan
|
||||
items.forEach((bsw, i) => {
|
||||
const card = createCard(bsw);
|
||||
card.style.animationDelay = `${i * 55}ms`;
|
||||
@@ -163,7 +203,6 @@ function renderCards() {
|
||||
renderPagination(totalPages, pagCont);
|
||||
}
|
||||
|
||||
/** Render tombol-tombol paginasi */
|
||||
function renderPagination(total, container) {
|
||||
container.innerHTML = '';
|
||||
if (total <= 1) return;
|
||||
@@ -173,12 +212,10 @@ function renderPagination(total, container) {
|
||||
btn.className = `page-btn${isActive ? ' active' : ''}`;
|
||||
btn.innerHTML = content;
|
||||
btn.disabled = disabled;
|
||||
btn.setAttribute('aria-label', typeof page === 'number' ? `Halaman ${page}` : content);
|
||||
if (!disabled) {
|
||||
btn.addEventListener('click', () => {
|
||||
currentPage = page;
|
||||
renderCards();
|
||||
// Scroll halus ke atas direktori
|
||||
const dir = document.getElementById('directory');
|
||||
if (dir) {
|
||||
const top = dir.getBoundingClientRect().top + window.scrollY - 120;
|
||||
@@ -189,19 +226,13 @@ function renderPagination(total, container) {
|
||||
return btn;
|
||||
};
|
||||
|
||||
// Prev
|
||||
container.appendChild(make('<i class="fas fa-chevron-left" style="font-size:11px;"></i>', currentPage - 1, currentPage === 1));
|
||||
|
||||
// Halaman
|
||||
for (let i = 1; i <= total; i++) {
|
||||
container.appendChild(make(i, i, false, i === currentPage));
|
||||
}
|
||||
|
||||
// Next
|
||||
container.appendChild(make('<i class="fas fa-chevron-right" style="font-size:11px;"></i>', currentPage + 1, currentPage === total));
|
||||
}
|
||||
|
||||
/** Inisialisasi tab filter */
|
||||
function initFilterTabs() {
|
||||
document.querySelectorAll('.filter-tab').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
@@ -218,47 +249,34 @@ function initFilterTabs() {
|
||||
});
|
||||
}
|
||||
|
||||
/** Inisialisasi real-time search */
|
||||
function initSearch() {
|
||||
const input = document.getElementById('search-input');
|
||||
if (!input) return;
|
||||
|
||||
input.addEventListener('input', () => {
|
||||
// Sanitasi input sebelum digunakan (cegah XSS)
|
||||
searchKeyword = sanitize(input.value).trim();
|
||||
currentPage = 1;
|
||||
renderCards();
|
||||
});
|
||||
}
|
||||
|
||||
/** Fetch data via API.js (BeasiswaAPI) lalu render */
|
||||
async function initDirectory() {
|
||||
const grid = document.getElementById('beasiswa-grid');
|
||||
|
||||
try {
|
||||
if (!window.BeasiswaAPI) {
|
||||
throw new Error('Modul API.js (BeasiswaAPI) belum dimuat');
|
||||
}
|
||||
|
||||
if (!window.BeasiswaAPI) throw new Error('Modul API.js (BeasiswaAPI) belum dimuat');
|
||||
const { data, source } = await window.BeasiswaAPI.getBeasiswa();
|
||||
allData = data;
|
||||
console.info(`[items] beasiswa dimuat dari sumber: ${source} (${allData.length} item)`);
|
||||
|
||||
// Simpan ke window agar popup.js bisa akses
|
||||
window.beasiswaData = allData;
|
||||
|
||||
renderCards();
|
||||
} catch (err) {
|
||||
console.error('[items] gagal memuat beasiswa', err);
|
||||
const lang = activeLang;
|
||||
const errTitle = lang === 'en' ? 'Failed to load scholarship data' : 'Gagal memuat data beasiswa';
|
||||
const errDesc = lang === 'en' ? 'Could not connect to the server. Please refresh or contact admin.' : 'Tidak dapat terhubung ke server. Silakan refresh atau hubungi admin.';
|
||||
|
||||
grid.innerHTML = `
|
||||
<div class="empty-state" style="grid-column:1/-1">
|
||||
<i class="fas fa-exclamation-triangle" style="color:#fca5a5;"></i>
|
||||
<p style="font-size:15px;font-weight:700;color:#94a3b8;margin-bottom:6px;">
|
||||
Gagal memuat data beasiswa
|
||||
</p>
|
||||
<p style="font-size:13px;color:#cbd5e1;">
|
||||
Tidak dapat terhubung ke server. Silakan refresh atau hubungi admin.
|
||||
</p>
|
||||
<p style="font-size:15px;font-weight:700;color:#94a3b8;margin-bottom:6px;">${errTitle}</p>
|
||||
<p style="font-size:13px;color:#cbd5e1;">${errDesc}</p>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
@@ -267,4 +285,4 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
initFilterTabs();
|
||||
initSearch();
|
||||
initDirectory();
|
||||
});
|
||||
});
|
||||
@@ -1,136 +0,0 @@
|
||||
// =====================================================
|
||||
// i18n.js — Toggle Bahasa Indonesia / English
|
||||
// Sesuai PROJECT_CONTEXT: tersimpan di sessionStorage
|
||||
// =====================================================
|
||||
|
||||
const i18nData = {
|
||||
id: {
|
||||
'nav.tentang': 'TENTANG KAMI',
|
||||
'nav.program': 'PROGRAM',
|
||||
'nav.sdm': 'SDM',
|
||||
'nav.fasilitas': 'FASILITAS',
|
||||
'nav.agenda': 'KARYA & AGENDA',
|
||||
'nav.kontak': 'KONTAK',
|
||||
'breadcrumb.home': 'Beranda',
|
||||
'breadcrumb.profile': 'Profil',
|
||||
'breadcrumb.scholarship': 'Beasiswa',
|
||||
'hero.badge': 'Program Beasiswa',
|
||||
'hero.title': 'Beasiswa Studi &<br>Penelitian',
|
||||
'hero.subtitle': 'Teknik Informatika Universitas Tanjungpura',
|
||||
'hero.desc': 'Temukan berbagai peluang beasiswa untuk mendukung perjalanan akademik dan penelitian Anda. Program Studi Informatika UNTAN berkomitmen membantu mahasiswa meraih prestasi terbaik.',
|
||||
'hero.cta': 'Lihat Daftar Beasiswa',
|
||||
'stats.total': 'Total Mahasiswa Penerima<br>Beasiswa Semester Ini',
|
||||
'stats.gov': 'Total Penerima Beasiswa<br>Pembiayaan Pemerintah',
|
||||
'stats.private': 'Total Penerima Beasiswa<br>Pembiayaan Swasta',
|
||||
'dir.label': 'Daftar Beasiswa',
|
||||
'dir.title': 'Daftar Beasiswa',
|
||||
'dir.desc': 'Temukan beragam beasiswa dari berbagai sumber resmi yang tersedia untuk mahasiswa aktif, alumni, dan calon mahasiswa Program Studi Informatika UNTAN.',
|
||||
'dir.receive': 'Terima Bantuan Beasiswa?',
|
||||
'filter.all': 'SEMUA',
|
||||
'filter.open': 'Sedang Buka',
|
||||
'filter.closed': 'Ditutup',
|
||||
'filter.gov': 'Pemerintah',
|
||||
'filter.private': 'Swasta',
|
||||
'search.placeholder': 'Cari Nama Beasiswa...',
|
||||
'testimonial.title': 'Apa Kata Mereka?',
|
||||
'testimonial.desc': 'Cerita inspiratif dari para penerima beasiswa Program Studi Informatika UNTAN',
|
||||
'cta.title': 'Ingin Mengetahui Info Lebih Lanjut?',
|
||||
'cta.desc': 'Hubungi admin prodi atau ikuti Instagram kemahasiswaan UNTAN untuk informasi terbaru tentang beasiswa dan program akademik.',
|
||||
'cta.btn1': 'DAFTAR BEASISWA',
|
||||
'cta.btn2': 'Hubungi Kami',
|
||||
'ticker': 'Pendaftaran Beasiswa KIP Kuliah 2026 telah dibuka! — Beasiswa Pertamina Foundation open hingga Agustus 2026 — Daftarkan diri Anda sekarang! — Info lengkap tersedia di halaman direktori beasiswa — Beasiswa LPDP S2 masih tersedia hingga September 2026 — ',
|
||||
},
|
||||
en: {
|
||||
'nav.tentang': 'ABOUT US',
|
||||
'nav.program': 'PROGRAMS',
|
||||
'nav.sdm': 'HUMAN RESOURCES',
|
||||
'nav.fasilitas': 'FACILITIES',
|
||||
'nav.agenda': 'WORKS & EVENTS',
|
||||
'nav.kontak': 'CONTACT',
|
||||
'breadcrumb.home': 'Home',
|
||||
'breadcrumb.profile': 'Profile',
|
||||
'breadcrumb.scholarship': 'Scholarship',
|
||||
'hero.badge': 'Scholarship Program',
|
||||
'hero.title': 'Study & Research<br>Scholarships',
|
||||
'hero.subtitle': 'Informatics Engineering, Tanjungpura University',
|
||||
'hero.desc': 'Discover various scholarship opportunities to support your academic and research journey. The Informatics Study Program at UNTAN is committed to helping students achieve excellence.',
|
||||
'hero.cta': 'View Scholarships',
|
||||
'stats.total': 'Total Scholarship<br>Recipients This Semester',
|
||||
'stats.gov': 'Total Government<br>Scholarship Recipients',
|
||||
'stats.private': 'Total Private<br>Scholarship Recipients',
|
||||
'dir.label': 'Scholarship Directory',
|
||||
'dir.title': 'Scholarship Directory',
|
||||
'dir.desc': 'Find various scholarships from official sources available for active students, alumni, and prospective students of the UNTAN Informatics Study Program.',
|
||||
'dir.receive': 'Need Scholarship Aid?',
|
||||
'filter.all': 'ALL',
|
||||
'filter.open': 'Open',
|
||||
'filter.closed': 'Closed',
|
||||
'filter.gov': 'Government',
|
||||
'filter.private': 'Private',
|
||||
'search.placeholder': 'Search Scholarship Name...',
|
||||
'testimonial.title': 'What They Say?',
|
||||
'testimonial.desc': 'Inspiring stories from scholarship recipients at UNTAN Informatics Study Program',
|
||||
'cta.title': 'Want to Know More?',
|
||||
'cta.desc': 'Contact the program admin or follow UNTAN student affairs Instagram for the latest scholarship and academic program information.',
|
||||
'cta.btn1': 'SCHOLARSHIP LIST',
|
||||
'cta.btn2': 'Contact Us',
|
||||
'ticker': 'KIP Kuliah Scholarship 2026 registration is now open! — Pertamina Foundation Scholarship open until August 2026 — Register now! — Full info available in the scholarship directory — LPDP S2 Scholarship available until September 2026 — ',
|
||||
}
|
||||
};
|
||||
|
||||
// Load saved language from session storage
|
||||
let currentLang = sessionStorage.getItem('lang') || 'id';
|
||||
|
||||
function applyTranslations(lang) {
|
||||
const t = i18nData[lang];
|
||||
if (!t) return;
|
||||
|
||||
// Apply data-i18n attributes
|
||||
document.querySelectorAll('[data-i18n]').forEach(el => {
|
||||
const key = el.getAttribute('data-i18n');
|
||||
if (t[key] !== undefined) {
|
||||
el.innerHTML = t[key];
|
||||
}
|
||||
});
|
||||
|
||||
// Apply data-i18n-placeholder attributes
|
||||
document.querySelectorAll('[data-i18n-placeholder]').forEach(el => {
|
||||
const key = el.getAttribute('data-i18n-placeholder');
|
||||
if (t[key] !== undefined) {
|
||||
el.placeholder = t[key];
|
||||
}
|
||||
});
|
||||
|
||||
// Update HTML lang attribute
|
||||
document.documentElement.lang = lang === 'id' ? 'id' : 'en';
|
||||
}
|
||||
|
||||
// Override setLanguage to delegate to localization.js when available,
|
||||
// so both systems stay in sync (localization.js dispatches 'languageChanged'
|
||||
// which triggers our listener below to re-apply dot-key translations).
|
||||
function setLanguage(lang) {
|
||||
if (lang !== 'id' && lang !== 'en') return;
|
||||
if (typeof setLang === 'function') {
|
||||
setLang(lang); // localization.js handles state + dispatches languageChanged
|
||||
} else {
|
||||
currentLang = lang;
|
||||
sessionStorage.setItem('lang', lang);
|
||||
applyTranslations(lang);
|
||||
}
|
||||
}
|
||||
window.setLanguage = setLanguage;
|
||||
|
||||
// Re-apply dot-key translations whenever localization.js changes the language.
|
||||
// This fixes the race where localization.js overwrites dot-key elements with the
|
||||
// raw key string (its fallback for unknown keys).
|
||||
document.addEventListener('languageChanged', (e) => {
|
||||
applyTranslations(e.detail.lang);
|
||||
});
|
||||
|
||||
// Initialize on DOM ready
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Use localization.js state if it's already loaded, otherwise fall back.
|
||||
const initLang = (typeof getLang === 'function') ? getLang()
|
||||
: (sessionStorage.getItem('lang') || 'id');
|
||||
applyTranslations(initLang);
|
||||
});
|
||||
@@ -18,9 +18,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
// 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.
|
||||
const navbar = document.getElementById('navbar');
|
||||
|
||||
// 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 {
|
||||
@@ -78,7 +82,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
const navHeight = navbar.offsetHeight || 110;
|
||||
// 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' });
|
||||
|
||||
@@ -165,4 +170,4 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (contactClose) contactClose.addEventListener('click', closeContactModal);
|
||||
if (contactBackdrop) contactBackdrop.addEventListener('click', closeContactModal);
|
||||
|
||||
}); // end DOMContentLoaded
|
||||
}); // end DOMContentLoaded
|
||||
@@ -1,6 +1,7 @@
|
||||
// =====================================================
|
||||
// popup.js — Modal Detail Beasiswa
|
||||
// Tutup via: tombol ×, klik overlay, tekan Escape
|
||||
// + DUKUNGAN MULTI-BAHASA (ID/EN)
|
||||
// =====================================================
|
||||
|
||||
const overlay = document.getElementById('popup-overlay');
|
||||
@@ -8,32 +9,91 @@ const modal = document.getElementById('popup-modal');
|
||||
const closeBtn = document.getElementById('popup-close');
|
||||
const popupBody = document.getElementById('popup-content');
|
||||
|
||||
// Format tanggal ke format Indonesia
|
||||
function fmtDate(str) {
|
||||
let currentOpenId = null; // Menyimpan status beasiswa mana yang sedang dibuka
|
||||
|
||||
// Bertanya langsung ke mesin utama (localization.js)
|
||||
function getPopupLang() {
|
||||
if (typeof window.getLang === 'function') {
|
||||
return window.getLang();
|
||||
}
|
||||
return localStorage.getItem('if_untan_lang') || 'id';
|
||||
}
|
||||
|
||||
// Format tanggal dinamis (ID / EN)
|
||||
function fmtDate(str, lang) {
|
||||
if (!str) return '—';
|
||||
const d = new Date(str);
|
||||
if (isNaN(d.getTime())) return str;
|
||||
return d.toLocaleDateString('id-ID', {
|
||||
return d.toLocaleDateString(lang === 'en' ? 'en-US' : 'id-ID', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
});
|
||||
}
|
||||
|
||||
// Kamus Teks Statis Modal
|
||||
const popupDict = {
|
||||
id: {
|
||||
req_empty: 'Syarat belum tersedia. Kunjungi sumber resmi.',
|
||||
req_title: 'Syarat Umum',
|
||||
open_date: 'Tanggal Buka',
|
||||
close_date: 'Tanggal Tutup',
|
||||
contact: 'Kontak',
|
||||
more_info: 'Info Lebih Lanjut',
|
||||
no_link: 'Tautan tidak tersedia. Silakan hubungi admin prodi.',
|
||||
status_open: 'Buka',
|
||||
status_closed: 'Ditutup'
|
||||
},
|
||||
en: {
|
||||
req_empty: 'Requirements not available. Visit official source.',
|
||||
req_title: 'General Requirements',
|
||||
open_date: 'Open Date',
|
||||
close_date: 'Closing Date',
|
||||
contact: 'Contact',
|
||||
more_info: 'More Information',
|
||||
no_link: 'Link unavailable. Please contact department admin.',
|
||||
status_open: 'Open',
|
||||
status_closed: 'Closed'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Buka popup modal untuk beasiswa dengan ID tertentu
|
||||
* Data diambil dari window.beasiswaData yang diset oleh filter.js
|
||||
*/
|
||||
function openPopup(id) {
|
||||
currentOpenId = id;
|
||||
const data = window.beasiswaData || [];
|
||||
const bsw = data.find(b => b.id === id);
|
||||
if (!bsw) return;
|
||||
|
||||
const lang = getPopupLang();
|
||||
const d = popupDict[lang];
|
||||
|
||||
const isBuka = bsw.status === 'Buka' || bsw.status === 'Dibuka';
|
||||
const badgeClass = isBuka ? 'badge-buka' : 'badge-ditutup';
|
||||
|
||||
// Terjemahkan Status & Kategori
|
||||
const statusText = isBuka ? d.status_open : (lang === 'en' && (bsw.status === 'Ditutup' || bsw.status === 'Tutup') ? d.status_closed : bsw.status);
|
||||
|
||||
// Menerjemahkan Kategori (Misal: "Beasiswa Swasta" -> "Private Scholarship")
|
||||
let kategoriText = bsw.kategori;
|
||||
if (lang === 'en') {
|
||||
if (bsw.kategori_en) {
|
||||
kategoriText = bsw.kategori_en;
|
||||
} else if (bsw.kategori.toLowerCase().includes('swasta')) {
|
||||
kategoriText = 'Private Scholarship';
|
||||
} else if (bsw.kategori.toLowerCase().includes('pemerintah')) {
|
||||
kategoriText = 'Government Scholarship';
|
||||
}
|
||||
}
|
||||
|
||||
// Teks Dinamis (Ambil _en dari JSON jika ada, jika tidak pakai ID)
|
||||
const namaText = (lang === 'en' && bsw.nama_en) ? bsw.nama_en : bsw.nama;
|
||||
const deskripsiText = (lang === 'en' && bsw.deskripsi_singkat_en) ? bsw.deskripsi_singkat_en : bsw.deskripsi_singkat;
|
||||
const syaratData = (lang === 'en' && bsw.syarat_umum_en) ? bsw.syarat_umum_en : (bsw.syarat_umum || []);
|
||||
|
||||
// Bangun list syarat umum
|
||||
const syaratItems = (bsw.syarat_umum || [])
|
||||
const syaratItems = syaratData
|
||||
.map(s => `
|
||||
<li>
|
||||
<i class="fas fa-check-circle"></i>
|
||||
@@ -43,74 +103,65 @@ function openPopup(id) {
|
||||
|
||||
const syaratHTML = syaratItems
|
||||
? `<ul class="syarat-list">${syaratItems}</ul>`
|
||||
: `<p style="font-size:13px;color:#94a3b8;font-style:italic;">
|
||||
Syarat belum tersedia. Kunjungi sumber resmi.
|
||||
</p>`;
|
||||
: `<p style="font-size:13px;color:#94a3b8;font-style:italic;">${d.req_empty}</p>`;
|
||||
|
||||
const hasUrl = bsw.url_sumber && bsw.url_sumber !== '#';
|
||||
|
||||
// Render konten popup
|
||||
// Render konten popup dengan template Anda yang dipertahankan
|
||||
popupBody.innerHTML = `
|
||||
<div class="popup-body">
|
||||
<!-- Badge status + kategori -->
|
||||
<div class="popup-kategori-row">
|
||||
<span class="badge-status ${badgeClass}">${bsw.status}</span>
|
||||
<span class="popup-kategori">${bsw.kategori}</span>
|
||||
<span class="badge-status ${badgeClass}">${statusText}</span>
|
||||
<span class="popup-kategori">${kategoriText}</span>
|
||||
</div>
|
||||
|
||||
<!-- Judul -->
|
||||
<h2 id="popup-title" class="popup-title">${bsw.nama}</h2>
|
||||
<h2 id="popup-title" class="popup-title">${namaText}</h2>
|
||||
|
||||
<!-- Deskripsi singkat -->
|
||||
<p class="popup-desc">${bsw.deskripsi_singkat}</p>
|
||||
<p class="popup-desc">${deskripsiText}</p>
|
||||
|
||||
<!-- Syarat Umum -->
|
||||
<div class="popup-section-title">
|
||||
<i class="fas fa-list-check"></i>
|
||||
Syarat Umum
|
||||
${d.req_title}
|
||||
</div>
|
||||
${syaratHTML}
|
||||
|
||||
<!-- Periode Pendaftaran -->
|
||||
<div class="popup-periode">
|
||||
<div class="popup-periode-item">
|
||||
<label>Tanggal Buka</label>
|
||||
<span>${fmtDate(bsw.tanggal_buka)}</span>
|
||||
<label>${d.open_date}</label>
|
||||
<span>${fmtDate(bsw.tanggal_buka, lang)}</span>
|
||||
</div>
|
||||
<div class="popup-periode-item">
|
||||
<label>Tanggal Tutup</label>
|
||||
<span style="${!isBuka ? 'color:#EF4444;' : ''}">${fmtDate(bsw.tanggal_tutup)}</span>
|
||||
<label>${d.close_date}</label>
|
||||
<span style="${!isBuka ? 'color:#EF4444;' : ''}">${fmtDate(bsw.tanggal_tutup, lang)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Kontak -->
|
||||
${bsw.kontak ? `
|
||||
<div class="popup-kontak">
|
||||
<i class="fas fa-phone-alt"></i>
|
||||
<div>
|
||||
<div style="font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;margin-bottom:2px;">Kontak</div>
|
||||
<div style="font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;margin-bottom:2px;">${d.contact}</div>
|
||||
${bsw.kontak}
|
||||
</div>
|
||||
</div>` : ''}
|
||||
|
||||
<!-- Tombol Info Lebih Lanjut (UC-05) -->
|
||||
${hasUrl
|
||||
? `<a
|
||||
href="${bsw.url_sumber}"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="popup-cta"
|
||||
aria-label="Info lebih lanjut tentang ${bsw.nama}"
|
||||
aria-label="${d.more_info} ${bsw.nama}"
|
||||
>
|
||||
Info Lebih Lanjut
|
||||
${d.more_info}
|
||||
<i class="fas fa-external-link-alt" style="font-size:12px;"></i>
|
||||
</a>`
|
||||
: `<button class="popup-cta disabled" disabled aria-disabled="true">
|
||||
Info Lebih Lanjut
|
||||
${d.more_info}
|
||||
<i class="fas fa-external-link-alt" style="font-size:12px;"></i>
|
||||
</button>
|
||||
<p style="text-align:center;font-size:11px;color:#94a3b8;margin-top:8px;">
|
||||
Tautan tidak tersedia. Silakan hubungi admin prodi.
|
||||
${d.no_link}
|
||||
</p>`
|
||||
}
|
||||
</div>
|
||||
@@ -126,25 +177,26 @@ function openPopup(id) {
|
||||
|
||||
/** Tutup popup modal */
|
||||
function closePopup() {
|
||||
currentOpenId = null;
|
||||
overlay.classList.remove('show');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
// === Event Listeners untuk menutup popup ===
|
||||
|
||||
// 1. Tombol × close
|
||||
closeBtn?.addEventListener('click', closePopup);
|
||||
|
||||
// 2. Klik di luar area modal (backdrop)
|
||||
document.getElementById('popup-backdrop')?.addEventListener('click', closePopup);
|
||||
|
||||
// 3. Tekan Escape
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && overlay.classList.contains('show')) {
|
||||
closePopup();
|
||||
}
|
||||
});
|
||||
|
||||
// Expose ke global (dipanggil dari onclick di kartu)
|
||||
// Jika bahasa diganti SAAT popup sedang terbuka, cetak ulang isinya!
|
||||
document.addEventListener('languageChanged', () => {
|
||||
if (currentOpenId && overlay.classList.contains('show')) {
|
||||
openPopup(currentOpenId);
|
||||
}
|
||||
});
|
||||
|
||||
window.openPopup = openPopup;
|
||||
window.closePopup = closePopup;
|
||||
window.closePopup = closePopup;
|
||||
@@ -2,16 +2,25 @@
|
||||
{
|
||||
"id": "bsw-001",
|
||||
"nama": "Boustany Foundation Scholarship, MBA - Harvard University",
|
||||
"nama_en": "Boustany Foundation Scholarship, MBA - Harvard University",
|
||||
"kategori": "Swasta",
|
||||
"kategori_en": "Private",
|
||||
"status": "Buka",
|
||||
"thumbnail": "https://images.unsplash.com/photo-1541339907198-e08756dedf3f?w=400&q=80&auto=format&fit=crop",
|
||||
"deskripsi_singkat": "Beasiswa bergengsi dari Boustany Foundation untuk program MBA di Harvard University, Amerika Serikat, bagi calon pemimpin bisnis dunia.",
|
||||
"deskripsi_singkat_en": "A prestigious scholarship from the Boustany Foundation for the MBA program at Harvard University, United States, for future global business leaders.",
|
||||
"syarat_umum": [
|
||||
"Diterima atau mendaftar di program MBA Harvard Business School",
|
||||
"Memiliki rekam jejak prestasi akademik dan profesional yang luar biasa",
|
||||
"Menunjukkan potensi kepemimpinan dan kontribusi internasional",
|
||||
"Melengkapi aplikasi beasiswa secara online melalui situs resmi Boustany Foundation"
|
||||
],
|
||||
"syarat_umum_en": [
|
||||
"Accepted or applying to the MBA program at Harvard Business School",
|
||||
"Have an exceptional track record of academic and professional achievements",
|
||||
"Demonstrate leadership potential and international contribution",
|
||||
"Complete the scholarship application online through the official Boustany Foundation website"
|
||||
],
|
||||
"kontak": "Boustany Foundation — boustany-foundation.com",
|
||||
"tanggal_buka": "2026-05-21",
|
||||
"tanggal_tutup": "2027-05-31",
|
||||
@@ -22,15 +31,23 @@
|
||||
"id": "bsw-002",
|
||||
"nama": "La Trobe University - La Trobe High Achiever Scholarship (S1)",
|
||||
"kategori": "Swasta",
|
||||
"kategori_en": "Private",
|
||||
"status": "Buka",
|
||||
"thumbnail": "https://images.unsplash.com/photo-1523240795612-9a054b0db644?w=400&q=80&auto=format&fit=crop",
|
||||
"deskripsi_singkat": "Beasiswa dari La Trobe University Australia untuk mahasiswa internasional jenjang S1 berprestasi tinggi yang ingin melanjutkan studi di Melbourne.",
|
||||
"deskripsi_singkat_en": "Scholarship from La Trobe University Australia for high-achieving international undergraduate students who wish to pursue their studies in Melbourne.",
|
||||
"syarat_umum": [
|
||||
"Mendaftar sebagai mahasiswa internasional jenjang S1 (Undergraduate) di La Trobe University",
|
||||
"Memiliki nilai akademik yang sangat baik (High Achiever)",
|
||||
"Tidak sedang menerima beasiswa penuh lainnya dari La Trobe University",
|
||||
"Memenuhi persyaratan masuk dan kemampuan bahasa Inggris La Trobe University"
|
||||
],
|
||||
"syarat_umum_en": [
|
||||
"Apply as an international undergraduate student at La Trobe University",
|
||||
"Have an excellent academic record (High Achiever)",
|
||||
"Not currently receiving any other full scholarship from La Trobe University",
|
||||
"Meet La Trobe University's entry and English language requirements"
|
||||
],
|
||||
"kontak": "La Trobe University Admissions — latrobe.edu.au",
|
||||
"tanggal_buka": "2023-09-29",
|
||||
"tanggal_tutup": "2026-12-31",
|
||||
@@ -41,15 +58,23 @@
|
||||
"id": "bsw-003",
|
||||
"nama": "Charles Sturt University - International Student Support Scholarship",
|
||||
"kategori": "Swasta",
|
||||
"kategori_en": "Private",
|
||||
"status": "Buka",
|
||||
"thumbnail": "https://images.unsplash.com/photo-1560472354-b33ff0c44a43?w=400&q=80&auto=format&fit=crop",
|
||||
"deskripsi_singkat": "Beasiswa dukungan dari Charles Sturt University bagi mahasiswa internasional jenjang S1 yang mendaftar di kampus yang berlokasi di New South Wales, Australia.",
|
||||
"deskripsi_singkat_en": "Support scholarship from Charles Sturt University for international undergraduate students enrolling at a campus located in New South Wales, Australia.",
|
||||
"syarat_umum": [
|
||||
"Mendaftar sebagai mahasiswa internasional penuh waktu (full-time) jenjang S1",
|
||||
"Memenuhi persyaratan penerimaan akademik Charles Sturt University",
|
||||
"Memiliki kemampuan Bahasa Inggris yang memadai (IELTS/TOEFL)",
|
||||
"Mendaftar melalui jalur penerimaan internasional resmi"
|
||||
],
|
||||
"syarat_umum_en": [
|
||||
"Apply as a full-time international undergraduate student",
|
||||
"Meet Charles Sturt University's academic admission requirements",
|
||||
"Have adequate English language proficiency (IELTS/TOEFL)",
|
||||
"Apply through the official international admission pathway"
|
||||
],
|
||||
"kontak": "Charles Sturt University International — csu.edu.au",
|
||||
"tanggal_buka": "2026-05-21",
|
||||
"tanggal_tutup": "2026-12-31",
|
||||
@@ -60,15 +85,23 @@
|
||||
"id": "bsw-004",
|
||||
"nama": "La Trobe University - La Trobe High Achiever Scholarship (S2)",
|
||||
"kategori": "Swasta",
|
||||
"kategori_en": "Private",
|
||||
"status": "Buka",
|
||||
"thumbnail": "https://images.unsplash.com/photo-1529156069898-49953e39b3ac?w=400&q=80&auto=format&fit=crop",
|
||||
"deskripsi_singkat": "Beasiswa dari La Trobe University Australia untuk mahasiswa internasional jenjang S2 (Postgraduate) dengan pencapaian akademik tinggi.",
|
||||
"deskripsi_singkat_en": "Scholarship from La Trobe University Australia for international postgraduate students with high academic achievements.",
|
||||
"syarat_umum": [
|
||||
"Mendaftar sebagai mahasiswa internasional jenjang S2 di La Trobe University",
|
||||
"Memiliki nilai akademik yang sangat baik di jenjang sarjana sebelumnya",
|
||||
"Memenuhi persyaratan bahasa Inggris yang ditetapkan",
|
||||
"Tidak sedang menerima beasiswa lain dari universitas yang sama"
|
||||
],
|
||||
"syarat_umum_en": [
|
||||
"Apply as an international postgraduate student at La Trobe University",
|
||||
"Have excellent academic grades in the previous undergraduate degree",
|
||||
"Meet the specified English language requirements",
|
||||
"Not currently receiving another scholarship from the same university"
|
||||
],
|
||||
"kontak": "La Trobe University Admissions — latrobe.edu.au",
|
||||
"tanggal_buka": "2023-09-29",
|
||||
"tanggal_tutup": "2026-12-31",
|
||||
@@ -79,9 +112,11 @@
|
||||
"id": "bsw-005",
|
||||
"nama": "ETH Zurich - Excellence Scholarship & Opportunity Programme (ESOP)",
|
||||
"kategori": "Pemerintah",
|
||||
"kategori_en": "Government",
|
||||
"status": "Buka",
|
||||
"thumbnail": "https://images.unsplash.com/photo-1497366754035-f200968a6e72?w=400&q=80&auto=format&fit=crop",
|
||||
"deskripsi_singkat": "Beasiswa bergengsi dari ETH Zurich (salah satu universitas teknik terbaik dunia) untuk mahasiswa S2 internasional yang unggul secara akademik.",
|
||||
"deskripsi_singkat_en": "Prestigious scholarship from ETH Zurich (one of the world's best engineering universities) for academically outstanding international master's students.",
|
||||
"syarat_umum": [
|
||||
"Mendaftar program Master (S2) di ETH Zurich",
|
||||
"Lulus ujian penerimaan ETH Zurich atau memiliki setara gelar sarjana yang diakui",
|
||||
@@ -89,6 +124,13 @@
|
||||
"Melengkapi formulir aplikasi beasiswa ESOP yang terpisah dari aplikasi studi",
|
||||
"Mendapatkan surat rekomendasi dari akademisi berwenang"
|
||||
],
|
||||
"syarat_umum_en": [
|
||||
"Apply for a Master's program at ETH Zurich",
|
||||
"Pass the ETH Zurich entrance exam or hold a recognized equivalent bachelor's degree",
|
||||
"Demonstrate outstanding academic performance (usually top 10% of the cohort)",
|
||||
"Complete the ESOP scholarship application form separately from the study application",
|
||||
"Obtain a recommendation letter from authorized academics"
|
||||
],
|
||||
"kontak": "ETH Zurich Scholarship Office — ethz.ch",
|
||||
"tanggal_buka": "2026-11-01",
|
||||
"tanggal_tutup": "2026-11-30",
|
||||
@@ -99,9 +141,11 @@
|
||||
"id": "bsw-006",
|
||||
"nama": "STUST - Scholarship for International Students (S3, Fully Funded)",
|
||||
"kategori": "Swasta",
|
||||
"kategori_en": "Private",
|
||||
"status": "Buka",
|
||||
"thumbnail": "https://images.unsplash.com/photo-1580582932707-520aed937b7b?w=400&q=80&auto=format&fit=crop",
|
||||
"deskripsi_singkat": "Beasiswa penuh (fully funded) dari Southern Taiwan University of Science and Technology untuk mahasiswa internasional jenjang S3 (Doktoral).",
|
||||
"deskripsi_singkat_en": "Fully funded scholarship from Southern Taiwan University of Science and Technology for international doctoral students.",
|
||||
"syarat_umum": [
|
||||
"Mendaftar program Doktoral (S3) di STUST, Taiwan",
|
||||
"Memiliki gelar magister (S2) yang diakui",
|
||||
@@ -109,6 +153,13 @@
|
||||
"Menyerahkan proposal penelitian yang relevan dengan bidang studi yang dipilih",
|
||||
"Mendapatkan persetujuan dari calon pembimbing (supervisor) di STUST"
|
||||
],
|
||||
"syarat_umum_en": [
|
||||
"Apply for a Doctoral program at STUST, Taiwan",
|
||||
"Hold a recognized Master's degree",
|
||||
"Meet the English or Mandarin language proficiency requirements",
|
||||
"Submit a research proposal relevant to the chosen field of study",
|
||||
"Obtain approval from a prospective supervisor at STUST"
|
||||
],
|
||||
"kontak": "STUST International Affairs Office — stust.edu.tw",
|
||||
"tanggal_buka": "2026-09-15",
|
||||
"tanggal_tutup": "2026-11-30",
|
||||
@@ -119,9 +170,11 @@
|
||||
"id": "bsw-007",
|
||||
"nama": "STUST - Scholarship for International Students (S2, Partially Funded)",
|
||||
"kategori": "Swasta",
|
||||
"kategori_en": "Private",
|
||||
"status": "Buka",
|
||||
"thumbnail": "https://images.unsplash.com/photo-1606761568499-6d2451b23c66?w=400&q=80&auto=format&fit=crop",
|
||||
"deskripsi_singkat": "Beasiswa parsial (partially funded) dari Southern Taiwan University of Science and Technology untuk mahasiswa internasional jenjang S2 (Magister).",
|
||||
"deskripsi_singkat_en": "Partially funded scholarship from Southern Taiwan University of Science and Technology for international master's students.",
|
||||
"syarat_umum": [
|
||||
"Mendaftar program Magister (S2) di STUST, Taiwan",
|
||||
"Memiliki gelar sarjana (S1) yang diakui",
|
||||
@@ -129,6 +182,13 @@
|
||||
"Melengkapi berkas aplikasi secara online melalui portal resmi STUST",
|
||||
"Menyertakan transkrip nilai dan surat motivasi"
|
||||
],
|
||||
"syarat_umum_en": [
|
||||
"Apply for a Master's program at STUST, Taiwan",
|
||||
"Hold a recognized Bachelor's degree",
|
||||
"Meet the English or Mandarin language proficiency requirements",
|
||||
"Complete the application file online through the official STUST portal",
|
||||
"Include academic transcripts and a motivation letter"
|
||||
],
|
||||
"kontak": "STUST International Affairs Office — stust.edu.tw",
|
||||
"tanggal_buka": "2026-09-15",
|
||||
"tanggal_tutup": "2026-11-30",
|
||||
@@ -139,9 +199,11 @@
|
||||
"id": "bsw-008",
|
||||
"nama": "University of Melbourne - Melbourne International Undergraduate Scholarship",
|
||||
"kategori": "Swasta",
|
||||
"kategori_en": "Private",
|
||||
"status": "Buka",
|
||||
"thumbnail": "https://images.unsplash.com/photo-1504711434969-e33886168f5c?w=400&q=80&auto=format&fit=crop",
|
||||
"deskripsi_singkat": "Beasiswa untuk mahasiswa internasional berprestasi yang mendaftar program S1 di University of Melbourne, salah satu universitas terbaik di Australia.",
|
||||
"deskripsi_singkat_en": "Scholarship for outstanding international students applying for an undergraduate program at the University of Melbourne, one of Australia's best universities.",
|
||||
"syarat_umum": [
|
||||
"Merupakan mahasiswa internasional (bukan warga negara Australia atau New Zealand)",
|
||||
"Mendaftar program sarjana (S1 / Undergraduate) di University of Melbourne",
|
||||
@@ -149,6 +211,13 @@
|
||||
"Memenuhi persyaratan kemampuan bahasa Inggris (IELTS minimal 6.5)",
|
||||
"Beasiswa diberikan otomatis berdasarkan nilai akademik, tidak perlu aplikasi terpisah"
|
||||
],
|
||||
"syarat_umum_en": [
|
||||
"Be an international student (not a citizen of Australia or New Zealand)",
|
||||
"Apply for an undergraduate program at the University of Melbourne",
|
||||
"Have an exceptional academic result at the high school level",
|
||||
"Meet the English language proficiency requirements (minimum IELTS 6.5)",
|
||||
"The scholarship is awarded automatically based on academic merit, no separate application required"
|
||||
],
|
||||
"kontak": "University of Melbourne Future Students — unimelb.edu.au",
|
||||
"tanggal_buka": "2026-05-21",
|
||||
"tanggal_tutup": "2026-10-31",
|
||||
@@ -159,9 +228,11 @@
|
||||
"id": "bsw-009",
|
||||
"nama": "Charles Darwin University - CDU Global Merit Scholarship",
|
||||
"kategori": "Swasta",
|
||||
"kategori_en": "Private",
|
||||
"status": "Buka",
|
||||
"thumbnail": "https://images.unsplash.com/photo-1434030216411-0b793f4b4173?w=400&q=80&auto=format&fit=crop",
|
||||
"deskripsi_singkat": "Beasiswa merit global dari Charles Darwin University (CDU) di Darwin, Australia, untuk mahasiswa internasional S1 yang memiliki rekam jejak akademik yang kuat.",
|
||||
"deskripsi_singkat_en": "Global merit scholarship from Charles Darwin University (CDU) in Darwin, Australia, for international undergraduate students with a strong academic track record.",
|
||||
"syarat_umum": [
|
||||
"Mendaftar sebagai mahasiswa internasional baru (new international student) di CDU",
|
||||
"Mendaftar untuk program sarjana (Undergraduate/S1) penuh waktu",
|
||||
@@ -169,6 +240,13 @@
|
||||
"Memenuhi persyaratan bahasa Inggris (IELTS 6.0 atau setara)",
|
||||
"Tidak sedang menerima potongan biaya kuliah lainnya dari CDU"
|
||||
],
|
||||
"syarat_umum_en": [
|
||||
"Apply as a new international student at CDU",
|
||||
"Apply for a full-time undergraduate program",
|
||||
"Have a good high school final exam score (converted to CDU scale)",
|
||||
"Meet the English language requirements (IELTS 6.0 or equivalent)",
|
||||
"Not currently receiving any other tuition fee discount from CDU"
|
||||
],
|
||||
"kontak": "CDU International — cdu.edu.au",
|
||||
"tanggal_buka": "2026-05-21",
|
||||
"tanggal_tutup": "2026-10-09",
|
||||
@@ -179,9 +257,11 @@
|
||||
"id": "bsw-010",
|
||||
"nama": "Deakin University - Vice-Chancellor's International Scholarship",
|
||||
"kategori": "Swasta",
|
||||
"kategori_en": "Private",
|
||||
"status": "Buka",
|
||||
"thumbnail": "https://images.unsplash.com/photo-1541339907198-e08756dedf3f?w=400&q=80&auto=format&fit=crop",
|
||||
"deskripsi_singkat": "Beasiswa bergengsi dari Wakil Rektor (Vice-Chancellor) Deakin University untuk mahasiswa internasional terbaik yang akan menempuh studi S1 atau S2 di Australia.",
|
||||
"deskripsi_singkat_en": "Prestigious Vice-Chancellor's scholarship from Deakin University for the best international students pursuing an undergraduate or postgraduate degree in Australia.",
|
||||
"syarat_umum": [
|
||||
"Merupakan mahasiswa internasional (bukan warga negara Australia/New Zealand)",
|
||||
"Mendaftar untuk program S1 (Undergraduate) atau S2 (Postgraduate Coursework) di Deakin",
|
||||
@@ -189,10 +269,17 @@
|
||||
"Memenuhi persyaratan masuk dan kemampuan bahasa Inggris Deakin University",
|
||||
"Mengajukan aplikasi beasiswa sebelum tanggal penutupan yang ditetapkan"
|
||||
],
|
||||
"syarat_umum_en": [
|
||||
"Be an international student (not an Australia/New Zealand citizen)",
|
||||
"Apply for an undergraduate or postgraduate coursework program at Deakin",
|
||||
"Have a consistent and excellent academic track record",
|
||||
"Meet Deakin University's entry and English language requirements",
|
||||
"Submit the scholarship application before the specified closing date"
|
||||
],
|
||||
"kontak": "Deakin University International — deakin.edu.au",
|
||||
"tanggal_buka": "2026-05-21",
|
||||
"tanggal_tutup": "2026-09-25",
|
||||
"url_sumber": "https://www.schoters.com/id/beasiswa/deakin-university-vice-chancellor-s-international-scholarship-10",
|
||||
"tags": ["S1", "S2", "Swasta", "Australia", "Partially Funded", "Prestasi"]
|
||||
"tags": ["S1 & S2", "S2", "Swasta", "Australia", "Partially Funded", "Prestasi"]
|
||||
}
|
||||
]
|
||||
]
|
||||
@@ -3,7 +3,9 @@
|
||||
"id": "tsm-001",
|
||||
"nama": "Violaine Gunawan",
|
||||
"foto": "https://ui-avatars.com/api/?name=Violaine+Gunawan&background=111a2f&color=fff&size=200&rounded=true",
|
||||
"nama_beasiswa": "Beasiswa Bakti BCA 2026",
|
||||
"kutipan": "Beasiswa ini sangat berdampak besar. Tantangannya menyeimbangkan kuliah, asisten praktikum, organisasi, dan projek besar beasiswa. Namun, relasi di kampus dan masyarakat jadi bertambah luas. Tips: Jangan takut mencoba, pahami fokus beasiswa, sesuaikan visi misi, dan aktiflah berorganisasi!"
|
||||
"nama_beasiswa": "BEASISWA BAKTI BCA 2026",
|
||||
"nama_beasiswa_en": "BAKTI BCA SCHOLARSHIP 2026",
|
||||
"kutipan": "Beasiswa ini sangat berdampak besar. Tantangannya menyeimbangkan kuliah, asisten praktikum, organisasi, dan projek besar beasiswa. Namun, relasi di kampus dan masyarakat jadi bertambah luas. Tips: Jangan takut mencoba, pahami fokus beasiswa, sesuaikan visi misi, dan aktiflah berorganisasi!",
|
||||
"kutipan_en": "This scholarship has a massive impact. The challenge is balancing college, practicum assistant duties, organizations, and major scholarship projects. However, connections on campus and in society have expanded. Tip: Don't be afraid to try, understand the scholarship's focus, align your vision and mission, and be active in organizations!"
|
||||
}
|
||||
]
|
||||
]
|
||||
@@ -1247,7 +1247,6 @@
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-3 divide-x divide-white/20">
|
||||
|
||||
<!-- Total Penerima -->
|
||||
<div class="stat-item" data-aos="fade-up" data-aos-delay="0">
|
||||
<div class="stat-number" data-counter="20" id="ctr-total">0</div>
|
||||
<div class="stat-label" data-i18n-html="ape_stats_total">
|
||||
@@ -1255,19 +1254,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pemerintah -->
|
||||
<div class="stat-item" data-aos="fade-up" data-aos-delay="100">
|
||||
<div class="stat-number" data-counter="12" id="ctr-gov">0</div>
|
||||
<div class="stat-label" data-i18n-html="ape_stats_gov">
|
||||
Total Penerima Beasiswa<br>Pembiayaan Pemerintah
|
||||
<div class="stat-number" data-counter="12" id="ctr-s1">0</div>
|
||||
<div class="stat-label" data-i18n-html="ape_stats_s1">
|
||||
Total Penerima Beasiswa<br>Khusus S1
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Swasta -->
|
||||
<div class="stat-item" data-aos="fade-up" data-aos-delay="200">
|
||||
<div class="stat-number" data-counter="8" id="ctr-pvt">0</div>
|
||||
<div class="stat-label" data-i18n-html="ape_stats_private">
|
||||
Total Penerima Beasiswa<br>Pembiayaan Swasta
|
||||
<div class="stat-number" data-counter="8" id="ctr-studi">0</div>
|
||||
<div class="stat-label" data-i18n-html="ape_stats_studi">
|
||||
Total Penerima Beasiswa<br>Studi Lanjut
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1302,14 +1299,10 @@
|
||||
aria-label="Filter beasiswa">
|
||||
<button class="filter-tab active" data-filter="semua" role="tab" aria-selected="true"
|
||||
data-i18n="filter_all">Semua</button>
|
||||
<button class="filter-tab" data-filter="Buka" role="tab" aria-selected="false"
|
||||
data-i18n="ape_filter_open">Sedang Buka</button>
|
||||
<button class="filter-tab" data-filter="Ditutup" role="tab" aria-selected="false"
|
||||
data-i18n="ape_filter_closed">Ditutup</button>
|
||||
<button class="filter-tab" data-filter="Pemerintah" role="tab" aria-selected="false"
|
||||
data-i18n="ape_filter_gov">Pemerintah</button>
|
||||
<button class="filter-tab" data-filter="Swasta" role="tab" aria-selected="false"
|
||||
data-i18n="ape_filter_private">Swasta</button>
|
||||
<button class="filter-tab" data-filter="s1" role="tab" aria-selected="false"
|
||||
data-i18n="ape_filter_s1">S1</button>
|
||||
<button class="filter-tab" data-filter="studi_lanjut" role="tab" aria-selected="false"
|
||||
data-i18n="ape_filter_studi">Studi Lanjut</button>
|
||||
</div>
|
||||
|
||||
<!-- Real-Time Search -->
|
||||
@@ -1487,6 +1480,7 @@
|
||||
<script src="https://unpkg.com/aos@2.3.4/dist/aos.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
|
||||
|
||||
<script src="../../assets/global/localization.js"></script>
|
||||
|
||||
<script src="assets/js/counter.js"></script>
|
||||
<script src="assets/js/API.js"></script>
|
||||
@@ -1608,7 +1602,6 @@
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script src="../../assets/global/localization.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user