diff --git a/assets/global/localization.js b/assets/global/localization.js
index 82d1ca7..45130c4 100644
--- a/assets/global/localization.js
+++ b/assets/global/localization.js
@@ -569,6 +569,51 @@ 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.",
+
+ // --- Group 06: ACAAB - Prestasi & Karya Mahasiswa ---
+ grp_acaab_hero_badge: "MAHASISWA & PRESTASI",
+ grp_acaab_hero_title: "Sorotan dan Prestasi Karya Mahasiswa",
+ grp_acaab_hero_desc: "Galeri pencapaian, penghargaan, dan portofolio inovasi teknologi yang dikembangkan oleh mahasiswa Program Studi Informatika Universitas Tanjungpura.",
+ grp_acaab_stats_heading: "Pencapaian dalam Angka",
+ grp_acaab_stats_total_ach: "Total Prestasi",
+ grp_acaab_stats_total_proj: "Total Karya",
+ grp_acaab_stats_intl: "Internasional",
+ grp_acaab_stats_national: "Nasional",
+ grp_acaab_stats_regional: "Regional",
+ grp_acaab_stats_data_note: "Data dikumpulkan per",
+ grp_acaab_ach_title: "Prestasi Mahasiswa",
+ grp_acaab_ach_desc: "Daftar pencapaian dan penghargaan yang diraih oleh mahasiswa Informatika UNTAN di berbagai kompetisi.",
+ grp_acaab_ach_search: "Cari prestasi...",
+ grp_acaab_ach_filter_level: "Semua Level",
+ grp_acaab_ach_filter_cat: "Semua Kategori",
+ grp_acaab_ach_filter_year: "Semua Tahun",
+ grp_acaab_ach_showing: "Menampilkan",
+ grp_acaab_ach_results: "prestasi",
+ grp_acaab_ach_load_more: "Tampilkan Lebih Banyak",
+ grp_acaab_ach_show_less: "Tampilkan Lebih Sedikit",
+ grp_acaab_ach_empty: "Tidak ada prestasi yang cocok dengan filter.",
+ grp_acaab_ach_loading: "Memuat data prestasi...",
+ grp_acaab_ach_error: "Gagal memuat data prestasi.",
+ grp_acaab_proj_title: "Top Projects & Portofolio",
+ grp_acaab_proj_desc: "Jelajahi inovasi digital dan aplikasi nyata yang dikembangkan oleh mahasiswa Informatika UNTAN.",
+ grp_acaab_proj_scroll_hint: "← Geser untuk melihat lebih banyak →",
+ grp_acaab_proj_search: "Cari karya...",
+ grp_acaab_proj_filter_cat: "Semua Kategori",
+ grp_acaab_proj_filter_year: "Semua Tahun",
+ grp_acaab_proj_load_more: "Tampilkan Lebih Banyak",
+ grp_acaab_proj_empty: "Tidak ada karya yang cocok dengan filter.",
+ grp_acaab_proj_loading: "Memuat data karya...",
+ grp_acaab_proj_error: "Gagal memuat data karya.",
+ grp_acaab_modal_team: "Tim / Peserta",
+ grp_acaab_modal_generation: "Angkatan",
+ grp_acaab_modal_members: "Anggota Tim",
+ grp_acaab_modal_visit: "Kunjungi Proyek",
+ grp_acaab_modal_read_more: "Selengkapnya",
+ grp_acaab_modal_no_link: "Link tidak tersedia",
+ grp_acaab_cta_badge: "HUBUNGI KAMI",
+ grp_acaab_cta_title: "Ingin Tahu Lebih Lanjut?",
+ grp_acaab_cta_desc: "Tertarik bergabung dengan Program Studi Informatika UNTAN, mendaftarkan prestasi mahasiswa, atau menjalin kerja sama? Hubungi kami sekarang.",
+ grp_acaab_cta_btn: "Chat via WhatsApp",
},
en: {
@@ -1135,6 +1180,51 @@ 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.",
+
+ // --- Group 06: ACAAB - Prestasi & Karya Mahasiswa ---
+ grp_acaab_hero_badge: "STUDENTS & ACHIEVEMENTS",
+ grp_acaab_hero_title: "Student Spotlights & Achievements",
+ grp_acaab_hero_desc: "Gallery of accomplishments, awards, and technology innovation portfolios developed by Informatics students of Tanjungpura University.",
+ grp_acaab_stats_heading: "Achievements by the Numbers",
+ grp_acaab_stats_total_ach: "Total Achievements",
+ grp_acaab_stats_total_proj: "Total Projects",
+ grp_acaab_stats_intl: "International",
+ grp_acaab_stats_national: "National",
+ grp_acaab_stats_regional: "Regional",
+ grp_acaab_stats_data_note: "Data collected as of",
+ grp_acaab_ach_title: "Student Achievements",
+ grp_acaab_ach_desc: "List of awards and accomplishments by Informatics UNTAN students in various competitions.",
+ grp_acaab_ach_search: "Search achievements...",
+ grp_acaab_ach_filter_level: "All Levels",
+ grp_acaab_ach_filter_cat: "All Categories",
+ grp_acaab_ach_filter_year: "All Years",
+ grp_acaab_ach_showing: "Showing",
+ grp_acaab_ach_results: "achievements",
+ grp_acaab_ach_load_more: "Show More",
+ grp_acaab_ach_show_less: "Show Less",
+ grp_acaab_ach_empty: "No achievements match the current filter.",
+ grp_acaab_ach_loading: "Loading achievements...",
+ grp_acaab_ach_error: "Failed to load achievements.",
+ grp_acaab_proj_title: "Top Projects & Portfolios",
+ grp_acaab_proj_desc: "Explore digital innovations and real-world applications developed by Informatics UNTAN students.",
+ grp_acaab_proj_scroll_hint: "← Scroll to see more →",
+ grp_acaab_proj_search: "Search projects...",
+ grp_acaab_proj_filter_cat: "All Categories",
+ grp_acaab_proj_filter_year: "All Years",
+ grp_acaab_proj_load_more: "Show More",
+ grp_acaab_proj_empty: "No projects match the current filter.",
+ grp_acaab_proj_loading: "Loading projects...",
+ grp_acaab_proj_error: "Failed to load projects.",
+ grp_acaab_modal_team: "Team / Participant",
+ grp_acaab_modal_generation: "Batch Year",
+ grp_acaab_modal_members: "Team Members",
+ grp_acaab_modal_visit: "Visit Project",
+ grp_acaab_modal_read_more: "Read More",
+ grp_acaab_modal_no_link: "Link not available",
+ grp_acaab_cta_badge: "CONTACT US",
+ grp_acaab_cta_title: "Want to Know More?",
+ grp_acaab_cta_desc: "Interested in joining the Informatics Study Program at UNTAN, registering a student achievement, or establishing a partnership? Contact us now.",
+ grp_acaab_cta_btn: "Chat via WhatsApp",
}
};
diff --git a/groups/ACAAB/hero.png b/groups/ACAAB/hero.png
new file mode 100644
index 0000000..33f70e0
Binary files /dev/null and b/groups/ACAAB/hero.png differ
diff --git a/groups/ACAAB/index.html b/groups/ACAAB/index.html
index cc0e229..4fefabf 100644
--- a/groups/ACAAB/index.html
+++ b/groups/ACAAB/index.html
@@ -1,5 +1,6 @@
+
@@ -7,9 +8,12 @@
-
+
-
+
Langsung ke Konten Utama
@@ -1139,18 +1144,340 @@
-
+
-
Tugas Halaman Statis
+
Tugas
+ Halaman Statis
Sorotan dan Prestasi Karya Mahasiswa
-
Kelompok 06 — ACAAB · Informatika UNTAN 2026
+
Kelompok 06 — ACAAB · Informatika UNTAN 2026
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
MAHASISWA & PRESTASI
+
Sorotan dan Prestasi Karya Mahasiswa
+
Temukan pencapaian luar biasa mahasiswa Informatika
+ UNTAN.
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Total Prestasi
+
+
+
+ Total Portofolio
+
+
+
+
+
+
+
+
+ 0
+
+ Prestasi
Mahasiswa
+
+
+
+
+
+
+
+
+
+
+ 0
+
+ Proyek
Mahasiswa
+
+
+
+
+
+
+
+
+
+
+
+
+
Prestasi Mahasiswa
+
Daftar pencapaian dan penghargaan yang diraih oleh mahasiswa Informatika UNTAN di berbagai kompetisi.
+
+
+
+
+
+
+
+
+ Filter Prestasi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Top Projects & Portofolio
+
Jelajahi inovasi digital dan aplikasi nyata yang dikembangkan oleh mahasiswa Informatika UNTAN.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Filter Portofolio
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
← Geser untuk melihat lebih banyak →
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
HUBUNGI KAMI
+
Ingin Tahu Lebih Lanjut?
+
Tertarik bergabung dengan Program Studi
+ Informatika UNTAN, mendaftarkan prestasi mahasiswa, atau menjalin kerja sama?
+ Hubungi kami sekarang.
+
+
+
+
+
+
+
+
+
+
+
@@ -1215,8 +1542,8 @@
-

