first bro

This commit is contained in:
Araya's Project
2026-06-04 14:08:17 +07:00
parent 8e9137f725
commit df4878a8af
12 changed files with 1867 additions and 626 deletions
+4
View File
@@ -1,3 +1,7 @@
node_modules/
dist/
.DS_Store
.env
.env.local
groups/**/.env
groups/**/.env.local
+54
View File
@@ -569,6 +569,33 @@ const translations = {
kontrib_guideline_3: "Sertakan fakta dan data yang akurat dan dapat diverifikasi.",
kontrib_guideline_4: "Hindari plagiarisme. Karya harus orisinal.",
kontrib_guideline_5: "Tim redaksi berhak menyunting konten untuk keperluan editorial.",
// --- apeNameTeamE (Halaman Beasiswa) ---
ape_breadcrumb_profile: 'Profil',
ape_breadcrumb_scholarship: 'Beasiswa',
ape_hero_badge: 'Program Beasiswa',
ape_hero_title: 'Beasiswa Studi &amp;<br>Penelitian',
ape_hero_subtitle: 'Teknik Informatika Universitas Tanjungpura',
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_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_search_placeholder: 'Cari Nama Beasiswa...',
ape_testimonial_title: 'Apa Kata Mereka?',
ape_testimonial_desc: 'Cerita inspiratif dari para penerima beasiswa Program Studi Informatika UNTAN',
ape_cta_title: 'Ingin Mengetahui Info Lebih Lanjut?',
ape_cta_desc: 'Hubungi admin prodi atau ikuti Instagram kemahasiswaan UNTAN untuk informasi terbaru tentang beasiswa dan program akademik.',
ape_cta_btn1: 'DAFTAR BEASISWA',
ape_cta_btn2: 'Hubungi Kami',
},
en: {
@@ -1135,6 +1162,33 @@ const translations = {
kontrib_guideline_3: "Include accurate and verifiable facts and data.",
kontrib_guideline_4: "Avoid plagiarism. Work must be original.",
kontrib_guideline_5: "The editorial team reserves the right to edit content for editorial purposes.",
// --- apeNameTeamE (Scholarship Page) ---
ape_breadcrumb_profile: 'Profile',
ape_breadcrumb_scholarship: 'Scholarship',
ape_hero_badge: 'Scholarship Program',
ape_hero_title: 'Study &amp; Research<br>Scholarships',
ape_hero_subtitle: 'Informatics Engineering, Tanjungpura University',
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_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_search_placeholder: 'Search Scholarship Name...',
ape_testimonial_title: 'What They Say?',
ape_testimonial_desc: 'Inspiring stories from scholarship recipients at UNTAN Informatics Study Program',
ape_cta_title: 'Want to Know More?',
ape_cta_desc: 'Contact the program admin or follow UNTAN student affairs Instagram for the latest scholarship and academic program information.',
ape_cta_btn1: 'SCHOLARSHIP LIST',
ape_cta_btn2: 'Contact Us',
}
};
+1 -1
View File
@@ -997,7 +997,7 @@
<!-- Overlay for mega menu -->
<div id="mega-overlay" class="fixed inset-0 bg-black/30 backdrop-blur-sm z-30"></div>
</div>
</div>
<div id="mobile-menu"
class="fixed inset-0 bg-slate-900 z-[60] transform -translate-x-full lg:hidden flex flex-col transition-transform duration-300"
+25 -9
View File
@@ -80,20 +80,28 @@ body {
.navbar-logo-name {
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
font-size: 15px;
font-weight: 800;
font-size: 1.125rem; /* text-lg = 18px */
font-weight: 700;
color: #ffffff;
line-height: 1;
letter-spacing: 0.5px;
letter-spacing: 0.1em; /* tracking-widest */
text-transform: uppercase;
margin-bottom: 0.25rem;
}
.navbar-logo-sub {
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
font-size: 9px;
font-weight: 600;
color: rgba(255,255,255,0.55);
letter-spacing: 1px;
margin-top: 2px;
font-size: 10px; /* text-[10px] */
font-weight: 400;
color: rgba(226, 232, 240, 0.9); /* text-slate-200 opacity-90 */
letter-spacing: 0.15em; /* tracking-[0.15em] */
margin-top: 0.125rem;
text-transform: uppercase;
}
@media (min-width: 1024px) {
.navbar-logo-name { font-size: 1.5rem; } /* lg:text-2xl = 24px */
.navbar-logo-sub { font-size: 0.875rem; letter-spacing: 0.3em; } /* lg:text-sm, lg:tracking-[0.3em] */
}
/* "SATU UNTAN" text */
@@ -179,10 +187,14 @@ body {
.navbar-main {
background: var(--color-secondary);
height: 48px;
display: flex;
display: none;
align-items: center;
}
@media (min-width: 1024px) {
.navbar-main { display: flex; }
}
/* Nav links — teks gelap di atas latar kuning */
.nav-link {
position: relative;
@@ -1270,6 +1282,10 @@ body {
}
/* ===== FOOTER ===== */
footer {
font-family: 'Roboto', sans-serif;
}
.site-footer {
background: var(--color-primary);
color: white;
+74
View File
@@ -0,0 +1,74 @@
// =====================================================
// API.js — Mekanisme Fetch Data Beasiswa (terpusat)
// -----------------------------------------------------
// Alur: coba API remote (Directus publik) → jika gagal,
// fallback ke berkas lokal `data/beasiswa.json`.
//
// Data remote dinormalisasi ke skema UI yang sama dengan
// data lokal agar filter.js dan popup.js tidak perlu berubah.
// =====================================================
(function (global) {
'use strict';
const CONFIG = {
baseUrl: 'https://api.ifuntanhub.dev',
collection: '/items/beasiswa',
localSource: 'data/beasiswa.json',
};
// Normalise status remote → nilai yang dipakai UI
function normalizeStatus(s) {
if (!s) return 'Ditutup';
const lower = s.toLowerCase();
if (lower === 'dibuka' || lower === 'buka') return 'Buka';
if (lower === 'ditutup') return 'Ditutup';
return s; // "Segera Dibuka" atau nilai lain: tampilkan apa adanya
}
// Petakan satu item Directus ke skema yang diharapkan filter.js / popup.js
function normalizeItem(item) {
const parts = [item.jenjang, item.negara, item.tipe_pendanaan].filter(Boolean);
return {
id: String(item.id),
nama: item.nama || '',
status: normalizeStatus(item.status),
kategori: item.tipe_pendanaan || '',
thumbnail: '',
deskripsi_singkat: parts.join(' · ') || 'Info beasiswa tersedia di sumber resmi.',
tanggal_buka: item.tanggal_buka || '',
tanggal_tutup: item.tenggat_waktu || '',
url_sumber: item.url || '',
syarat_umum: [],
kontak: '',
tags: parts,
};
}
async function fetchRemote() {
const res = await fetch(CONFIG.baseUrl + CONFIG.collection);
if (!res.ok) throw new Error(`API merespons HTTP ${res.status}`);
const json = await res.json();
return (json.data || []).map(normalizeItem);
}
async function fetchLocal() {
const res = await fetch(CONFIG.localSource);
if (!res.ok) throw new Error(`Gagal memuat data lokal (HTTP ${res.status})`);
const json = await res.json();
return Array.isArray(json) ? json : json.data || [];
}
async function getBeasiswa() {
try {
const data = await fetchRemote();
return { data, source: 'remote' };
} catch (err) {
console.warn('[BeasiswaAPI] API remote gagal, fallback ke data lokal:', err.message);
const data = await fetchLocal();
return { data, source: 'local' };
}
}
global.BeasiswaAPI = { config: CONFIG, getBeasiswa, fetchRemote, fetchLocal };
})(window);
+10 -6
View File
@@ -32,7 +32,7 @@ function sanitize(str) {
* Gunakan textContent/setAttribute — hindari langsung innerHTML dari user input
*/
function createCard(bsw) {
const isBuka = bsw.status === 'Buka';
const isBuka = bsw.status === 'Buka' || bsw.status === 'Dibuka';
const article = document.createElement('article');
article.className = 'scholarship-card fade-in-up';
@@ -207,21 +207,25 @@ function initSearch() {
});
}
/** Fetch data JSON dan render */
/** Fetch data via API.js (BeasiswaAPI) lalu render */
async function initDirectory() {
const grid = document.getElementById('beasiswa-grid');
try {
const res = await fetch('data/beasiswa.json');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
if (!window.BeasiswaAPI) {
throw new Error('Modul API.js (BeasiswaAPI) belum dimuat');
}
allData = await res.json();
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);
grid.innerHTML = `
<div class="empty-state" style="grid-column:1/-1">
<i class="fas fa-exclamation-triangle" style="color:#fca5a5;"></i>
@@ -229,7 +233,7 @@ async function initDirectory() {
Gagal memuat data beasiswa
</p>
<p style="font-size:13px;color:#cbd5e1;">
Pastikan menggunakan live server. Silakan refresh atau hubungi admin.
Tidak dapat terhubung ke server. Silakan refresh atau hubungi admin.
</p>
</div>`;
}
+21 -13
View File
@@ -105,24 +105,32 @@ function applyTranslations(lang) {
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;
currentLang = lang;
sessionStorage.setItem('lang', lang);
// Update button visual states
document.querySelectorAll('.lang-btn').forEach(btn => {
const isActive = btn.dataset.lang === lang;
btn.classList.toggle('active', isActive);
});
applyTranslations(lang);
if (typeof setLang === 'function') {
setLang(lang); // localization.js handles state + dispatches languageChanged
} else {
currentLang = lang;
sessionStorage.setItem('lang', lang);
applyTranslations(lang);
}
}
// Expose globally so onclick in HTML works
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', () => {
setLanguage(currentLang);
// Use localization.js state if it's already loaded, otherwise fall back.
const initLang = (typeof getLang === 'function') ? getLang()
: (sessionStorage.getItem('lang') || 'id');
applyTranslations(initLang);
});
+10 -19
View File
@@ -55,24 +55,17 @@ document.addEventListener('DOMContentLoaded', () => {
// ─── Hamburger Menu (Mobile) ─────────────────────────
const hamburger = document.getElementById('hamburger');
const mobileMenu = document.getElementById('mobile-menu');
const mobileBtn = document.getElementById('mobile-menu-button');
const mobileMenu = document.getElementById('mobile-menu');
const closeMobileBtn = document.getElementById('close-mobile-menu');
hamburger?.addEventListener('click', () => {
const isOpen = !mobileMenu.classList.contains('hidden');
mobileMenu.classList.toggle('hidden');
const closeMobileMenu = () => {
mobileMenu?.classList.add('-translate-x-full');
mobileBtn?.setAttribute('aria-expanded', 'false');
};
// Toggle icon
const icon = hamburger.querySelector('i');
if (isOpen) {
icon.className = 'fas fa-bars text-lg';
} else {
icon.className = 'fas fa-times text-lg';
}
// Aksesibilitas
hamburger.setAttribute('aria-expanded', String(!isOpen));
});
// openMobileMenu handled by inline script in HTML; this covers close-on-scroll
closeMobileBtn?.addEventListener('click', closeMobileMenu);
// ─── Smooth Scroll untuk anchor links ──────────────
@@ -90,9 +83,7 @@ document.addEventListener('DOMContentLoaded', () => {
window.scrollTo({ top, behavior: 'smooth' });
// Tutup mobile menu jika sedang terbuka
mobileMenu?.classList.add('hidden');
const icon = hamburger?.querySelector('i');
if (icon) icon.className = 'fas fa-bars text-lg';
closeMobileMenu();
});
});
+1 -1
View File
@@ -29,7 +29,7 @@ function openPopup(id) {
const bsw = data.find(b => b.id === id);
if (!bsw) return;
const isBuka = bsw.status === 'Buka';
const isBuka = bsw.status === 'Buka' || bsw.status === 'Dibuka';
const badgeClass = isBuka ? 'badge-buka' : 'badge-ditutup';
// Bangun list syarat umum
File diff suppressed because it is too large Load Diff
+3
View File
@@ -178,6 +178,9 @@
"deadline": "2026-09-25",
"status": "Pendaftaran dibuka",
"url": "https://www.schoters.com/id/beasiswa/deakin-university-vice-chancellor-s-international-scholarship-10"
},
{
}
],
"pagination": {
+7 -8
View File
@@ -886,9 +886,9 @@
}
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"version": "3.3.12",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
"dev": true,
"funding": [
{
@@ -917,7 +917,6 @@
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -926,9 +925,9 @@
}
},
"node_modules/postcss": {
"version": "8.5.8",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
"integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
"version": "8.5.15",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
"integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
"dev": true,
"funding": [
{
@@ -946,7 +945,7 @@
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.11",
"nanoid": "^3.3.12",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},