+
@@ -1312,27 +1639,29 @@
})();
-
+
-
+
+
+
\ No newline at end of file
diff --git a/groups/ACAAB/main.js b/groups/ACAAB/main.js
new file mode 100644
index 0000000..a40ece1
--- /dev/null
+++ b/groups/ACAAB/main.js
@@ -0,0 +1,822 @@
+/**
+ * main.js — ACAAB Prestasi & Karya Mahasiswa
+ * Vanilla JS: fetch data Directus CMS, render sections, handle interactions.
+ */
+
+// ─── Constants ────────────────────────────────────────────────────────────────
+
+const API_BASE = 'https://api.ifuntanhub.dev';
+const LANG_KEY = 'if_untan_lang';
+
+const BIDANG_MAP = {
+ 1: 'Kecerdasan Buatan', 2: 'Cyber Security', 3: 'Data',
+ 4: 'Software Engineering', 5: 'Competitive Programming', 6: 'IoT',
+ 7: 'Smart City', 8: 'Web3', 9: 'Karya Tulis Ilmiah', 10: 'Robotik',
+};
+
+// ─── Utilities ────────────────────────────────────────────────────────────────
+
+function escHtml(str) {
+ if (str === null || str === undefined) return '';
+ return String(str)
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+}
+
+function getLang() {
+ try {
+ return localStorage.getItem(LANG_KEY) ||
+ (navigator.language && navigator.language.startsWith('en') ? 'en' : 'id');
+ } catch (_) { return 'id'; }
+}
+
+// Baca dari translations global yang didefinisikan localization.js
+function t(key) {
+ try {
+ const lang = getLang();
+ if (typeof translations !== 'undefined' && translations[lang] && translations[lang][key]) {
+ return translations[lang][key];
+ }
+ } catch (_) {}
+ return key;
+}
+
+// ─── Data Transformation ──────────────────────────────────────────────────────
+
+function extractRank(title) {
+ const m = (title || '').match(/(Juara\s+\d+|Juara\s+Harapan\s*\d*|Top\s+\d+|Finalis|Peringkat\s+\d+|Medali\s+\w+)/i);
+ return m ? m[0] : 'Pemenang';
+}
+
+function extractLevel(title, category) {
+ const text = ((title || '') + ' ' + (category || '')).toLowerCase();
+ if (/internasional|international|global|world|asean|unesco/.test(text)) return 'Internasional';
+ if (/regional|kalbar|kalimantan|provinsi|kota|kabupaten|lokal/.test(text)) return 'Regional';
+ return 'Nasional';
+}
+
+function extractCompetition(title) {
+ return (title || '')
+ .replace(/^(Juara\s+\d+|Juara\s+Harapan\s*\d*|Top\s+\d+|Finalis|Peringkat\s+\d+|Medali\s+\w+)\s+(di\s+|pada\s+|dalam\s+)?/i, '')
+ .trim();
+}
+
+function getImageUrl(item, seed) {
+ if (item.galeri && Array.isArray(item.galeri) && item.galeri.length > 0) {
+ const first = item.galeri[0];
+ const imgId = typeof first === 'string' ? first : (first.directus_files_id || first.id);
+ if (imgId) return `${API_BASE}/assets/${imgId}`;
+ }
+ if (item.foto) return `${API_BASE}/assets/${item.foto}`;
+ return `https://picsum.photos/seed/${seed}/800/600`;
+}
+
+function transformAchievement(item) {
+ const id = String(item.id);
+ const title = item.nama_prestasi || 'Untitled Achievement';
+
+ let category = 'Umum';
+ if (item.bidang_id) {
+ category = (typeof item.bidang_id === 'object' && item.bidang_id.nama_bidang)
+ ? item.bidang_id.nama_bidang
+ : (BIDANG_MAP[item.bidang_id] || 'Umum');
+ }
+
+ const rank = extractRank(title);
+ const level = extractLevel(title, category);
+ const competition = extractCompetition(title);
+ const year = parseInt(item.tahun) || new Date().getFullYear();
+ const anggota = Array.isArray(item.anggota) ? item.anggota : [];
+ const team = item.nama_tim || (anggota.length > 0 ? anggota[0].nama : 'Mahasiswa');
+ const avatar = (item.nama_tim || title || 'IF').substring(0, 2).toUpperCase();
+ const description = item.deskripsi ||
+ `${team} berhasil meraih prestasi luar biasa sebagai ${rank} dalam ajang ${competition} tahun ${year} pada bidang ${category.toLowerCase()}.`;
+
+ return {
+ id, title, category, competition, rank, year, level, description,
+ team,
+ nim: item.nim || '',
+ generation: item.angkatan ? `Angkatan ${item.angkatan}` : 'Informatika',
+ image: getImageUrl(item, `prestasi-${id}`),
+ avatar,
+ members: anggota.map(m => ({ nama: m.nama || '', angkatan: m.angkatan ? parseInt(m.angkatan) : undefined })),
+ };
+}
+
+function transformProject(item) {
+ const id = String(item.id);
+ const title = item.nama_proyek || 'Untitled Project';
+ const anggota = Array.isArray(item.anggota) ? item.anggota : [];
+ const studentName = item.nama_mahasiswa || (anggota.length > 0 ? anggota[0].nama : 'Mahasiswa');
+ const year = parseInt(item.tahun) || new Date().getFullYear();
+ const description = item.deskripsi || 'Tidak ada deskripsi.';
+
+ let categories = [];
+ if (item.bidang && Array.isArray(item.bidang)) {
+ categories = item.bidang.map(b => {
+ if (b.bidang_id && typeof b.bidang_id === 'object' && b.bidang_id.nama_bidang) return b.bidang_id.nama_bidang;
+ if (typeof b.bidang_id === 'number') return BIDANG_MAP[b.bidang_id] || 'Umum';
+ return 'Umum';
+ }).filter(Boolean);
+ }
+ if (categories.length === 0) categories = ['Umum'];
+
+ let link = item.url || null;
+ if (link && !/^https?:\/\//i.test(link)) link = `https://${link}`;
+
+ return {
+ id, title, studentName, year, description, categories, link,
+ nim: item.nim || '',
+ image: getImageUrl(item, `project-${id}`),
+ catatan: item.catatan || '',
+ members: anggota.map(m => ({ nama: m.nama || '', nim: m.nim || '' })),
+ };
+}
+
+// ─── API Fetch ────────────────────────────────────────────────────────────────
+
+async function fetchAchievements() {
+ const res = await fetch(`${API_BASE}/items/prestasi?fields=*.*`);
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
+ const { data } = await res.json();
+ return (data || [])
+ .filter(item => item.status === 'published')
+ .map(transformAchievement);
+}
+
+async function fetchProjects() {
+ const res = await fetch(`${API_BASE}/items/portfolio?fields=*,bidang.bidang_id.nama_bidang,galeri.*`);
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
+ const { data } = await res.json();
+ return (data || [])
+ .filter(item => item.status === 'published')
+ .map(transformProject);
+}
+
+// ─── App State ────────────────────────────────────────────────────────────────
+
+let achievements = [];
+let projects = [];
+let achVisible = 3;
+let projVisible = 6;
+
+let achFilter = { search: '', level: '', cat: '', year: '' };
+let projFilter = { search: '', cat: '', year: '' };
+
+// ─── Init ─────────────────────────────────────────────────────────────────────
+
+document.addEventListener('DOMContentLoaded', async () => {
+ initWaButton();
+
+ // Loading placeholders
+ showSkeleton('acaab-ach-grid', 3);
+ showSkeleton('acaab-proj-track', 4);
+
+ const [achResult, projResult] = await Promise.allSettled([
+ fetchAchievements(),
+ fetchProjects(),
+ ]);
+
+ if (achResult.status === 'fulfilled') {
+ achievements = achResult.value;
+ } else {
+ console.error('[ACAAB] achievements error:', achResult.reason);
+ showError('acaab-ach-grid', t('grp_acaab_ach_error'));
+ }
+
+ if (projResult.status === 'fulfilled') {
+ projects = projResult.value;
+ } else {
+ console.error('[ACAAB] projects error:', projResult.reason);
+ showError('acaab-proj-track', t('grp_acaab_proj_error'));
+ }
+
+ initStats();
+ initAchievements();
+ initProjects();
+ updateDataNote();
+});
+
+// Re-render saat bahasa berubah dari kit navbar
+document.addEventListener('languageChanged', () => {
+ updateStats();
+ renderAchievements();
+ buildAchFilterOptions();
+ renderProjects();
+ buildProjFilterOptions();
+ updateWaButton();
+ updateDataNote();
+ updateSearchPlaceholders();
+});
+
+// ─── Skeleton / Error ─────────────────────────────────────────────────────────
+
+function showSkeleton(containerId, count) {
+ const el = document.getElementById(containerId);
+ if (!el) return;
+ el.innerHTML = Array(count).fill(0).map(() =>
+ ``
+ ).join('');
+}
+
+function showError(containerId, msg) {
+ const el = document.getElementById(containerId);
+ if (el) el.innerHTML = `${escHtml(msg)}
`;
+}
+
+// ─── Stats ────────────────────────────────────────────────────────────────────
+
+function initStats() {
+ updateStats();
+
+ const statsEl = document.getElementById('acaab-stats');
+ if (!statsEl) return;
+
+ const intl = achievements.filter(a => a.level === 'Internasional').length;
+ const national = achievements.filter(a => a.level === 'Nasional').length;
+ const regional = achievements.filter(a => a.level === 'Regional').length;
+
+ const observer = new IntersectionObserver(entries => {
+ entries.forEach(entry => {
+ if (!entry.isIntersecting) return;
+ animateCount('stat-total-ach', achievements.length, 1600);
+ animateCount('stat-total-proj', projects.length, 1600);
+ animateCount('stat-intl', intl, 1200);
+ animateCount('stat-national', national, 1350);
+ animateCount('stat-regional', regional, 1500);
+ observer.disconnect();
+ });
+ }, { threshold: 0.4 });
+
+ observer.observe(statsEl);
+}
+
+function updateStats() {
+ const set = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; };
+ set('stat-total-ach', achievements.length);
+ set('stat-total-proj', projects.length);
+ set('stat-intl', achievements.filter(a => a.level === 'Internasional').length);
+ set('stat-national', achievements.filter(a => a.level === 'Nasional').length);
+ set('stat-regional', achievements.filter(a => a.level === 'Regional').length);
+}
+
+function animateCount(elId, target, duration) {
+ const el = document.getElementById(elId);
+ if (!el) return;
+ const start = performance.now();
+ (function step(now) {
+ const p = Math.min((now - start) / duration, 1);
+ const eased = 1 - Math.pow(1 - p, 4); // easeOutQuart
+ el.textContent = Math.round(target * eased).toLocaleString();
+ if (p < 1) requestAnimationFrame(step);
+ })(start);
+}
+
+function updateDataNote() {
+ const el = document.getElementById('acaab-data-note');
+ if (!el) return;
+ const lang = getLang();
+ const locale = lang === 'en' ? 'en-US' : 'id-ID';
+ const date = new Date().toLocaleDateString(locale, { day: 'numeric', month: 'long', year: 'numeric' });
+ el.textContent = `${t('grp_acaab_stats_data_note')} ${date}`;
+}
+
+// ─── Achievements ─────────────────────────────────────────────────────────────
+
+const ACH_INIT = 3;
+const ACH_MORE = 3;
+
+const LEVEL_CLASS = {
+ Internasional: 'acaab-badge-intl',
+ Nasional: 'acaab-badge-nasional',
+ Regional: 'acaab-badge-regional',
+};
+
+function initAchievements() {
+ buildAchFilterOptions();
+ renderAchievements();
+ bindAchEvents();
+}
+
+function buildAchFilterOptions() {
+ const levelSel = document.getElementById('acaab-ach-level');
+ const catSel = document.getElementById('acaab-ach-cat');
+ const yearSel = document.getElementById('acaab-ach-year');
+ if (!levelSel || !catSel || !yearSel) return;
+
+ const levels = ['Internasional', 'Nasional', 'Regional'];
+ const cats = [...new Set(achievements.map(a => a.category))].sort();
+ const years = [...new Set(achievements.map(a => String(a.year)))].filter(Boolean).sort((a, b) => b - a);
+
+ levelSel.innerHTML = `` +
+ levels.map(l => ``).join('');
+ catSel.innerHTML = `` +
+ cats.map(c => ``).join('');
+ yearSel.innerHTML = `` +
+ years.map(y => ``).join('');
+}
+
+function filterAchievements() {
+ const q = achFilter.search.toLowerCase();
+ return achievements.filter(a => {
+ const matchSearch = !q || [a.title, a.team, a.category, a.competition]
+ .some(s => (s || '').toLowerCase().includes(q));
+ const matchLevel = !achFilter.level || a.level === achFilter.level;
+ const matchCat = !achFilter.cat || a.category === achFilter.cat;
+ const matchYear = !achFilter.year || String(a.year) === achFilter.year;
+ return matchSearch && matchLevel && matchCat && matchYear;
+ });
+}
+
+function renderAchievements() {
+ const grid = document.getElementById('acaab-ach-grid');
+ const countEl = document.getElementById('acaab-ach-count');
+ const moreBtn = document.getElementById('acaab-ach-more');
+ const lessBtn = document.getElementById('acaab-ach-less');
+ const resetBtn= document.getElementById('acaab-ach-reset');
+ if (!grid) return;
+
+ const filtered = filterAchievements();
+ const visible = filtered.slice(0, achVisible);
+ const hasFilter = achFilter.search || achFilter.level || achFilter.cat || achFilter.year;
+
+ if (countEl) countEl.textContent =
+ `${t('grp_acaab_ach_showing')} ${filtered.length} ${t('grp_acaab_ach_results')}`;
+ if (resetBtn) resetBtn.style.display = hasFilter ? 'flex' : 'none';
+
+ if (filtered.length === 0) {
+ grid.innerHTML = `${escHtml(t('grp_acaab_ach_empty'))}
`;
+ if (moreBtn) moreBtn.style.display = 'none';
+ if (lessBtn) lessBtn.style.display = 'none';
+ return;
+ }
+
+ grid.innerHTML = visible.map((a, i) => achCardHtml(a, i)).join('');
+
+ if (moreBtn) moreBtn.style.display = achVisible < filtered.length ? 'inline-block' : 'none';
+ if (lessBtn) lessBtn.style.display = achVisible > ACH_INIT ? 'inline-block' : 'none';
+
+ grid.querySelectorAll('.acaab-ach-readmore').forEach(btn => {
+ btn.addEventListener('click', () => {
+ const ach = achievements.find(a => a.id === btn.dataset.id);
+ if (ach) openAchModal(ach);
+ });
+ });
+}
+
+function achCardHtml(a, idx) {
+ return `
+
+
+
+
+
})
+
+
+
+ ${escHtml(a.rank)}
+
+
+
+
+
+
+ ${escHtml(a.category)}
+
+ ${escHtml(String(a.year))}
+
+
+
+
${escHtml(a.title)}
+
+
${escHtml(a.description)}
+
+
+
+
+
+
${escHtml(a.avatar)}
+
+ ${escHtml(a.team)}
+ ${escHtml(a.generation)}
+
+
+
+
${escHtml(a.level)}
+
+
+
+
+
+ `;
+}
+
+function bindAchEvents() {
+ const search = document.getElementById('acaab-ach-search');
+ const level = document.getElementById('acaab-ach-level');
+ const cat = document.getElementById('acaab-ach-cat');
+ const year = document.getElementById('acaab-ach-year');
+ const reset = document.getElementById('acaab-ach-reset');
+ const moreBtn = document.getElementById('acaab-ach-more');
+ const lessBtn = document.getElementById('acaab-ach-less');
+
+ const onChange = () => {
+ achFilter.search = search ? search.value.trim() : '';
+ achFilter.level = level ? level.value : '';
+ achFilter.cat = cat ? cat.value : '';
+ achFilter.year = year ? year.value : '';
+ achVisible = ACH_INIT;
+ renderAchievements();
+ };
+
+ if (search) search.addEventListener('input', onChange);
+ if (level) level.addEventListener('change', onChange);
+ if (cat) cat.addEventListener('change', onChange);
+ if (year) year.addEventListener('change', onChange);
+
+ if (reset) reset.addEventListener('click', () => {
+ achFilter = { search: '', level: '', cat: '', year: '' };
+ if (search) search.value = '';
+ if (level) level.value = '';
+ if (cat) cat.value = '';
+ if (year) year.value = '';
+ achVisible = ACH_INIT;
+ renderAchievements();
+ });
+
+ if (moreBtn) moreBtn.addEventListener('click', () => { achVisible += ACH_MORE; renderAchievements(); });
+ if (lessBtn) lessBtn.addEventListener('click', () => {
+ achVisible = ACH_INIT;
+ document.getElementById('acaab-ach-section')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
+ renderAchievements();
+ });
+}
+
+// ─── Achievement Modal ────────────────────────────────────────────────────────
+
+function infoRow(icon, label, value) {
+ if (!value) return '';
+ return `
+
+
+
${escHtml(label)}
+
${escHtml(value)}
+
+
`;
+}
+
+function memberItem(nama, sub) {
+ const initial = (nama || '?').charAt(0).toUpperCase();
+ return `
+ ${initial}
+
+ ${escHtml(nama)}
+ ${sub ? `${escHtml(sub)}` : ''}
+
+ `;
+}
+
+function openAchModal(a) {
+ const membersHtml = a.members && a.members.length > 0 ? `
+
+
Anggota Tim
+
+ ${a.members.map(m => memberItem(m.nama, m.angkatan ? `Angkatan ${m.angkatan}` : '')).join('')}
+
+
` : '';
+
+ renderModal(`
+
+
+
})
+
+
+
+
+
+
+ ${escHtml(a.rank)}
+
+
+ ${escHtml(a.level)}
+
+
+
+
+
+
${escHtml(a.category)}
+
${escHtml(a.title)}
+
+
+ ${infoRow('users', t('grp_acaab_modal_team'), a.team)}
+ ${a.nim ? infoRow('id-card', 'NIM', a.nim) : ''}
+ ${infoRow('user-graduate', t('grp_acaab_modal_generation'), a.generation)}
+ ${infoRow('calendar-alt', 'Tahun', String(a.year))}
+ ${infoRow('award', 'Kompetisi', a.competition)}
+ ${infoRow('tag', 'Bidang', a.category)}
+
+ ${membersHtml}
+
+
+
Deskripsi
+
${escHtml(a.description)}
+
+
+
+
+ `, true);
+}
+
+// ─── Projects ─────────────────────────────────────────────────────────────────
+
+const PROJ_INIT = 6;
+const PROJ_MORE = 3;
+
+function initProjects() {
+ buildProjFilterOptions();
+ renderProjects();
+ bindProjEvents();
+}
+
+function buildProjFilterOptions() {
+ const catSel = document.getElementById('acaab-proj-cat');
+ const yearSel = document.getElementById('acaab-proj-year');
+ if (!catSel || !yearSel) return;
+
+ const cats = [...new Set(projects.flatMap(p => p.categories))].sort();
+ const years = [...new Set(projects.map(p => String(p.year)))].filter(Boolean).sort((a, b) => b - a);
+
+ catSel.innerHTML = `` +
+ cats.map(c => ``).join('');
+ yearSel.innerHTML = `` +
+ years.map(y => ``).join('');
+}
+
+function filterProjects() {
+ const q = projFilter.search.toLowerCase();
+ return projects.filter(p => {
+ const matchSearch = !q || [p.title, p.studentName, p.description, ...p.categories]
+ .some(s => (s || '').toLowerCase().includes(q));
+ const matchCat = !projFilter.cat || p.categories.includes(projFilter.cat);
+ const matchYear = !projFilter.year || String(p.year) === projFilter.year;
+ return matchSearch && matchCat && matchYear;
+ });
+}
+
+function renderProjects() {
+ const track = document.getElementById('acaab-proj-track');
+ const moreBtn = document.getElementById('acaab-proj-more');
+ const resetBtn = document.getElementById('acaab-proj-reset');
+ if (!track) return;
+
+ const filtered = filterProjects();
+ const visible = filtered.slice(0, projVisible);
+ const hasFilter = projFilter.search || projFilter.cat || projFilter.year;
+
+ if (resetBtn) resetBtn.style.display = hasFilter ? 'flex' : 'none';
+
+ if (filtered.length === 0) {
+ track.innerHTML = `${escHtml(t('grp_acaab_proj_empty'))}
`;
+ if (moreBtn) moreBtn.style.display = 'none';
+ return;
+ }
+
+ track.innerHTML = visible.map((p, i) => projCardHtml(p, i)).join('');
+ if (moreBtn) moreBtn.style.display = projVisible < filtered.length ? 'inline-block' : 'none';
+
+ track.querySelectorAll('.acaab-proj-readmore').forEach(btn => {
+ btn.addEventListener('click', () => {
+ const proj = projects.find(p => p.id === btn.dataset.id);
+ if (proj) openProjModal(proj);
+ });
+ });
+}
+
+function projCardHtml(p, idx) {
+ const catBadges = p.categories.map(c => `
+
+ ${escHtml(c)}
+ `).join('');
+
+ return `
+
+
+
+
+
})
+
+
+
${escHtml(p.title)}
+
${catBadges}
+
+
+
+
+
+
+ ${p.studentName ? `${escHtml(p.studentName)}` : ''}
+ ${p.year ? `${escHtml(String(p.year))}` : ''}
+
+
+
${escHtml(p.description)}
+
+
+
+
`;
+}
+
+function bindProjEvents() {
+ const search = document.getElementById('acaab-proj-search');
+ const cat = document.getElementById('acaab-proj-cat');
+ const year = document.getElementById('acaab-proj-year');
+ const reset = document.getElementById('acaab-proj-reset');
+ const moreBtn = document.getElementById('acaab-proj-more');
+ const prevBtn = document.getElementById('acaab-proj-prev');
+ const nextBtn = document.getElementById('acaab-proj-next');
+
+ const onChange = () => {
+ projFilter.search = search ? search.value.trim() : '';
+ projFilter.cat = cat ? cat.value : '';
+ projFilter.year = year ? year.value : '';
+ projVisible = PROJ_INIT;
+ renderProjects();
+ };
+
+ if (search) search.addEventListener('input', onChange);
+ if (cat) cat.addEventListener('change', onChange);
+ if (year) year.addEventListener('change', onChange);
+
+ if (reset) reset.addEventListener('click', () => {
+ projFilter = { search: '', cat: '', year: '' };
+ if (search) search.value = '';
+ if (cat) cat.value = '';
+ if (year) year.value = '';
+ projVisible = PROJ_INIT;
+ renderProjects();
+ });
+
+ if (moreBtn) moreBtn.addEventListener('click', () => { projVisible += PROJ_MORE; renderProjects(); });
+
+ const track = document.getElementById('acaab-proj-track');
+ if (prevBtn && track) prevBtn.addEventListener('click', () => {
+ const cardW = (track.firstElementChild ? track.firstElementChild.offsetWidth : 300) + 20;
+ track.scrollBy({ left: -cardW, behavior: 'smooth' });
+ });
+ if (nextBtn && track) nextBtn.addEventListener('click', () => {
+ const cardW = (track.firstElementChild ? track.firstElementChild.offsetWidth : 300) + 20;
+ track.scrollBy({ left: cardW, behavior: 'smooth' });
+ });
+}
+
+// ─── Project Modal ────────────────────────────────────────────────────────────
+
+function openProjModal(p) {
+ const membersHtml = p.members && p.members.length > 0 ? `
+
+
Anggota Tim
+
+ ${p.members.map(m => memberItem(m.nama, m.nim || '')).join('')}
+
+
` : '';
+
+ const catBadgesImg = p.categories.map(c => `
+
+ ${escHtml(c)}
+ `).join('');
+
+ const linkSection = p.link
+ ? `
+ ${escHtml(t('grp_acaab_modal_visit'))}
+ `
+ : `
+ ${escHtml(t('grp_acaab_modal_no_link'))}
+
`;
+
+ renderModal(`
+
+
+
})
+
+
+
+
+
${catBadgesImg}
+
+
+
+
${escHtml(p.title)}
+
+
+ ${p.studentName ? infoRow('users', t('grp_acaab_modal_team'), p.studentName) : ''}
+ ${p.nim ? infoRow('id-card', 'NIM', p.nim) : ''}
+ ${infoRow('calendar-alt', 'Tahun', String(p.year))}
+ ${infoRow('tag', 'Bidang', p.categories.join(', '))}
+
+ ${membersHtml}
+
+
+
Deskripsi
+
${escHtml(p.description)}
+ ${p.catatan ? `
+
+
Catatan Tambahan
+
${escHtml(p.catatan)}
+
` : ''}
+
+
+ ${linkSection}
+
+
+ `, true);
+}
+
+// ─── Modal shared ─────────────────────────────────────────────────────────────
+
+function renderModal(innerHtml, hasCloseBtn = false) {
+ const container = document.getElementById('acaab-modal');
+ if (!container) return;
+
+ container.innerHTML = `
+ `;
+
+ document.body.classList.add('acaab-modal-open');
+
+ document.getElementById('acaab-modal-close')?.addEventListener('click', closeModal);
+ if (hasCloseBtn) {
+ document.getElementById('acaab-modal-close-btn')?.addEventListener('click', closeModal);
+ }
+ document.getElementById('acaab-modal-bd').addEventListener('click', e => {
+ if (e.target === e.currentTarget) closeModal();
+ });
+ document.addEventListener('keydown', onEsc);
+}
+
+function closeModal() {
+ const c = document.getElementById('acaab-modal');
+ if (c) c.innerHTML = '';
+ document.body.classList.remove('acaab-modal-open');
+ document.removeEventListener('keydown', onEsc);
+}
+
+function onEsc(e) { if (e.key === 'Escape') closeModal(); }
+
+// ─── CTA / WhatsApp ───────────────────────────────────────────────────────────
+
+const WA_NUMBER = '6285177655161';
+
+function initWaButton() { updateWaButton(); }
+
+function updateWaButton() {
+ const btn = document.getElementById('acaab-cta-wa');
+ if (!btn) return;
+ const msg = getLang() === 'en'
+ ? 'Hello! I would like to know more about Informatika UNTAN student achievements and projects.'
+ : 'Halo! Saya ingin mengetahui lebih lanjut tentang prestasi dan karya mahasiswa Informatika UNTAN.';
+ btn.href = `https://wa.me/${WA_NUMBER}?text=${encodeURIComponent(msg)}`;
+}
+
+// ─── Placeholder sync ─────────────────────────────────────────────────────────
+
+function updateSearchPlaceholders() {
+ const s1 = document.getElementById('acaab-ach-search');
+ const s2 = document.getElementById('acaab-proj-search');
+ if (s1) s1.placeholder = t('grp_acaab_ach_search');
+ if (s2) s2.placeholder = t('grp_acaab_proj_search');
+}
diff --git a/groups/ACAAB/style.css b/groups/ACAAB/style.css
new file mode 100644
index 0000000..4aef586
--- /dev/null
+++ b/groups/ACAAB/style.css
@@ -0,0 +1,206 @@
+/* =====================================================
+ ACAAB – Prestasi & Karya Mahasiswa
+ Custom styles yang tidak dicakup output.css
+ ===================================================== */
+
+/* Cegah horizontal scroll tanpa clip full-bleed */
+body { overflow-x: hidden; }
+
+/* --- Full-bleed: section keluar dari container prose --- */
+.acaab-full-bleed {
+ width: 100vw;
+ margin-left: calc(50% - 50vw);
+ margin-right: calc(50% - 50vw);
+}
+
+/* --- Sembunyikan scrollbar carousel tapi tetap scrollable --- */
+.acaab-hide-scrollbar {
+ -ms-overflow-style: none;
+ scrollbar-width: none;
+}
+.acaab-hide-scrollbar::-webkit-scrollbar {
+ display: none;
+}
+
+/* --- Card ukuran responsif di carousel --- */
+.acaab-proj-card {
+ width: 80vw;
+ flex-shrink: 0;
+ scroll-snap-align: start;
+}
+@media (min-width: 768px) { .acaab-proj-card { width: 44vw; } }
+@media (min-width: 1024px) { .acaab-proj-card { width: 30vw; } }
+
+/* --- Snap scroll carousel --- */
+.acaab-carousel-track {
+ scroll-snap-type: x mandatory;
+ -webkit-overflow-scrolling: touch;
+}
+
+/* --- Animasi fade-in untuk cards --- */
+@keyframes acaab-fade-in {
+ from { opacity: 0; transform: translateY(14px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+.acaab-fade-in {
+ animation: acaab-fade-in 0.35s ease both;
+}
+
+/* --- Badge level warna --- */
+.acaab-badge-intl { background-color: #ca8a04; color: #fff; }
+.acaab-badge-nasional { background-color: #1d4ed8; color: #fff; }
+.acaab-badge-regional { background-color: #64748b; color: #fff; }
+
+/* --- Skeleton loading --- */
+.acaab-skeleton {
+ background: linear-gradient(90deg, #e2e8f0 25%, #f1f5f9 50%, #e2e8f0 75%);
+ background-size: 200% 100%;
+ animation: acaab-skeleton-pulse 1.5s infinite;
+ border-radius: 0.5rem;
+}
+@keyframes acaab-skeleton-pulse {
+ 0% { background-position: 200% 0; }
+ 100% { background-position: -200% 0; }
+}
+
+/* --- Modal backdrop --- */
+.acaab-modal-backdrop {
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.65);
+ backdrop-filter: blur(4px);
+ -webkit-backdrop-filter: blur(4px);
+ z-index: 200;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 1rem;
+}
+
+/* --- Scroll lock saat modal terbuka --- */
+body.acaab-modal-open {
+ overflow: hidden;
+}
+
+/* --- Stats grid: divider vertikal di desktop --- */
+@media (min-width: 768px) {
+ .acaab-stats-grid {
+ grid-template-columns: 1fr 1px 1fr 1px 1fr;
+ }
+}
+
+/* --- Line clamp (browser lama) --- */
+.acaab-line-clamp-2 {
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+}
+.acaab-line-clamp-3 {
+ display: -webkit-box;
+ -webkit-line-clamp: 3;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+}
+
+/* --- Achievement card --- */
+.acaab-ach-card {
+ position: relative;
+ background: white;
+ border-radius: 0.5rem;
+ overflow: hidden;
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1), 0 1px 2px rgba(0,0,0,0.06);
+ border: 1px solid #e2e8f0;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
+}
+.acaab-ach-card:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 10px 25px rgba(0,0,0,0.12), 0 4px 10px rgba(0,0,0,0.08);
+}
+.acaab-ach-top-bar {
+ position: absolute;
+ top: 0; left: 0; right: 0;
+ height: 3px;
+ background: #003150;
+ z-index: 10;
+ transition: background 0.25s ease;
+}
+.acaab-ach-card:hover .acaab-ach-top-bar { background: #feb401; }
+.acaab-ach-card:hover .acaab-card-title { color: #1e40af; }
+.acaab-card-title { transition: color 0.2s ease; }
+.acaab-ach-read-btn:hover { background: #003150 !important; color: white !important; }
+
+/* --- Project card wrapper --- */
+.acaab-proj-card-wrap {
+ position: relative;
+ background: white;
+ border-radius: 0.5rem;
+ overflow: hidden;
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1), 0 1px 2px rgba(0,0,0,0.06);
+ border: 1px solid #e2e8f0;
+ display: flex;
+ flex-direction: column;
+ flex-shrink: 0;
+ transition: box-shadow 0.3s ease;
+}
+.acaab-proj-card-wrap:hover { box-shadow: 0 10px 25px rgba(0,0,0,0.12); }
+.acaab-proj-left-bar {
+ position: absolute;
+ top: 0; left: 0;
+ width: 3px; height: 100%;
+ background: #feb401;
+ z-index: 10;
+ transform-origin: bottom;
+ transition: transform 0.3s ease;
+}
+.acaab-proj-card-wrap:hover .acaab-proj-left-bar { transform: scaleY(1.08); }
+.acaab-proj-card-wrap:hover .acaab-card-img { transform: scale(1.05); }
+
+/* --- Card image zoom --- */
+.acaab-card-img {
+ transition: transform 0.5s ease;
+}
+
+/* --- Stats grid dividers responsive --- */
+.acaab-stat-vdivider { display: none; }
+@media (min-width: 768px) {
+ .acaab-stat-vdivider { display: flex; }
+ .acaab-stat-hdivider { display: none; }
+}
+
+/* --- Filter focus --- */
+#acaab-ach-filters input:focus,
+#acaab-ach-filters select:focus,
+#acaab-proj-filters input:focus,
+#acaab-proj-filters select:focus {
+ outline: 2px solid #003150;
+ outline-offset: 1px;
+ border-color: #003150;
+}
+
+/* --- CTA inner layout: stacked mobile → row desktop --- */
+.acaab-cta-inner {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 2rem;
+ text-align: center;
+}
+@media (min-width: 768px) {
+ .acaab-cta-inner {
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ text-align: left;
+ gap: 3rem;
+ }
+}
+
+/* --- Reduced motion --- */
+@media (prefers-reduced-motion: reduce) {
+ .acaab-fade-in, .acaab-skeleton, .acaab-card-img,
+ .acaab-ach-card, .acaab-proj-card-wrap { animation: none !important; transition: none !important; }
+}