18 Commits

Author SHA1 Message Date
tistopandita ad70dbd2d5 feat: add lazyloading 2026-06-05 12:24:44 +07:00
tistopandita 11425353c1 perbaiki index.html 2026-06-05 12:12:30 +07:00
tistopandita 8ba180e34b Merge remote-tracking branch 'origin/main' into feature/multilanguage 2026-06-05 12:10:23 +07:00
tistopandita dd18ed24a2 feat: add multilingual support (id/en) 2026-06-05 11:57:08 +07:00
tisto e5b9bfcf4b merge upstream 2026-06-05 03:09:15 +00:00
tistopandita 51f4c69ebb Merge branch 'main' of https://git.ifuntanhub.dev/tisto/student-web-if-development-kit 2026-06-05 10:06:19 +07:00
tistopandita f0d3070a5e fitur bahasa billingual 2026-06-05 10:01:41 +07:00
pancaa e5bd8d0653 Merge pull request 'Optimize images and convert PNG to WebP' (#4) from feature/optimize-images into main
Reviewed-on: #4
Reviewed-by: requestha <d1041231091@student.untan.ac.id>
Reviewed-by: pancaa <d1041231077@student.untan.ac.id>
2026-06-05 02:36:39 +00:00
tistopandita 693e41af0a Optimize images and convert PNG to WebP 2026-06-05 08:34:24 +07:00
pancaa 00cc837fa1 Merge pull request 'chore: replace remaining low-res images with upscaled versions' (#3) from chore/upscale-images into main
Reviewed-on: #3
Reviewed-by: pancaa <d1041231077@student.untan.ac.id>
2026-06-04 17:31:04 +00:00
Syariffullah fc338e8473 Merge branch 'main' of https://git.ifuntanhub.dev/tisto/student-web-if-development-kit into chore/upscale-images 2026-06-05 00:16:46 +07:00
Syariffullah 125828a566 chore: replace remaining low-res images with upscaled versions 2026-06-05 00:11:46 +07:00
pancaa 5186195930 Merge pull request 'chore: replace low-res images with upscaled versions' (#2) from chore/upscale-images into main
Reviewed-on: #2
Reviewed-by: requestha <d1041231091@student.untan.ac.id>
Reviewed-by: pancaa <d1041231077@student.untan.ac.id>
2026-06-04 17:05:53 +00:00
Syariffullah 783ac833f2 chore: replace low-res images with upscaled versions 2026-06-04 23:40:41 +07:00
requestha 82a386e3bb Merge pull request 'fix(responsive): fixing responsiveness on mobile devices' (#1) from fix/mobile-responsive into main
Reviewed-on: #1
Reviewed-by: requestha <d1041231091@student.untan.ac.id>
Reviewed-by: pancaa <d1041231077@student.untan.ac.id>
2026-06-04 16:25:19 +00:00
Syariffullah b6c9bbbefe fix(responsive): fixing responsiveness on mobile devices 2026-06-04 22:43:02 +07:00
tistopandita 066f47dd48 mengubah gambar museum,qubu-resort,hero,taman-digulis,mujahidin menjadi format webp 2026-06-04 20:13:56 +07:00
Syariffullah 4853d1f7dd Add localization support for HarusSelesaiKP with Indonesian and English translations 2026-06-04 18:12:03 +07:00
161 changed files with 5180 additions and 15733 deletions
File diff suppressed because it is too large Load Diff
-1
View File
@@ -1 +0,0 @@
node_modules/
Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 KiB

+33 -364
View File
@@ -1,6 +1,5 @@
<!DOCTYPE html>
<!DOCTYPE html>
<html lang="id" class="scroll-smooth">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -8,12 +7,9 @@
<link rel="stylesheet" href="../../assets/global/output.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Roboto+Condensed:ital,wght@0,100..900;1,100..900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"
rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Roboto+Condensed:ital,wght@0,100..900;1,100..900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="stylesheet" href="./style.css">
<style>
<style>
/* ===== MOBILE NAV STABILITY FIX =====
Prevents logo bar from collapsing/shifting on Android when
address bar hides/shows and triggers viewport reflow.
@@ -297,7 +293,6 @@
}
</style>
</head>
<body class="text-slate-800 antialiased bg-white selection:bg-untan-yellow selection:text-black">
<a href="#main-content" class="skip-link" data-i18n="skip_to_content">Langsung ke Konten Utama</a>
@@ -1144,341 +1139,18 @@
</div>
</div>
<main id="main-content" class="pt-[88px] lg:pt-[160px] min-h-screen bg-white">
<main id="main-content" class="pt-[88px] lg:pt-[160px] min-h-screen bg-white">
<div class="container mx-auto px-4 lg:px-8 py-12">
<div class="mb-8 pb-8 border-b border-slate-200">
<span
class="bg-untan-yellow text-slate-900 text-[10px] font-bold uppercase tracking-widest px-2 py-0.5 inline-block mb-3">Tugas
Halaman Statis</span>
<span class="bg-untan-yellow text-slate-900 text-[10px] font-bold uppercase tracking-widest px-2 py-0.5 inline-block mb-3">Tugas Halaman Statis</span>
<h1 class="text-3xl lg:text-4xl font-bold text-slate-900 mb-2">Sorotan dan Prestasi Karya Mahasiswa</h1>
<p class="text-slate-500 text-sm">Kelompok 06 &mdash; ACAAB &nbsp;&middot;&nbsp; Informatika UNTAN 2026
</p>
<p class="text-slate-500 text-sm">Kelompok 06 &mdash; ACAAB &nbsp;&middot;&nbsp; Informatika UNTAN 2026</p>
</div>
<div id="assignment-body" class="prose prose-slate max-w-none">
<!-- ================= START: BAGIAN YANG DIEDIT KELOMPOK ================= -->
<!-- ── BREADCRUMB ─────────────────────────────────────────────────────── -->
<nav class="not-prose py-3" aria-label="Breadcrumb">
<ol class="flex items-center gap-1 text-[10px] text-slate-500 font-medium tracking-wide uppercase">
<li><a href="https://informatika.untan.ac.id" target="_blank" rel="noopener noreferrer"
class="hover:text-blue-600 transition-colors">Beranda</a></li>
<li><span class="mx-1 text-slate-300" aria-hidden="true">/</span></li>
<li><span class="text-slate-800 font-bold" aria-current="page"
data-i18n="grp_acaab_hero_badge">Mahasiswa &amp; Prestasi</span></li>
</ol>
</nav>
<!-- ── HERO ───────────────────────────────────────────────────────────── -->
<section id="acaab-hero"
class="not-prose acaab-full-bleed bg-untan-navy text-white relative overflow-hidden"
style="padding-top:3rem;padding-bottom:0;margin-top:0.5rem;">
<!-- Decorative glow -->
<div class="absolute top-0 right-0 w-80 h-80 rounded-full pointer-events-none"
style="background:rgba(254,180,1,0.08);filter:blur(80px);margin-right:-5rem;margin-top:-5rem;">
</div>
<div class="container mx-auto px-4 lg:px-8">
<div style="display:grid;grid-template-columns:1fr;gap:1.5rem;align-items:center;"
class="lg-hero-grid">
<!-- Left: text -->
<div style="padding:1.5rem 0 2rem;max-width:640px;">
<span class="acaab-fade-in"
style="display:inline-block;background:#feb401;color:#0f172a;font-size:0.65rem;font-weight:700;letter-spacing:0.15em;text-transform:uppercase;padding:0.2rem 0.625rem;margin-bottom:0.75rem;"
data-i18n="grp_acaab_hero_badge">MAHASISWA &amp; PRESTASI</span>
<h2 class="acaab-fade-in"
style="font-size:clamp(1.875rem,3.5vw,2.5rem);font-weight:700;line-height:1.2;letter-spacing:-0.01em;margin:0 0 0.75rem;font-family:'Roboto Condensed',sans-serif;"
data-i18n="grp_acaab_hero_title">Sorotan dan Prestasi Karya Mahasiswa</h2>
<p class="acaab-fade-in"
style="color:rgba(255,255,255,0.7);font-size:0.9375rem;line-height:1.7;margin:0;"
data-i18n="grp_acaab_hero_desc">Temukan pencapaian luar biasa mahasiswa Informatika
UNTAN.</p>
</div>
<!-- Right: image -->
<div id="acaab-hero-img-wrap"
style="display:flex;justify-content:flex-end;align-items:flex-end;position:relative;">
<div
style="position:absolute;bottom:0;width:280px;height:280px;background:rgba(103,232,249,0.06);border-radius:50%;filter:blur(60px);">
</div>
<div style="position:relative;">
<img src="./hero.png" alt="Mahasiswa Informatika UNTAN"
width="612" height="407"
fetchpriority="high"
style="position:relative;z-index:10;width:100%;max-width:320px;height:auto;object-fit:contain;opacity:0.95;filter:drop-shadow(0 15px 35px rgba(0,0,0,0.25));transition:transform 0.5s;"
onerror="this.style.display='none'">
<!-- Gradient fades over image -->
<div
style="position:absolute;bottom:0;left:0;width:100%;height:8rem;background:linear-gradient(to top,#003150,transparent);z-index:20;pointer-events:none;">
</div>
<div
style="position:absolute;top:0;left:0;height:100%;width:4rem;background:linear-gradient(to right,#003150,transparent);z-index:20;pointer-events:none;">
</div>
</div>
</div>
</div>
</div>
<!-- Responsive: 2-column on desktop -->
<style>
@media (min-width:1024px) {
.lg-hero-grid {
grid-template-columns: 1fr 1fr !important;
}
#acaab-hero-img-wrap img {
max-width: 420px !important;
}
}
</style>
</section>
<!-- ── STATS (yellow card) ────────────────────────────────────────────── -->
<div id="acaab-stats" class="not-prose" style="padding:1.5rem 0 0;">
<div
style="background:#feb401;border-radius:0.75rem;box-shadow:0 10px 30px -10px rgba(253,184,19,0.4);padding:1.25rem 1.5rem 1.5rem;">
<div style="display:flex;flex-wrap:wrap;gap:1.5rem;">
<!-- Prestasi Block -->
<div style="flex:4;min-width:240px;">
<div style="background:#003150;border-radius:0.5rem;padding:0.5rem 0.75rem;text-align:center;margin-bottom:1.25rem;">
<span style="color:white;font-size:0.875rem;font-weight:800;letter-spacing:0.12em;text-transform:uppercase;"
data-i18n="grp_acaab_stats_total_ach">Total Prestasi</span>
</div>
<div style="display:flex;gap:0.25rem;">
<!-- Prestasi Mahasiswa (total) -->
<div style="flex:1;text-align:center;padding:0 0.15rem;">
<div id="stat-total-ach"
style="font-size:clamp(2rem,4.5vw,4rem);font-weight:900;color:#003150;line-height:1;font-family:'Roboto Condensed',sans-serif;">
0</div>
<div style="font-size:clamp(0.5rem,1.2vw,0.85rem);font-weight:800;text-transform:uppercase;letter-spacing:0.08em;color:#003150;margin-top:0.5rem;line-height:1.3;word-break:break-word;"
data-i18n="grp_acaab_stats_label_ach">Prestasi<br>Mahasiswa</div>
</div>
<!-- Internasional -->
<div style="flex:1;text-align:center;padding:0 0.15rem;">
<div id="stat-intl"
style="font-size:clamp(2rem,4.5vw,4rem);font-weight:900;color:#003150;line-height:1;font-family:'Roboto Condensed',sans-serif;">
0</div>
<div style="font-size:clamp(0.5rem,1.2vw,0.85rem);font-weight:800;text-transform:uppercase;letter-spacing:0.08em;color:#003150;margin-top:0.5rem;word-break:break-word;"
data-i18n="grp_acaab_stats_intl">Internasional</div>
</div>
<!-- Nasional -->
<div style="flex:1;text-align:center;padding:0 0.15rem;">
<div id="stat-national"
style="font-size:clamp(2rem,4.5vw,4rem);font-weight:900;color:#003150;line-height:1;font-family:'Roboto Condensed',sans-serif;">
0</div>
<div style="font-size:clamp(0.5rem,1.2vw,0.85rem);font-weight:800;text-transform:uppercase;letter-spacing:0.08em;color:#003150;margin-top:0.5rem;word-break:break-word;"
data-i18n="grp_acaab_stats_national">Nasional</div>
</div>
<!-- Regional -->
<div style="flex:1;text-align:center;padding:0 0.15rem;">
<div id="stat-regional"
style="font-size:clamp(2rem,4.5vw,4rem);font-weight:900;color:#003150;line-height:1;font-family:'Roboto Condensed',sans-serif;">
0</div>
<div style="font-size:clamp(0.5rem,1.2vw,0.85rem);font-weight:800;text-transform:uppercase;letter-spacing:0.08em;color:#003150;margin-top:0.5rem;word-break:break-word;"
data-i18n="grp_acaab_stats_regional">Regional</div>
</div>
</div>
</div>
<!-- Proyek Block -->
<div style="flex:1;min-width:120px;">
<div style="background:#003150;border-radius:0.5rem;padding:0.5rem 0.75rem;text-align:center;margin-bottom:1.25rem;">
<span style="color:white;font-size:0.875rem;font-weight:800;letter-spacing:0.12em;text-transform:uppercase;"
data-i18n="grp_acaab_stats_total_proj">Total Portofolio</span>
</div>
<div style="display:flex;justify-content:center;">
<!-- Proyek Mahasiswa (total) -->
<div style="text-align:center;padding:0 0.15rem;">
<div id="stat-total-proj"
style="font-size:clamp(2rem,4.5vw,4rem);font-weight:900;color:#003150;line-height:1;font-family:'Roboto Condensed',sans-serif;">
0</div>
<div style="font-size:clamp(0.5rem,1.2vw,0.85rem);font-weight:800;text-transform:uppercase;letter-spacing:0.08em;color:#003150;margin-top:0.5rem;line-height:1.3;word-break:break-word;"
data-i18n="grp_acaab_stats_label_proj">Proyek<br>Mahasiswa</div>
</div>
</div>
</div>
</div>
</div>
<!-- Data note -->
<p id="acaab-data-note"
style="font-size:0.6rem;font-weight:600;color:#94a3b8;letter-spacing:0.05em;text-align:right;padding-right:0.25rem;margin:0.5rem 0 0;">
</p>
</div>
<!-- ── ACHIEVEMENT GRID ────────────────────────────────────────────────── -->
<section id="acaab-ach-section" class="not-prose" style="padding:2.5rem 0 3rem;">
<div style="margin-bottom:2rem;">
<h2 style="font-size:clamp(1.5rem,3vw,2.25rem);font-weight:900;color:#003150;margin:0 0 0.5rem;font-family:'Roboto Condensed',sans-serif;"
data-i18n="grp_acaab_ach_title">Prestasi Mahasiswa</h2>
<p style="color:#64748b;font-size:1rem;max-width:42rem;margin:0;line-height:1.6;"
data-i18n="grp_acaab_ach_desc">Daftar pencapaian dan penghargaan yang diraih oleh mahasiswa Informatika UNTAN di berbagai kompetisi.</p>
</div>
<!-- Filter panel — gold top border matching original -->
<div id="acaab-ach-filters"
style="background:rgba(255,255,255,0.8);backdrop-filter:blur(12px);padding:1rem;border-radius:0.5rem;box-shadow:0 1px 3px rgba(0,0,0,0.08);border-top:4px solid #feb401;margin-bottom:2rem;">
<!-- Row 1: Label + Reset -->
<div
style="display:flex;align-items:center;justify-content:space-between;margin-bottom:0.75rem;">
<div
style="display:flex;align-items:center;gap:0.5rem;font-size:0.875rem;font-weight:700;color:#003150;font-family:'Roboto Condensed',sans-serif;">
<i class="fas fa-filter" style="font-size:0.8rem;"></i>
<span>Filter Prestasi</span>
</div>
<button id="acaab-ach-reset"
style="display:none;align-items:center;gap:0.25rem;font-size:0.75rem;font-weight:700;color:#64748b;background:none;border:none;cursor:pointer;"
data-i18n="btn_reset_filter">
<i class="fas fa-times" style="font-size:0.7rem;"></i> Reset Filter
</button>
</div>
<!-- Row 2: Search -->
<div style="position:relative;margin-bottom:0.75rem;">
<i class="fas fa-search"
style="position:absolute;left:0.75rem;top:50%;transform:translateY(-50%);color:#94a3b8;font-size:0.8rem;pointer-events:none;"></i>
<input id="acaab-ach-search" type="search"
style="width:100%;background:#f8fafc;color:#334155;font-size:0.875rem;border-radius:0.375rem;padding:0.5rem 0.75rem 0.5rem 2.25rem;border:1px solid #e2e8f0;outline:none;box-sizing:border-box;"
placeholder="Cari prestasi..." data-i18n-placeholder="grp_acaab_ach_search">
</div>
<!-- Row 3: Dropdowns -->
<div style="display:flex;flex-wrap:wrap;gap:0.625rem;">
<select id="acaab-ach-level" aria-label="Filter tingkat prestasi"
style="flex:1;min-width:130px;background:#f8fafc;color:#334155;font-size:0.8rem;font-weight:700;border-radius:0.375rem;padding:0.5rem 0.75rem;border:1px solid #e2e8f0;cursor:pointer;"></select>
<select id="acaab-ach-cat" aria-label="Filter kategori prestasi"
style="flex:1;min-width:130px;background:#f8fafc;color:#334155;font-size:0.8rem;font-weight:700;border-radius:0.375rem;padding:0.5rem 0.75rem;border:1px solid #e2e8f0;cursor:pointer;"></select>
<select id="acaab-ach-year" aria-label="Filter tahun prestasi"
style="flex:1;min-width:120px;background:#f8fafc;color:#334155;font-size:0.8rem;font-weight:700;border-radius:0.375rem;padding:0.5rem 0.75rem;border:1px solid #e2e8f0;cursor:pointer;"></select>
</div>
</div>
<!-- Result count -->
<p id="acaab-ach-count" style="font-size:0.8rem;color:#64748b;margin-bottom:1rem;"></p>
<!-- Cards grid -->
<div id="acaab-ach-grid"
style="display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:2rem;justify-content:center;">
</div>
<!-- Pagination buttons -->
<div style="text-align:center;margin-top:2rem;display:flex;gap:0.75rem;justify-content:center;">
<button id="acaab-ach-more"
style="display:none;padding:0.625rem 1.5rem;background:#003150;color:white;border:none;border-radius:0.5rem;font-weight:700;font-size:0.875rem;cursor:pointer;"
data-i18n="grp_acaab_ach_load_more">Tampilkan Lebih Banyak</button>
<button id="acaab-ach-less"
style="display:none;padding:0.625rem 1.5rem;background:white;color:#003150;border:1px solid #003150;border-radius:0.5rem;font-weight:700;font-size:0.875rem;cursor:pointer;"
data-i18n="grp_acaab_ach_show_less">Tampilkan Lebih Sedikit</button>
</div>
</section>
<!-- ── PROJECT CAROUSEL ───────────────────────────────────────────────── -->
<section id="acaab-proj-section" class="not-prose acaab-full-bleed" style="padding:2.5rem 0 3rem;background:#f8fafc;">
<div class="container mx-auto px-4 lg:px-8">
<!-- Section header row: title + scroll buttons (seperti di React source) -->
<div
style="display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:1rem;margin-bottom:1.5rem;">
<div style="flex:1;min-width:250px;">
<h2 style="font-size:clamp(1.5rem,3vw,2.25rem);font-weight:900;color:#003150;margin:0 0 0.5rem;font-family:'Roboto Condensed',sans-serif;"
data-i18n="grp_acaab_proj_title">Top Projects Portofolio</h2>
<p style="color:#64748b;font-size:1rem;max-width:42rem;margin:0;line-height:1.6;"
data-i18n="grp_acaab_proj_desc">Jelajahi inovasi digital dan aplikasi nyata yang dikembangkan oleh mahasiswa Informatika UNTAN.</p>
</div>
</div>
<!-- Filter panel -->
<div id="acaab-proj-filters"
style="background:rgba(255,255,255,0.8);backdrop-filter:blur(12px);padding:1rem;border-radius:0.5rem;box-shadow:0 1px 3px rgba(0,0,0,0.08);border-top:4px solid #feb401;margin-bottom:1.25rem;">
<div
style="display:flex;align-items:center;justify-content:space-between;margin-bottom:0.75rem;">
<div
style="display:flex;align-items:center;gap:0.5rem;font-size:0.875rem;font-weight:700;color:#003150;font-family:'Roboto Condensed',sans-serif;">
<i class="fas fa-filter" style="font-size:0.8rem;"></i>
<span>Filter Portofolio</span>
</div>
<button id="acaab-proj-reset"
style="display:none;align-items:center;gap:0.25rem;font-size:0.75rem;font-weight:700;color:#64748b;background:none;border:none;cursor:pointer;"
data-i18n="btn_reset_filter">
<i class="fas fa-times" style="font-size:0.7rem;"></i> Reset Filter
</button>
</div>
<div style="position:relative;margin-bottom:0.75rem;">
<i class="fas fa-search"
style="position:absolute;left:0.75rem;top:50%;transform:translateY(-50%);color:#94a3b8;font-size:0.8rem;pointer-events:none;"></i>
<input id="acaab-proj-search" type="search"
style="width:100%;background:#f8fafc;color:#334155;font-size:0.875rem;border-radius:0.375rem;padding:0.5rem 0.75rem 0.5rem 2.25rem;border:1px solid #e2e8f0;outline:none;box-sizing:border-box;"
placeholder="Cari karya..." data-i18n-placeholder="grp_acaab_proj_search">
</div>
<div style="display:flex;flex-wrap:wrap;gap:0.625rem;">
<select id="acaab-proj-cat" aria-label="Filter kategori portofolio"
style="flex:1;min-width:130px;background:#f8fafc;color:#334155;font-size:0.8rem;font-weight:700;border-radius:0.375rem;padding:0.5rem 0.75rem;border:1px solid #e2e8f0;cursor:pointer;"></select>
<select id="acaab-proj-year" aria-label="Filter tahun portofolio"
style="flex:1;min-width:120px;background:#f8fafc;color:#334155;font-size:0.8rem;font-weight:700;border-radius:0.375rem;padding:0.5rem 0.75rem;border:1px solid #e2e8f0;cursor:pointer;"></select>
</div>
</div>
<!-- Scroll hint -->
<p style="font-size:0.7rem;color:#94a3b8;text-align:center;margin-bottom:0.75rem;"
data-i18n="grp_acaab_proj_scroll_hint">← Geser untuk melihat lebih banyak →</p>
<!-- Carousel track -->
<div id="acaab-proj-track" class="acaab-carousel-track acaab-hide-scrollbar"
style="display:flex;gap:1.25rem;overflow-x:auto;padding-bottom:1rem;"></div>
<!-- Scroll control buttons (bawah) -->
<div style="display:flex;justify-content:center;align-items:center;gap:1rem;margin-top:1rem;">
<button id="acaab-proj-prev" aria-label="Geser kiri"
style="width:3rem;height:3rem;border-radius:50%;border:2px solid rgba(0,49,80,0.2);display:flex;align-items:center;justify-content:center;color:#003150;background:white;cursor:pointer;transition:all 0.2s;">
<i class="fas fa-chevron-left" style="font-size:1rem;"></i>
</button>
<button id="acaab-proj-next" aria-label="Geser kanan"
style="width:3rem;height:3rem;border-radius:50%;border:2px solid rgba(0,49,80,0.2);display:flex;align-items:center;justify-content:center;color:#003150;background:white;cursor:pointer;transition:all 0.2s;">
<i class="fas fa-chevron-right" style="font-size:1rem;"></i>
</button>
</div>
<!-- Load more -->
<div style="text-align:center;margin-top:1.5rem;">
<button id="acaab-proj-more"
style="display:none;padding:0.625rem 1.5rem;background:#003150;color:white;border:none;border-radius:0.5rem;font-weight:700;font-size:0.875rem;cursor:pointer;"
data-i18n="grp_acaab_proj_load_more">Tampilkan Lebih Banyak</button>
</div>
</div><!-- /inner container -->
</section>
<!-- ── CTA ────────────────────────────────────────────────────────────── -->
<section class="not-prose acaab-full-bleed bg-untan-navy"
style="padding:3.5rem 0; margin-bottom: 3rem;">
<div class="container mx-auto px-4 lg:px-8">
<!-- Inner card: kuning transparan dengan border, rounded-2xl -->
<div
style="background:rgba(254,180,1,0.08);border:1px solid rgba(254,180,1,0.2);border-radius:1rem;padding:2.5rem 2rem;">
<div class="acaab-cta-inner">
<!-- Kiri: teks -->
<div>
<span
style="display:inline-block;background:#feb401;color:#0f172a;font-size:0.625rem;font-weight:700;letter-spacing:0.15em;text-transform:uppercase;padding:0.2rem 0.5rem;border-radius:2px;margin-bottom:1rem;"
data-i18n="grp_acaab_cta_badge">HUBUNGI KAMI</span>
<h2 style="color:white;font-size:clamp(1.5rem,2.5vw,1.875rem);font-weight:700;margin:0 0 0.75rem;font-family:'Roboto Condensed',sans-serif;line-height:1.2;"
data-i18n="grp_acaab_cta_title">Ingin Tahu Lebih Lanjut?</h2>
<p style="color:#cbd5e1;font-size:0.9375rem;line-height:1.65;max-width:480px;margin:0;"
data-i18n="grp_acaab_cta_desc">Tertarik bergabung dengan Program Studi
Informatika UNTAN, mendaftarkan prestasi mahasiswa, atau menjalin kerja sama?
Hubungi kami sekarang.</p>
</div>
<!-- Kanan: tombol WA -->
<div style="flex-shrink:0;">
<a id="acaab-cta-wa" href="#" target="_blank" rel="noopener noreferrer"
style="display:inline-flex;align-items:center;gap:0.625rem;background:#25D366;color:white;padding:0.75rem 1.5rem;border-radius:9999px;font-weight:700;font-size:0.9375rem;text-decoration:none;box-shadow:0 4px 15px rgba(37,211,102,0.25);white-space:nowrap;transition:background 0.2s,transform 0.2s;">
<i class="fab fa-whatsapp" style="font-size:1.125rem;"></i>
<span data-i18n="grp_acaab_cta_btn">Chat via WhatsApp</span>
</a>
</div>
</div>
</div>
</div>
</section>
<!-- Modal container (diisi oleh main.js) -->
<div id="acaab-modal" class="not-prose"></div>
<p>Tulis konten halaman kelompok di sini.</p>
<h3>Subjudul</h3>
<p>Gunakan paragraf, list, tabel, gambar, dan elemen konten lainnya.</p>
<!-- ================= END: BAGIAN YANG DIEDIT KELOMPOK ================= -->
</div>
</div>
@@ -1543,8 +1215,8 @@
</div>
<div class="w-full relative z-0 mt-4 pointer-events-none flex justify-center">
<img src="../../assets/global/footer.png" alt="Footer Illustration"
class="w-full h-auto object-cover object-bottom" style="object-position: bottom;">
<img src="../../assets/global/footer.png" alt="Footer Illustration" class="w-full h-auto object-cover object-bottom"
style="object-position: bottom;">
</div>
</footer>
@@ -1640,30 +1312,27 @@
})();
</script>
<script>
(function () {
const mobileBtn = document.getElementById('mobile-menu-button');
const mobileMenu = document.getElementById('mobile-menu');
const closeMobileBtn = document.getElementById('close-mobile-menu');
if (mobileBtn && mobileMenu) {
mobileBtn.addEventListener('click', function () {
const isOpen = !mobileMenu.classList.contains('-translate-x-full');
mobileMenu.classList.toggle('-translate-x-full');
mobileBtn.setAttribute('aria-expanded', isOpen ? 'false' : 'true');
});
}
if (closeMobileBtn && mobileMenu) {
closeMobileBtn.addEventListener('click', function () {
mobileMenu.classList.add('-translate-x-full');
if (mobileBtn) mobileBtn.setAttribute('aria-expanded', 'false');
});
}
})();
</script>
<script>
(function() {
const mobileBtn = document.getElementById('mobile-menu-button');
const mobileMenu = document.getElementById('mobile-menu');
const closeMobileBtn= document.getElementById('close-mobile-menu');
if (mobileBtn && mobileMenu) {
mobileBtn.addEventListener('click', function() {
const isOpen = !mobileMenu.classList.contains('-translate-x-full');
mobileMenu.classList.toggle('-translate-x-full');
mobileBtn.setAttribute('aria-expanded', isOpen ? 'false' : 'true');
});
}
if (closeMobileBtn && mobileMenu) {
closeMobileBtn.addEventListener('click', function() {
mobileMenu.classList.add('-translate-x-full');
if (mobileBtn) mobileBtn.setAttribute('aria-expanded', 'false');
});
}
})();
</script>
<script src="../../assets/global/localization.js"></script>
<script src="./localization.js"></script>
<script src="./main.js"></script>
<script src="../../assets/global/localization.js"></script>
</body>
</html>
</html>
-123
View File
@@ -1,123 +0,0 @@
/**
* ACAAB localization extension.
* Keep group-specific translations outside assets/global/localization.js
* to avoid conflicts with the main project.
*/
(function extendAcaabTranslations() {
if (typeof translations === 'undefined') return;
const acaabTranslations = {
id: {
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_stats_label_ach: "Prestasi Mahasiswa",
grp_acaab_stats_label_proj: "Proyek Mahasiswa",
grp_acaab_modal_year: "Tahun",
grp_acaab_modal_competition: "Kompetisi",
grp_acaab_modal_field: "Bidang",
grp_acaab_modal_description: "Deskripsi",
grp_acaab_modal_close: "Tutup",
grp_acaab_modal_note: "Catatan Tambahan",
grp_acaab_modal_batch_prefix: "Angkatan",
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: {
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_stats_label_ach: "Student Achievements",
grp_acaab_stats_label_proj: "Student Projects",
grp_acaab_modal_year: "Year",
grp_acaab_modal_competition: "Competition",
grp_acaab_modal_field: "Field",
grp_acaab_modal_description: "Description",
grp_acaab_modal_close: "Close",
grp_acaab_modal_note: "Additional Notes",
grp_acaab_modal_batch_prefix: "Batch",
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",
},
};
Object.assign(translations.id, acaabTranslations.id);
Object.assign(translations.en, acaabTranslations.en);
}());
-837
View File
@@ -1,837 +0,0 @@
/**
* 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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
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 ? `${t('grp_acaab_modal_batch_prefix')} ${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.sort((a, b) => b.year - a.year);
} 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(() =>
`<div class="acaab-skeleton" style="height:220px;border-radius:0.75rem;"></div>`
).join('');
}
function showError(containerId, msg) {
const el = document.getElementById(containerId);
if (el) el.innerHTML = `<p style="color:#ef4444;padding:1rem 0;grid-column:1/-1;">${escHtml(msg)}</p>`;
}
// ─── 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 = `<option value="">${escHtml(t('grp_acaab_ach_filter_level'))}</option>` +
levels.map(l => `<option value="${escHtml(l)}"${achFilter.level === l ? ' selected' : ''}>${escHtml(l)}</option>`).join('');
catSel.innerHTML = `<option value="">${escHtml(t('grp_acaab_ach_filter_cat'))}</option>` +
cats.map(c => `<option value="${escHtml(c)}"${achFilter.cat === c ? ' selected' : ''}>${escHtml(c)}</option>`).join('');
yearSel.innerHTML = `<option value="">${escHtml(t('grp_acaab_ach_filter_year'))}</option>` +
years.map(y => `<option value="${escHtml(y)}"${achFilter.year === y ? ' selected' : ''}>${escHtml(y)}</option>`).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 = `<p style="color:#64748b;padding:0.5rem 0;grid-column:1/-1;">${escHtml(t('grp_acaab_ach_empty'))}</p>`;
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-card').forEach(card => {
card.addEventListener('click', (e) => {
// Biarkan tombol yang merupakan link tetap berfungsi
if (e.target.closest('a')) return;
const ach = achievements.find(a => a.id === card.dataset.id);
if (ach) openAchModal(ach);
});
});
}
function achCardHtml(a, idx) {
return `
<article class="acaab-ach-card acaab-fade-in" data-id="${escHtml(a.id)}" style="animation-delay:${idx * 0.05}s;cursor:pointer;">
<div class="acaab-ach-top-bar"></div>
<!-- Image h-56 (224px) -->
<div style="position:relative;height:224px;overflow:hidden;background:#f1f5f9;">
<img src="${escHtml(a.image)}" alt="${escHtml(a.title)}" loading="lazy" class="acaab-card-img"
style="width:100%;height:100%;object-fit:cover;"
onerror="this.src='https://placehold.co/800x600/003150/FDB813?text=Informatika+UNTAN'">
<!-- Rank badge top-left: white pill dengan trophy icon -->
<div style="position:absolute;top:0.75rem;left:0.75rem;background:rgba(255,255,255,0.95);backdrop-filter:blur(4px);color:#003150;font-weight:700;padding:0.25rem 0.625rem;border-radius:9999px;box-shadow:0 1px 3px rgba(0,0,0,0.15);display:flex;align-items:center;gap:0.375rem;font-size:0.72rem;z-index:2;">
<i class="fas fa-trophy" style="color:#feb401;font-size:0.65rem;"></i>
${escHtml(a.rank)}
</div>
</div>
<!-- Content p-6 -->
<div style="padding:1.5rem;display:flex;flex-direction:column;flex-grow:1;">
<!-- Category + Year row -->
<div style="display:flex;align-items:center;gap:0.75rem;margin-bottom:0.75rem;">
<span style="color:#1e40af;font-size:0.72rem;font-weight:700;text-transform:uppercase;letter-spacing:0.05em;">${escHtml(a.category)}</span>
<span style="color:#94a3b8;font-size:0.72rem;font-weight:500;display:flex;align-items:center;gap:0.25rem;border-left:1px solid #e2e8f0;padding-left:0.625rem;">
<i class="fas fa-calendar-alt" style="font-size:0.6rem;"></i> ${escHtml(String(a.year))}
</span>
</div>
<!-- Title -->
<h3 class="acaab-card-title acaab-line-clamp-2" style="font-size:1.125rem;font-weight:700;color:#003150;margin:0 0 0.75rem;line-height:1.35;font-family:'Roboto Condensed',sans-serif;">${escHtml(a.title)}</h3>
<!-- Description 3 lines -->
<p class="acaab-line-clamp-3" style="color:#475569;font-size:0.875rem;line-height:1.6;margin:0 0 1.5rem;flex-grow:1;">${escHtml(a.description)}</p>
<!-- Bottom -->
<div style="border-top:1px solid #f1f5f9;padding-top:1rem;margin-top:auto;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;">
<!-- Avatar + team + generation -->
<div style="display:flex;align-items:center;gap:0.625rem;">
<div style="width:2rem;height:2rem;border-radius:0.375rem;background:rgba(0,49,80,0.05);border:1px solid rgba(0,49,80,0.1);display:flex;align-items:center;justify-content:center;color:#003150;font-weight:700;font-size:0.65rem;flex-shrink:0;">${escHtml(a.avatar)}</div>
<div>
<span style="font-weight:700;color:#1e293b;display:block;font-size:0.8125rem;line-height:1.2;">${escHtml(a.team)}</span>
<span style="color:#64748b;display:block;font-size:0.72rem;margin-top:0.1rem;">${escHtml(a.generation)}</span>
</div>
</div>
<!-- Level badge -->
<span style="background:#f1f5f9;color:#475569;font-size:0.575rem;padding:0.2rem 0.5rem;border-radius:0.25rem;font-weight:700;text-transform:uppercase;letter-spacing:0.05em;white-space:nowrap;">${escHtml(a.level)}</span>
</div>
<!-- Full-width CTA button -->
<button class="acaab-ach-readmore acaab-ach-read-btn" data-id="${escHtml(a.id)}"
style="width:100%;padding:0.625rem;border-radius:0.375rem;background:rgba(0,49,80,0.05);color:#003150;font-weight:700;font-size:0.875rem;border:none;cursor:pointer;transition:background 0.2s,color 0.2s;min-height:44px;">
${escHtml(t('grp_acaab_modal_read_more'))}
</button>
</div>
</div>
</article>`;
}
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 `<div style="display:flex;gap:0.625rem;align-items:flex-start;">
<span style="color:#feb401;margin-top:0.125rem;flex-shrink:0;"><i class="fas fa-${icon}" style="font-size:0.75rem;"></i></span>
<div>
<p style="font-size:0.6rem;font-weight:700;text-transform:uppercase;letter-spacing:0.08em;color:#94a3b8;margin:0 0 0.125rem;line-height:1;">${escHtml(label)}</p>
<p style="font-size:0.875rem;font-weight:600;color:#1e293b;margin:0;line-height:1.3;">${escHtml(value)}</p>
</div>
</div>`;
}
function memberItem(nama, sub) {
const initial = (nama || '?').charAt(0).toUpperCase();
return `<li style="display:flex;align-items:center;gap:0.5rem;background:#f8fafc;padding:0.5rem 0.75rem;border-radius:0.375rem;">
<span style="width:1.5rem;height:1.5rem;border-radius:50%;background:rgba(254,180,1,0.2);color:#b45309;display:flex;align-items:center;justify-content:center;font-size:0.72rem;font-weight:700;flex-shrink:0;">${initial}</span>
<div style="display:flex;flex-direction:column;">
<span style="font-size:0.875rem;font-weight:600;color:#334155;line-height:1.2;">${escHtml(nama)}</span>
${sub ? `<span style="font-size:0.65rem;color:#94a3b8;margin-top:0.125rem;">${escHtml(sub)}</span>` : ''}
</div>
</li>`;
}
function openAchModal(a) {
const membersHtml = a.members && a.members.length > 0 ? `
<div style="border-top:1px solid #f1f5f9;padding-top:1rem;margin-bottom:1rem;">
<p style="font-size:0.6rem;font-weight:700;text-transform:uppercase;letter-spacing:0.08em;color:#94a3b8;margin:0 0 0.5rem;">${t('grp_acaab_modal_members')}</p>
<ul style="list-style:none;margin:0;padding:0;display:grid;grid-template-columns:1fr 1fr;gap:0.5rem;">
${a.members.map(m => memberItem(m.nama, m.angkatan ? `${t('grp_acaab_modal_batch_prefix')} ${m.angkatan}` : '')).join('')}
</ul>
</div>` : '';
renderModal(`
<!-- Image header -->
<div style="position:relative;border-radius:0.75rem 0.75rem 0 0;background:#0f172a;flex-shrink:0;">
<img src="${escHtml(a.image)}" alt="${escHtml(a.title)}" style="width:100%;max-height:450px;object-fit:contain;display:block;"
onerror="this.src='https://placehold.co/800x600/0f172a/FDB813?text=Informatika+UNTAN'">
<div style="position:absolute;inset:0;background:linear-gradient(to top,rgba(0,0,0,0.8) 0%,rgba(0,0,0,0.1) 40%,transparent 100%);pointer-events:none;"></div>
<!-- Close button: white rounded-full -->
<button id="acaab-modal-close" aria-label="Tutup"
style="position:absolute;top:0.75rem;right:0.75rem;width:2.25rem;height:2.25rem;background:rgba(255,255,255,0.9);backdrop-filter:blur(4px);border:none;border-radius:50%;display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 1px 3px rgba(0,0,0,0.2);">
<i class="fas fa-times" style="color:#334155;font-size:0.875rem;"></i>
</button>
<!-- Two badges bottom-left -->
<div style="position:absolute;bottom:1rem;left:1rem;display:flex;gap:0.5rem;flex-wrap:wrap;">
<span style="background:#feb401;color:#0f172a;font-size:0.72rem;font-weight:700;padding:0.3rem 0.75rem;border-radius:0.25rem;display:flex;align-items:center;gap:0.375rem;">
<i class="fas fa-trophy" style="font-size:0.65rem;"></i>${escHtml(a.rank)}
</span>
<span style="background:rgba(255,255,255,0.2);backdrop-filter:blur(4px);color:white;font-size:0.72rem;font-weight:700;padding:0.3rem 0.75rem;border-radius:0.25rem;text-transform:uppercase;letter-spacing:0.05em;">
${escHtml(a.level)}
</span>
</div>
</div>
<!-- Content -->
<div style="padding:1.5rem;">
<span style="color:#1e40af;font-size:0.72rem;font-weight:700;text-transform:uppercase;letter-spacing:0.05em;">${escHtml(a.category)}</span>
<h2 style="font-size:1.125rem;font-weight:700;color:#003150;line-height:1.3;margin:0.25rem 0 1.25rem;font-family:'Roboto Condensed',sans-serif;">${escHtml(a.title)}</h2>
<!-- Info grid: slate-50 bg -->
<div style="display:grid;grid-template-columns:1fr 1fr;gap:1rem;padding:1rem;background:#f8fafc;border-radius:0.5rem;margin-bottom:1.25rem;">
${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', t('grp_acaab_modal_year'), String(a.year))}
${infoRow('award', t('grp_acaab_modal_competition'), a.competition)}
${infoRow('tag', t('grp_acaab_modal_field'), a.category)}
</div>
${membersHtml}
<!-- Description -->
<div style="border-top:1px solid #f1f5f9;padding-top:1rem;">
<p style="font-size:0.6rem;font-weight:700;text-transform:uppercase;letter-spacing:0.08em;color:#94a3b8;margin:0 0 0.5rem;">${t('grp_acaab_modal_description')}</p>
<p style="color:#475569;font-size:0.875rem;line-height:1.65;margin:0;">${escHtml(a.description)}</p>
</div>
<!-- Close button full-width -->
<button id="acaab-modal-close-btn" style="margin-top:1.5rem;width:100%;padding:0.75rem;background:#003150;color:white;font-weight:700;border:none;border-radius:0.5rem;cursor:pointer;font-size:0.875rem;">
${t('grp_acaab_modal_close')}
</button>
</div>
`, 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 = `<option value="">${escHtml(t('grp_acaab_proj_filter_cat'))}</option>` +
cats.map(c => `<option value="${escHtml(c)}"${projFilter.cat === c ? ' selected' : ''}>${escHtml(c)}</option>`).join('');
yearSel.innerHTML = `<option value="">${escHtml(t('grp_acaab_proj_filter_year'))}</option>` +
years.map(y => `<option value="${escHtml(y)}"${projFilter.year === y ? ' selected' : ''}>${escHtml(y)}</option>`).join('');
}
function filterProjects() {
const q = projFilter.search.toLowerCase();
const result = 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;
});
// Sort descending by year
result.sort((a, b) => (b.year || 0) - (a.year || 0));
return result;
}
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 = `<p style="color:#64748b;padding:0.5rem 0;">${escHtml(t('grp_acaab_proj_empty'))}</p>`;
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';
// Responsif: Sembunyikan scroll button & center jika cuma 1 data
const prevBtn = document.getElementById('acaab-proj-prev');
const nextBtn = document.getElementById('acaab-proj-next');
if (prevBtn) prevBtn.style.display = filtered.length <= 1 ? 'none' : 'flex';
if (nextBtn) nextBtn.style.display = filtered.length <= 1 ? 'none' : 'flex';
track.style.justifyContent = filtered.length === 1 ? 'center' : 'flex-start';
track.querySelectorAll('.acaab-proj-card-wrap').forEach(card => {
card.addEventListener('click', (e) => {
if (e.target.closest('a') && e.target.closest('a').href) return;
const proj = projects.find(p => p.id === card.dataset.id);
if (proj) openProjModal(proj);
});
});
}
function projCardHtml(p, idx) {
const catBadges = p.categories.map(c => `
<span style="display:inline-flex;align-items:center;gap:0.2rem;font-size:0.6rem;font-weight:700;color:#feb401;text-transform:uppercase;letter-spacing:0.05em;background:rgba(0,0,0,0.3);padding:0.2rem 0.45rem;border-radius:0.2rem;backdrop-filter:blur(4px);">
<i class="fas fa-tag" style="font-size:0.5rem;"></i>${escHtml(c)}
</span>`).join('');
return `
<div class="acaab-proj-card acaab-proj-card-wrap acaab-fade-in" data-id="${escHtml(p.id)}" style="animation-delay:${idx * 0.05}s;cursor:pointer;">
<div class="acaab-proj-left-bar"></div>
<!-- Image h-52 (208px) dengan title overlay -->
<div style="height:208px;overflow:hidden;position:relative;">
<img src="${escHtml(p.image)}" alt="${escHtml(p.title)}" loading="lazy" class="acaab-card-img"
style="width:100%;height:100%;object-fit:cover;"
onerror="this.src='https://placehold.co/800x600/003150/FDB813?text=Informatika+UNTAN'">
<!-- Gradient overlay: title + categories di dalam gambar -->
<div style="position:absolute;inset:0;background:linear-gradient(to top,rgba(0,49,80,0.85),transparent);padding:1.25rem;display:flex;flex-direction:column;justify-content:flex-end;pointer-events:none;">
<h4 style="font-size:1rem;font-weight:700;color:white;line-height:1.25;margin:0 0 0.375rem;font-family:'Roboto Condensed',sans-serif;">${escHtml(p.title)}</h4>
<div style="display:flex;flex-wrap:wrap;gap:0.3rem;">${catBadges}</div>
</div>
</div>
<!-- Body p-5 -->
<div style="padding:1.25rem;display:flex;flex-direction:column;flex-grow:1;">
<!-- Meta: student + year -->
<div style="display:flex;flex-wrap:wrap;align-items:center;gap:0.625rem;margin-bottom:0.75rem;font-size:0.72rem;color:#64748b;font-weight:500;">
${p.studentName ? `<span style="display:flex;align-items:center;gap:0.25rem;"><i class="fas fa-user" style="color:#94a3b8;font-size:0.6rem;"></i>${escHtml(p.studentName)}</span>` : ''}
${p.year ? `<span style="display:flex;align-items:center;gap:0.25rem;"><i class="fas fa-calendar-alt" style="color:#94a3b8;font-size:0.6rem;"></i>${escHtml(String(p.year))}</span>` : ''}
</div>
<!-- Description 3 lines -->
<p class="acaab-line-clamp-3" style="color:#475569;font-size:0.875rem;line-height:1.6;margin:0 0 1.25rem;flex-grow:1;">${escHtml(p.description)}</p>
<!-- Actions -->
<div style="display:flex;align-items:center;gap:0.625rem;margin-top:auto;">
<button class="acaab-proj-readmore" data-id="${escHtml(p.id)}"
style="color:#003150;font-weight:700;font-size:0.875rem;background:none;border:none;cursor:pointer;display:flex;align-items:center;gap:0.375rem;min-height:44px;">
${escHtml(t('grp_acaab_modal_read_more'))} <i class="fas fa-arrow-right" style="font-size:0.7rem;"></i>
</button>
${p.link ? `
<a href="${escHtml(p.link)}" target="_blank" rel="noopener noreferrer"
style="margin-left:auto;display:flex;align-items:center;gap:0.375rem;font-size:0.72rem;font-weight:700;color:#64748b;border:1px solid #e2e8f0;border-radius:0.375rem;padding:0.375rem 0.625rem;text-decoration:none;min-height:44px;transition:color 0.2s,border-color 0.2s;">
<i class="fas fa-external-link-alt" style="font-size:0.65rem;"></i>${escHtml(t('grp_acaab_modal_visit'))}
</a>` : ''}
</div>
</div>
</div>`;
}
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 ? `
<div style="border-top:1px solid #f1f5f9;padding-top:1rem;margin-bottom:1rem;">
<p style="font-size:0.6rem;font-weight:700;text-transform:uppercase;letter-spacing:0.08em;color:#94a3b8;margin:0 0 0.5rem;">${t('grp_acaab_modal_members')}</p>
<ul style="list-style:none;margin:0;padding:0;display:grid;grid-template-columns:1fr 1fr;gap:0.5rem;">
${p.members.map(m => memberItem(m.nama, m.nim || '')).join('')}
</ul>
</div>` : '';
const catBadgesImg = p.categories.map(c => `
<span style="background:#feb401;color:#0f172a;font-size:0.72rem;font-weight:700;padding:0.3rem 0.75rem;border-radius:0.25rem;display:inline-flex;align-items:center;gap:0.375rem;text-transform:uppercase;letter-spacing:0.05em;">
<i class="fas fa-tag" style="font-size:0.6rem;"></i>${escHtml(c)}
</span>`).join('');
const linkSection = p.link
? `<a href="${escHtml(p.link)}" target="_blank" rel="noopener noreferrer"
style="display:flex;align-items:center;justify-content:center;gap:0.5rem;width:100%;padding:0.75rem;background:#003150;color:white;font-weight:700;border-radius:0.5rem;font-size:0.875rem;text-decoration:none;margin-bottom:0.625rem;">
<i class="fas fa-external-link-alt" style="font-size:0.8rem;"></i>${escHtml(t('grp_acaab_modal_visit'))}
</a>`
: `<div style="display:flex;align-items:center;justify-content:center;gap:0.5rem;width:100%;padding:0.75rem;background:#f1f5f9;color:#94a3b8;font-weight:700;border-radius:0.5rem;font-size:0.875rem;margin-bottom:0.625rem;cursor:not-allowed;">
<i class="fas fa-file-alt" style="font-size:0.8rem;"></i>${escHtml(t('grp_acaab_modal_no_link'))}
</div>`;
renderModal(`
<!-- Image header -->
<div style="position:relative;border-radius:0.75rem 0.75rem 0 0;background:#0f172a;flex-shrink:0;">
<img src="${escHtml(p.image)}" alt="${escHtml(p.title)}" style="width:100%;max-height:450px;object-fit:contain;display:block;"
onerror="this.src='https://placehold.co/800x600/0f172a/FDB813?text=Informatika+UNTAN'">
<div style="position:absolute;inset:0;background:linear-gradient(to top,rgba(0,0,0,0.8) 0%,rgba(0,0,0,0.1) 40%,transparent 100%);pointer-events:none;"></div>
<!-- Close button -->
<button id="acaab-modal-close" aria-label="Tutup"
style="position:absolute;top:0.75rem;right:0.75rem;width:2.25rem;height:2.25rem;background:rgba(255,255,255,0.9);backdrop-filter:blur(4px);border:none;border-radius:50%;display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 1px 3px rgba(0,0,0,0.2);">
<i class="fas fa-times" style="color:#334155;font-size:0.875rem;"></i>
</button>
<!-- Category badges bottom-left -->
<div style="position:absolute;bottom:1rem;left:1rem;display:flex;flex-wrap:wrap;gap:0.375rem;">${catBadgesImg}</div>
</div>
<!-- Content -->
<div style="padding:1.5rem;">
<h2 style="font-size:1.125rem;font-weight:700;color:#003150;line-height:1.3;margin:0 0 1.25rem;font-family:'Roboto Condensed',sans-serif;">${escHtml(p.title)}</h2>
<!-- Info grid -->
<div style="display:grid;grid-template-columns:1fr 1fr;gap:1rem;padding:1rem;background:#f8fafc;border-radius:0.5rem;margin-bottom:1.25rem;">
${p.studentName ? infoRow('users', t('grp_acaab_modal_team'), p.studentName) : ''}
${p.nim ? infoRow('id-card', 'NIM', p.nim) : ''}
${infoRow('calendar-alt', t('grp_acaab_modal_year'), String(p.year))}
${infoRow('tag', t('grp_acaab_modal_field'), p.categories.join(', '))}
</div>
${membersHtml}
<!-- Description -->
<div style="border-top:1px solid #f1f5f9;padding-top:1rem;margin-bottom:1.25rem;">
<p style="font-size:0.6rem;font-weight:700;text-transform:uppercase;letter-spacing:0.08em;color:#94a3b8;margin:0 0 0.5rem;">${t('grp_acaab_modal_description')}</p>
<p style="color:#475569;font-size:0.875rem;line-height:1.65;margin:0;">${escHtml(p.description)}</p>
${p.catatan ? `
<div style="margin-top:1rem;padding:0.75rem;background:rgba(239,246,255,0.5);border:1px solid #bfdbfe;border-radius:0.5rem;">
<p style="font-size:0.6rem;font-weight:700;text-transform:uppercase;color:#93c5fd;margin:0 0 0.25rem;">${t('grp_acaab_modal_note')}</p>
<p style="font-size:0.875rem;color:#475569;font-style:italic;margin:0;">${escHtml(p.catatan)}</p>
</div>` : ''}
</div>
<!-- Link + close buttons -->
${linkSection}
<button id="acaab-modal-close-btn" style="width:100%;padding:0.625rem;border:2px solid #e2e8f0;color:#475569;font-weight:700;background:white;border-radius:0.5rem;cursor:pointer;font-size:0.875rem;transition:border-color 0.2s,background 0.2s;">
${t('grp_acaab_modal_close')}
</button>
</div>
`, true);
}
// ─── Modal shared ─────────────────────────────────────────────────────────────
function renderModal(innerHtml, hasCloseBtn = false) {
const container = document.getElementById('acaab-modal');
if (!container) return;
container.innerHTML = `
<div class="acaab-modal-backdrop" id="acaab-modal-bd">
<div style="background:white;border-radius:0.75rem;max-width:672px;width:100%;max-height:90vh;overflow-y:auto;box-shadow:0 20px 60px rgba(0,0,0,0.35);">
${innerHtml}
</div>
</div>`;
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');
}
+38 -212
View File
@@ -1,222 +1,48 @@
/* =====================================================
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);
:root {
--brand-navy: #003150;
--brand-yellow: #feb401;
--text-main: #1e293b;
--text-soft: #64748b;
--bg-soft: #f8fafc;
--line: #e2e8f0;
}
/* --- Sembunyikan scrollbar carousel tapi tetap scrollable --- */
.acaab-hide-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
.acaab-hide-scrollbar::-webkit-scrollbar {
display: none;
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body { font-family: "Roboto", Arial, sans-serif; color: var(--text-main); background: #fff; line-height: 1.6; }
.container { width: min(1100px, 92%); margin-inline: auto; }
/* --- 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; } }
.nav { background: var(--brand-navy); color: #fff; border-bottom: 4px solid var(--brand-yellow); padding: 14px 0; }
.brand { font-size: 1.1rem; font-weight: 700; letter-spacing: .02em; }
/* --- Snap scroll carousel --- */
.acaab-carousel-track {
scroll-snap-type: x mandatory;
-webkit-overflow-scrolling: touch;
}
.hero { position: relative; color: #fff; min-height: 220px; display: flex; align-items: flex-end; overflow: hidden; }
.hero-bg { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; filter: brightness(0.45); }
.hero-inner { position: relative; z-index: 1; padding: 36px 0; }
.badge { display: inline-block; background: var(--brand-yellow); color: #111827; font-size: .68rem; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; padding: .2rem .45rem; margin-bottom: 12px; }
.hero h1 { margin: 0; font-size: clamp(1.6rem, 3.2vw, 2.8rem); line-height: 1.2; }
/* --- 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;
}
.layout { display: grid; grid-template-columns: 1fr; gap: 2rem; padding: 2rem 0 4rem; }
@media (min-width: 1024px) { .layout { grid-template-columns: minmax(0, 3fr) minmax(260px, 1fr); } }
/* --- 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; }
.meta { font-size: .78rem; text-transform: uppercase; color: #475569; display: flex; flex-wrap: wrap; gap: .75rem 1rem; margin-bottom: 1rem; }
.cover { background: var(--bg-soft); border: 1px solid var(--line); margin-bottom: 1.5rem; }
.cover img { width: 100%; height: auto; display: block; }
/* --- 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; }
}
.article-body > * + * { margin-top: 1.1em; }
.article-body h2 { font-size: 1.35rem; margin: 1.6em 0 .5em; }
.article-body h3 { font-size: 1.1rem; margin: 1.4em 0 .4em; }
.article-body p { margin: 0 0 1em; }
.article-body ul, .article-body ol { padding-left: 1.4rem; }
.article-body blockquote { border-left: 4px solid #005eb8; background: #eef6ff; padding: .8rem 1rem; color: #334155; }
.article-body a { color: #005eb8; }
.article-body img { max-width: 100%; border-radius: 8px; }
.article-body code { background: #f1f5f9; padding: .1em .35em; border-radius: 4px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
/* --- 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;
}
.note { border: 1px solid var(--line); background: var(--bg-soft); padding: 1rem; }
.note h3 { margin: 0 0 .5rem; font-size: .76rem; text-transform: uppercase; letter-spacing: .12em; color: #475569; }
/* --- 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) {
}
/* --- Modal Responsiveness Overrides --- */
.acaab-modal-achievement {
max-width: 500px !important;
}
.acaab-modal-project {
max-width: 700px !important;
}
@media (max-width: 639px) {
.acaab-modal-achievement, .acaab-modal-project {
max-width: 100% !important;
margin: 0 !important;
border-radius: 1rem 1rem 0 0 !important;
}
}
.footer { margin-top: 2rem; background: var(--brand-navy); color: #fff; border-top: 4px solid var(--brand-yellow); }
.footer-inner { display: flex; align-items: center; justify-content: center; gap: .75rem; padding: 1rem 0; }
.footer-logo { height: 28px; width: auto; }
.footer-ornament { width: 100%; display: block; opacity: .9; }
-86
View File
@@ -1,86 +0,0 @@
# Panduan Pengelolaan Data Kelompok C4A
Dokumentasi ini dibuat untuk memudahkan pengembang atau pengguna berikutnya dalam mengelola data Staf Pendukung, SOP, dan FAQ pada halaman C4A.
---
## 📋 Aturan Umum Pengelolaan Data
1. **Lokasi Berkas Data**:
Seluruh data disimpan dalam format JSON di direktori `groups/C4A/data/`:
- Data Staf: `groups/C4A/data/data-staff.json`
- Data SOP: `groups/C4A/data/data-sop.json`
- Data FAQ: `groups/C4A/data/data-faq.json`
2. **Ikon FontAwesome (v4)**:
Jika ingin mengubah atau menambahkan logo/ikon pada data SOP, harap gunakan daftar ikon dari **FontAwesome v4** melalui tautan berikut:
👉 **[FontAwesome v4 Icons](https://fontawesome.com/v4/icons/)** (Contoh penggunaan: `fa-dot-circle-o`, `fa-graduation-cap`, `fa-flask`).
3. **Field Opsional**:
Pada data SOP, properti **`staff_responsible`** (Staf Bertanggung Jawab) dan **`additional_info`** (Informasi Tambahan) bersifat **opsional**. Jika informasi tersebut tidak tersedia, kosongkan nilainya atau hapus propertinya dari JSON agar UI secara otomatis menyembunyikan bloknya.
---
## 🛠️ Template Penambahan Data
Silakan salin-tempel (copy-paste) format di bawah ini ke dalam array JSON yang sesuai di folder `data/` untuk menambahkan data baru.
### 1. Template Data Staf (`data-staff.json`)
```json
{
"id": 3,
"name": "Nama Staf Beserta Gelar",
"role": "Peran / Jabatan Staf",
"image": "assets/images/nama-file-foto.png",
"location": "Lokasi Ruang Kerja / Kantor Staf",
"contact_consent": true,
"phone": "628xxxxxxxxxx",
"schedule": [
{
"day": "Hari Kerja (contoh: Senin Jumat)",
"time": "Jam Kerja (contoh: 07.3016.00 WIB)",
"break": "Jam Istirahat (contoh: istirahat 12.0013.00 WIB)"
}
],
"tasks": [
"Tugas dan tanggung jawab utama 1",
"Tugas dan tanggung jawab utama 2"
]
}
```
*Catatan: Letakkan file gambar/foto staf ke dalam direktori `groups/C4A/assets/images/` dan masukkan nama berkas fotonya pada properti `image` di atas. Untuk nomor telepon (`phone`), Anda dapat menggunakan format `08xxxxxxxxxx` atau `628xxxxxxxxxx` karena skrip JavaScript akan melakukan normalisasi secara otomatis.*
### 2. Template Data SOP (`data-sop.json`)
```json
{
"tab_id": "id-unik-sop-tanpa-spasi",
"tab_number": 9,
"category": "akademik",
"icon": "fa-file-text",
"title": "Judul Lengkap SOP",
"description": "Deskripsi singkat mengenai SOP ini.",
"purpose": "Tujuan dibuatnya standar operasional prosedur ini.",
"requirements": [
"Persyaratan administratif/akademik 1",
"Persyaratan administratif/akademik 2"
],
"procedures": [
"Langkah alur prosedur ke-1",
"Langkah alur prosedur ke-2"
],
"staff_responsible": [
"👤 Staf Administrasi Informatika"
],
"additional_info": "Informasi tambahan penting."
}
```
*Catatan: Properti `category` bernilai antara `"akademik"` atau `"praktikum"`. Properti `staff_responsible` dan `additional_info` bersifat opsional.*
### 3. Template Data FAQ (`data-faq.json`)
```json
{
"id": 11,
"question": "Pertanyaan yang sering diajukan?",
"answer": "Jawaban dari pertanyaan tersebut. Boleh menggunakan tag HTML seperti <strong>tebal</strong>."
}
```
Binary file not shown.

Before

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 313 KiB

-112
View File
@@ -1,112 +0,0 @@
[
{
"id": 1,
"question": {
"id": "Apa saja syarat administratif agar saya bisa mengikuti perkuliahan?",
"en": "What are the administrative requirements for me to attend lectures?"
},
"answer": {
"id": "Syarat untuk mengikuti perkuliahan adalah mahasiswa telah melakukan <strong>pembayaran UKT</strong>, mengisi <strong>KRS</strong>, dan terdaftar di <strong>kelas Edlink</strong>.",
"en": "The requirements to attend lectures are that the student has completed <strong>UKT payment</strong>, filled out the <strong>KRS</strong>, and is registered in <strong>Edlink classes</strong>."
}
},
{
"id": 2,
"question": {
"id": "Berapa persentase kehadiran minimal agar bisa mengikuti Ujian Akhir Semester (UAS)?",
"en": "What is the minimum attendance percentage required to take the Final Semester Exam (UAS)?"
},
"answer": {
"id": "Mahasiswa wajib memenuhi minimal <strong>75% kehadiran</strong> dari total pertemuan perkuliahan. Mahasiswa yang tidak memenuhi syarat kehadiran ini tidak diperkenankan mengikuti UAS.",
"en": "Students must meet a minimum of <strong>75% attendance</strong> out of the total lecture sessions. Students who do not meet this attendance requirement are not permitted to take the UAS."
}
},
{
"id": 3,
"question": {
"id": "Apakah pengisian evaluasi perkuliahan itu wajib?",
"en": "Is filling out the lecture evaluation mandatory?"
},
"answer": {
"id": "Ya, karena pengisian evaluasi perkuliahan dapat menjadi salah satu syarat untuk <strong>mengakses hasil studi</strong> sesuai dengan kebijakan yang berlaku. Hasil ini sangat penting untuk perbaikan dan pengembangan kualitas pembelajaran prodi ke depannya.",
"en": "Yes, because completing the lecture evaluation may be one of the requirements to <strong>access study results</strong> in accordance with applicable policies. This is very important for improving and developing the quality of the study program's learning in the future."
}
},
{
"id": 4,
"question": {
"id": "Berapa minimal SKS dan IPK untuk bisa mendaftar Kerja Praktik (KP)?",
"en": "What is the minimum number of credits (SKS) and GPA required to register for an Internship (KP)?"
},
"answer": {
"id": "Untuk mendaftar KP, mahasiswa harus sudah lulus minimal <strong>100 SKS</strong> dan memiliki Indeks Prestasi Kumulatif (IPK) <strong>≥ 2,00</strong>.",
"en": "To register for KP, students must have passed at least <strong>100 credits (SKS)</strong> and have a Cumulative Grade Point Average (GPA) of <strong>≥ 2.00</strong>."
}
},
{
"id": 5,
"question": {
"id": "Apakah ada syarat kelulusan mata kuliah tertentu sebelum saya bisa mengajukan Kerja Praktik (KP)?",
"en": "Are there specific course completion requirements before I can apply for an Internship (KP)?"
},
"answer": {
"id": "Ya, selain memenuhi syarat minimal SKS dan IPK, mahasiswa juga diwajibkan telah lulus mata kuliah <strong>Penulisan Proposal Tugas Akhir dengan nilai minimal C</strong>. Selain itu, mahasiswa juga harus menyerahkan berkas pendukung pengajuan KP seperti KTM, riwayat studi, transkrip nilai, dan surat permohonan.",
"en": "Yes, in addition to meeting the minimum SKS and GPA requirements, students are also required to have passed the <strong>Final Project Proposal Writing course with a minimum grade of C</strong>. Furthermore, students must also submit supporting documents for the KP application, such as student ID card, academic record, transcript, and application letter."
}
},
{
"id": 6,
"question": {
"id": "Apa kualifikasi yang dibutuhkan untuk Pembimbing Lapangan di instansi tempat KP?",
"en": "What qualifications are required for a Field Supervisor at the KP institution?"
},
"answer": {
"id": "Pembimbing Lapangan dari tempat pelaksanaan KP diwajibkan memiliki kualifikasi pendidikan minimal <strong>S1 atau D4</strong>.",
"en": "The Field Supervisor from the KP institution is required to have a minimum educational qualification of <strong>Bachelor's (S1) or Applied Bachelor's (D4)</strong>."
}
},
{
"id": 7,
"question": {
"id": "Bolehkah saya mengambil mata kuliah Kerja Praktik (KP) bersamaan dengan Tugas Akhir?",
"en": "Can I take the Internship (KP) course concurrently with my Final Project (Tugas Akhir)?"
},
"answer": {
"id": "Boleh. Mata Kuliah Kerja Praktik (KP) dapat diambil secara bersamaan dengan <strong>Tugas Akhir 1 (TA1)</strong> dan <strong>Tugas Akhir 2 (TA2)</strong> dalam satu semester yang sama.",
"en": "Yes. The Internship (KP) course can be taken concurrently with <strong>Final Project 1 (TA1)</strong> and <strong>Final Project 2 (TA2)</strong> within the same semester."
}
},
{
"id": 8,
"question": {
"id": "Apa saja syarat akademik utama sebelum mengajukan judul Tugas Akhir (Skripsi)?",
"en": "What are the main academic requirements before submitting a Final Project (Thesis) title?"
},
"answer": {
"id": "Syarat utamanya adalah mahasiswa harus telah lulus minimal <strong>120 SKS</strong>, memiliki <strong>IPK minimal 2,00</strong>, telah lulus mata kuliah <strong>Penulisan Proposal Tugas Akhir dengan nilai minimal C</strong>, serta telah mendaftarkan mata kuliah skripsi pada KRS.",
"en": "The main requirements are that the student must have passed at least <strong>120 credits (SKS)</strong>, have a <strong>minimum GPA of 2.00</strong>, have passed the <strong>Final Project Proposal Writing course with a minimum grade of C</strong>, and have registered the thesis course in the KRS."
}
},
{
"id": 9,
"question": {
"id": "Siapa yang berhak mendaftar menjadi Asisten Praktikum (Aslab)?",
"en": "Who is eligible to register as a Practicum Assistant (Aslab)?"
},
"answer": {
"id": "Rekrutmen terbuka bagi <strong>mahasiswa aktif Program Studi Informatika UNTAN</strong> yang telah lulus mata kuliah terkait (dengan nilai minimal sesuai ketentuan Dosen Pengampu) dan berkomitmen mengikuti seluruh jadwal praktikum selama semester berjalan.",
"en": "Recruitment is open to <strong>active students of the Informatics Study Program at UNTAN</strong> who have passed the relevant courses (with a minimum grade as per the Lecturer's requirements) and are committed to attending all practicum schedules throughout the semester."
}
},
{
"id": 10,
"question": {
"id": "Bagaimana prosedur meminjam Laboratorium Komputer untuk keperluan kegiatan kampus?",
"en": "What is the procedure for borrowing the Computer Laboratory for campus activities?"
},
"answer": {
"id": "Peminjam (dosen atau mahasiswa) harus mengajukan <strong>surat permohonan peminjaman</strong> kepada staf laboratorium pengelola dan menyampaikan jadwal penggunaannya. Penggunaan lab juga harus didampingi oleh <strong>dosen atau asisten praktikum</strong> yang bertanggung jawab.",
"en": "The borrower (lecturer or student) must submit a <strong>borrowing request letter</strong> to the laboratory management staff and provide the usage schedule. Lab usage must also be supervised by a <strong>responsible lecturer or practicum assistant</strong>."
}
}
]
-293
View File
@@ -1,293 +0,0 @@
[
{
"tab_id": "kuliah",
"tab_number": 1,
"category": "akademik",
"icon": "fa-chalkboard-teacher",
"title": {
"id": "SOP Pelaksanaan Kuliah",
"en": "Lecture Procedure SOP"
},
"description": {
"id": "Mengatur tata cara dan tahapan pelaksanaan perkuliahan mulai dari persiapan hingga evaluasi pembelajaran.",
"en": "Regulates the procedures and stages of lecture implementation from preparation to learning evaluation."
},
"purpose": {
"id": "SOP ini bertujuan untuk menjelaskan tata cara pelaksanaan perkuliahan bagi mahasiswa Program Studi Informatika Universitas Tanjungpura.",
"en": "This SOP aims to explain the procedures for conducting lectures for students of the Informatics Study Program at Tanjungpura University."
},
"requirements": [
{ "id": "Mahasiswa telah melakukan pembayaran UKT", "en": "Student has completed UKT payment" },
{ "id": "Mahasiswa telah mengisi KRS", "en": "Student has filled out the KRS" },
{ "id": "Mahasiswa terdaftar di kelas Edlink", "en": "Student is registered in Edlink classes" },
{ "id": "Mahasiswa wajib mengikuti perkuliahan dan menyelesaikan tugas tepat waktu", "en": "Students must attend lectures and complete assignments on time" },
{ "id": "Mahasiswa wajib mengisi daftar hadir dan memberikan alasan resmi jika tidak hadir", "en": "Students must fill in the attendance list and provide an official reason if absent" },
{ "id": "Mahasiswa memenuhi minimal 70% kehadiran untuk mengikuti UAS", "en": "Students must meet a minimum of 70% attendance to take the UAS" }
],
"procedures": [
{ "id": "Dosen memasuki ruang perkuliahan sesuai jadwal yang telah ditetapkan", "en": "The lecturer enters the lecture room according to the set schedule" },
{ "id": "Dosen mengabsensi mahasiswa dengan mencantumkan keterangan hadir, izin, atau tidak hadir", "en": "The lecturer takes attendance, noting present, excused, or absent" },
{ "id": "Pada pertemuan pertama, dosen membuat kontrak perkuliahan secara tertulis bersama mahasiswa", "en": "At the first meeting, the lecturer creates a written lecture contract with students" },
{ "id": "Dosen menjelaskan materi perkuliahan sesuai RPS yang telah diserahkan ke program studi", "en": "The lecturer explains the course material according to the RPS submitted to the study program" },
{ "id": "Mahasiswa mendiskusikan, mencatat, dan mengkritisi materi perkuliahan", "en": "Students discuss, take notes, and critically engage with the lecture material" },
{ "id": "Dosen menyimpulkan materi perkuliahan dan memberikan penugasan", "en": "The lecturer summarizes the lecture material and gives assignments" }
],
"additional_info": {
"id": "Mahasiswa yang tidak memenuhi persyaratan kehadiran minimal tidak diperkenankan mengikuti Ujian Akhir Semester (UAS) sesuai ketentuan akademik yang berlaku.",
"en": "Students who do not meet the minimum attendance requirements are not permitted to take the Final Semester Exam (UAS) in accordance with applicable academic regulations."
}
},
{
"tab_id": "ujian",
"tab_number": 2,
"category": "akademik",
"icon": "fa-file-signature",
"title": {
"id": "SOP Pelaksanaan Ujian (UTS & UAS)",
"en": "Exam Procedure SOP (Mid-Term & Final Exams)"
},
"description": {
"id": "Mengatur pedoman pelaksanaan Ujian Tengah Semester (UTS) dan Ujian Akhir Semester (UAS) mulai dari penentuan kriteria evaluasi, pelaksanaan, hingga perekapan nilai.",
"en": "Regulates the guidelines for conducting Mid-Term Exams (UTS) and Final Semester Exams (UAS), from determining evaluation criteria and implementation to grade recording."
},
"purpose": {
"id": "SOP ini bertujuan untuk memastikan kelancaran proses evaluasi pembelajaran mahasiswa melalui UTS dan UAS agar berjalan tertib, terukur, dan sesuai dengan instruksi dosen pengampu mata kuliah.",
"en": "This SOP aims to ensure the smooth process of student learning evaluation through UTS and UAS so that it runs orderly, measurably, and in accordance with the course lecturer's instructions."
},
"requirements": [
{ "id": "Mahasiswa terdaftar aktif pada mata kuliah yang sedang berjalan pada semester tersebut", "en": "Students are actively registered in the courses running in that semester" },
{ "id": "Persentase kehadiran perkuliahan mahasiswa tidak kurang dari 75%", "en": "Student lecture attendance percentage is not less than 75%" }
],
"procedures": [
{ "id": "Dosen pengampu menentukan kriteria UTS dan UAS", "en": "The course lecturer determines the UTS and UAS criteria" },
{ "id": "Mahasiswa mengikuti UTS atau UAS sesuai instruksi dosen pengampu, dan wajib melakukan presensi", "en": "Students take UTS or UAS according to the lecturer's instructions and must complete attendance" },
{ "id": "Mahasiswa mengumpulkan hasil UTS atau UAS sesuai rentang waktu yang diberikan", "en": "Students submit UTS or UAS results within the given timeframe" },
{ "id": "Dosen menilai dan merekap pengerjaan UTS atau UAS mahasiswa", "en": "The lecturer grades and records the students' UTS or UAS work" }
],
"additional_info": {
"id": "Segala bentuk kecurangan saat ujian, maupun tindakan plagiarisme dalam pengerjaan UTS/UAS, dapat mengakibatkan pembatalan nilai atau pemberian sanksi.",
"en": "Any form of cheating during exams, as well as plagiarism in completing the UTS/UAS, may result in grade cancellation or sanctions."
}
},
{
"tab_id": "evaluasi",
"tab_number": 3,
"category": "akademik",
"icon": "fa-chart-line",
"title": {
"id": "SOP Pelaksanaan Evaluasi Pengembangan Perkuliahan",
"en": "Lecture Development Evaluation SOP"
},
"description": {
"id": "Mengatur tata cara pelaksanaan evaluasi perkuliahan oleh mahasiswa sebagai sarana umpan balik untuk meningkatkan kualitas proses belajar mengajar.",
"en": "Regulates the procedures for conducting lecture evaluations by students as a feedback mechanism to improve the quality of the teaching and learning process."
},
"purpose": {
"id": "SOP ini bertujuan memberikan pedoman pelaksanaan evaluasi perkuliahan guna mendukung peningkatan mutu pembelajaran di Program Studi Informatika Universitas Tanjungpura.",
"en": "This SOP aims to provide guidelines for conducting lecture evaluations to support the improvement of learning quality in the Informatics Study Program at Tanjungpura University."
},
"requirements": [
{ "id": "Mahasiswa terdaftar aktif pada semester berjalan", "en": "Students are actively registered for the current semester" },
{ "id": "Telah mengikuti kegiatan perkuliahan pada mata kuliah yang dievaluasi", "en": "Has attended lecture activities for the course being evaluated" },
{ "id": "Tersedia akses ke sistem evaluasi perkuliahan", "en": "Access to the lecture evaluation system is available" }
],
"procedures": [
{ "id": "Program Studi membuka periode evaluasi perkuliahan", "en": "The Study Program opens the lecture evaluation period" },
{ "id": "Mahasiswa mengakses satu untan dan mengisi kuesioner evaluasi perkuliahan", "en": "Students access the Satu Untan portal and fill out the lecture evaluation questionnaire" },
{ "id": "Sistem merekap hasil evaluasi yang telah diisi mahasiswa", "en": "The system records the evaluation results filled in by students" },
{ "id": "Program Studi meninjau hasil evaluasi sebagai bahan pengembangan perkuliahan", "en": "The Study Program reviews the evaluation results as material for lecture development" }
],
"additional_info": {
"id": "Hasil evaluasi digunakan sebagai dasar perbaikan dan pengembangan kualitas pembelajaran pada semester berikutnya. Pengisian evaluasi perkuliahan dapat menjadi salah satu syarat untuk mengakses hasil studi sesuai kebijakan yang berlaku.",
"en": "Evaluation results are used as a basis for improving and developing the quality of learning in the following semester. Completing the lecture evaluation may be one of the requirements for accessing study results in accordance with applicable policies."
}
},
{
"tab_id": "kp",
"tab_number": 4,
"category": "akademik",
"icon": "fa-briefcase",
"title": {
"id": "SOP Kerja Praktik",
"en": "Internship (KP) SOP"
},
"description": {
"id": "Panduan resmi pelaksanaan Kerja Praktek (KP) mulai dari tata cara pendaftaran, pelaksanaan, hingga seminar Kerja Praktek (KP).",
"en": "The official guide for conducting the Internship (KP), from registration procedures and implementation to the KP seminar."
},
"purpose": {
"id": "SOP ini bertujuan memberikan pedoman kepada seluruh pihak terkait dalam pelaksanaan Kerja Praktek serta meningkatkan efisiensi dan efektivitas pelaksanaannya di Program Studi Informatika Fakultas Teknik Universitas Tanjungpura.",
"en": "This SOP aims to provide guidelines to all relevant parties in the implementation of the Internship and to improve the efficiency and effectiveness of its execution in the Informatics Study Program, Faculty of Engineering, Tanjungpura University."
},
"requirements": [
{ "id": "Telah lulus minimal 100 SKS", "en": "Has passed a minimum of 100 credits (SKS)" },
{ "id": "Memiliki IPK minimal 2,00", "en": "Has a minimum GPA of 2.00" },
{ "id": "Telah lulus mata kuliah Penulisan Proposal Tugas Akhir dengan nilai minimal C", "en": "Has passed the Final Project Proposal Writing course with a minimum grade of C" },
{ "id": "Menyerahkan berkas pendukung pengajuan KP (KTM, riwayat studi, transkrip, dan surat permohonan)", "en": "Submits supporting documents for the KP application (student ID, academic record, transcript, and application letter)" },
{ "id": "Pembimbing Lapangan dari tempat KP memiliki kualifikasi pendidikan minimal S1/D4", "en": "The Field Supervisor at the KP institution holds a minimum educational qualification of Bachelor's (S1) or Applied Bachelor's (D4)" }
],
"procedures": [
{ "id": "Mahasiswa mengusulkan topik, draf proposal, dan menyerahkan berkas KP", "en": "Students propose a topic, submit a draft proposal, and submit KP documents" },
{ "id": "Staf Administrasi memverifikasi kelengkapan berkas", "en": "Administrative Staff verifies the completeness of documents" },
{ "id": "Ketua Prodi menerbitkan surat pengantar KP", "en": "The Head of the Study Program issues the KP introductory letter" },
{ "id": "Mahasiswa melaksanakan KP dan mengikuti bimbingan", "en": "Students carry out the KP and attend supervision sessions" },
{ "id": "Pembimbing Lapangan memberikan penilaian KP", "en": "The Field Supervisor provides a KP assessment" },
{ "id": "Mahasiswa mengajukan seminar dan Prodi menerbitkan undangan", "en": "Students apply for the seminar and the Study Program issues an invitation" },
{ "id": "Mahasiswa melaksanakan seminar KP", "en": "Students conduct the KP seminar" },
{ "id": "Mahasiswa melakukan revisi laporan akhir KP", "en": "Students revise the final KP report" }
],
"staff_responsible": [
"👤 Staf Administrasi Informatika / Informatics Administrative Staff"
],
"additional_info": {
"id": "Mata Kuliah Kerja Praktik (KP) dapat diambil bersamaan dengan Tugas Akhir 1 (TA1) dan Tugas Akhir 2 (TA2) dalam semester yang sama.",
"en": "The Internship (KP) course can be taken concurrently with Final Project 1 (TA1) and Final Project 2 (TA2) in the same semester."
}
},
{
"tab_id": "ta",
"tab_number": 5,
"category": "akademik",
"icon": "fa-graduation-cap",
"title": {
"id": "SOP Tugas Akhir/Skripsi",
"en": "Final Project / Thesis SOP"
},
"description": {
"id": "Mengatur tata cara pelaksanaan skripsi mulai dari pengajuan judul hingga ujian dan penyelesaian skripsi mahasiswa.",
"en": "Regulates the procedures for thesis implementation, from title submission to examination and completion of the student's thesis."
},
"purpose": {
"id": "SOP ini bertujuan untuk memberikan pedoman kepada mahasiswa dan dosen dalam pelaksanaan skripsi agar proses penyusunan skripsi berjalan tertib, terarah, dan sesuai ketentuan akademik.",
"en": "This SOP aims to provide guidance to students and lecturers in the implementation of the thesis so that the thesis writing process runs in an orderly, directed, and academically compliant manner."
},
"requirements": [
{ "id": "Telah lulus minimal 120 SKS", "en": "Has passed a minimum of 120 credits (SKS)" },
{ "id": "Memiliki IPK minimal 2,00", "en": "Has a minimum GPA of 2.00" },
{ "id": "Telah lulus mata kuliah Penulisan Proposal Tugas Akhir dengan nilai minimal C", "en": "Has passed the Final Project Proposal Writing course with a minimum grade of C" },
{ "id": "Telah mendaftarkan mata kuliah skripsi pada KRS", "en": "Has registered the thesis course in the KRS" },
{ "id": "Telah menyiapkan judul dan proposal skripsi", "en": "Has prepared a thesis title and proposal" },
{ "id": "Memiliki dosen pembimbing skripsi", "en": "Has a thesis supervisor" },
{ "id": "Melengkapi berkas persyaratan", "en": "Completes the required documents" }
],
"procedures": [
{ "id": "Mahasiswa mengajukan judul dan proposal skripsi", "en": "Students submit the thesis title and proposal" },
{ "id": "Program studi menetapkan dosen pembimbing skripsi", "en": "The study program assigns a thesis supervisor" },
{ "id": "Mahasiswa melakukan bimbingan dan penyusunan skripsi", "en": "Students conduct supervision sessions and write the thesis" },
{ "id": "Mahasiswa mengajukan seminar atau sidang skripsi", "en": "Students apply for the thesis seminar or defense" },
{ "id": "Tim penguji dan pembimbing melaksanakan seminar skripsi", "en": "The examination committee and supervisor conduct the thesis seminar" },
{ "id": "Mahasiswa melakukan revisi skripsi", "en": "Students revise the thesis" },
{ "id": "Mahasiswa menyerahkan skripsi final", "en": "Students submit the final thesis" }
],
"staff_responsible": [
"👤 Staf Administrasi Informatika / Informatics Administrative Staff"
],
"additional_info": {
"id": "Mahasiswa diberi waktu maksimal 2 semester untuk menyelesaikan Tugas Akhir. Perpanjangan dapat dilakukan dengan persetujuan tertulis dari Kaprodi dan Pembimbing.",
"en": "Students are given a maximum of 2 semesters to complete the Final Project. Extensions can be made with written approval from the Head of Study Program and the Supervisor."
}
},
{
"tab_id": "praktikum",
"tab_number": 6,
"category": "praktikum",
"icon": "fa-flask",
"title": {
"id": "SOP Praktikum Mata Kuliah",
"en": "Course Practicum SOP"
},
"description": {
"id": "Mengatur tata cara pelaksanaan praktikum mata kuliah agar kegiatan praktikum berjalan tertib, aman, dan sesuai ketentuan pembelajaran.",
"en": "Regulates the procedures for conducting course practicums so that practicum activities run in an orderly, safe, and learning-compliant manner."
},
"purpose": {
"id": "SOP ini bertujuan untuk menjelaskan tata cara pelaksanaan perkuliahan yang memiliki komponen praktikum pada semester berjalan.",
"en": "This SOP aims to explain the procedures for conducting courses that have a practicum component in the current semester."
},
"requirements": [
{ "id": "Terdaftar sebagai peserta mata kuliah yang memiliki komponen praktikum pada semester berjalan", "en": "Registered as a participant in courses with a practicum component in the current semester" },
{ "id": "Mahasiswa membawa perlengkapan praktikum yang diperlukan", "en": "Students bring the required practicum equipment" }
],
"procedures": [
{ "id": "Dosen mata kuliah menuntaskan pembahasan materi yang menjadi dasar praktikum", "en": "The course lecturer completes the discussion of material that forms the basis of the practicum" },
{ "id": "Mahasiswa mengisi daftar hadir dan menerima penjelasan prosedur praktikum", "en": "Students fill in the attendance list and receive an explanation of the practicum procedures" },
{ "id": "Mahasiswa melaksanakan praktikum sesuai modul di bawah pengawasan dosen atau asisten praktikum", "en": "Students carry out the practicum according to the module under the supervision of the lecturer or practicum assistant" },
{ "id": "Mahasiswa menyusun dan mengumpulkan laporan praktikum sesuai tenggat waktu", "en": "Students compile and submit the practicum report by the deadline" },
{ "id": "Dosen atau asisten melakukan penilaian laporan dan hasil praktikum sebagai komponen nilai akhir", "en": "The lecturer or assistant grades the practicum report and results as a component of the final grade" }
],
"additional_info": {
"id": "Mahasiswa wajib mengikuti seluruh ketentuan praktikum dan menjaga ketertiban serta keamanan selama kegiatan praktikum berlangsung.",
"en": "Students must comply with all practicum regulations and maintain order and safety throughout the practicum activities."
}
},
{
"tab_id": "rekrut_asprak",
"tab_number": 7,
"category": "praktikum",
"icon": "fa-user-plus",
"title": {
"id": "SOP Perekrutan Asisten Praktikum",
"en": "Practicum Assistant Recruitment SOP"
},
"description": {
"id": "Prosedur perekrutan Asisten Praktikum (Aslab) mulai dari pendataan kebutuhan mata kuliah, proses seleksi, hingga pengumuman kelulusan.",
"en": "The procedure for recruiting Practicum Assistants (Aslab), from identifying course needs and the selection process to announcing results."
},
"purpose": {
"id": "SOP ini bertujuan sebagai panduan baku untuk menyelenggarakan rekrutmen Asisten Praktikum secara transparan, guna menyaring mahasiswa Informatika UNTAN yang kompeten dan berkomitmen sesuai kualifikasi mata kuliah.",
"en": "This SOP aims to serve as a standard guide for conducting transparent Practicum Assistant recruitment, in order to select competent and committed Informatics UNTAN students according to course qualifications."
},
"requirements": [
{ "id": "Mahasiswa aktif Program Studi Informatika UNTAN", "en": "Active student of the Informatics Study Program at UNTAN" },
{ "id": "Lulus mata kuliah terkait dengan nilai minimal ketentuan Dosen Pengampu", "en": "Passed the relevant course with a minimum grade as per the Lecturer's requirements" },
{ "id": "Berkomitmen mengikuti seluruh jadwal dan kontrak praktikum selama semester berjalan", "en": "Committed to following all practicum schedules and contracts throughout the current semester" }
],
"procedures": [
{ "id": "Dosen mendata kebutuhan kuota dan kualifikasi Asisten Praktikum", "en": "The lecturer records the quota needs and qualifications for Practicum Assistants" },
{ "id": "Dosen mengumumkan pembukaan rekrutmen asisten praktikum", "en": "The lecturer announces the opening of Practicum Assistant recruitment" },
{ "id": "Mahasiswa mendaftarkan diri sesuai persyaratan yang diberikan", "en": "Students register according to the given requirements" },
{ "id": "Dosen melakukan seleksi wawancara kepada calon asisten praktikum", "en": "The lecturer conducts interview selection for practicum assistant candidates" },
{ "id": "Dosen mengumumkan calon yang diterima berdasarkan hasil seleksi", "en": "The lecturer announces the accepted candidates based on the selection results" }
],
"additional_info": {
"id": "Keputusan hasil seleksi bersifat final dan ditetapkan oleh dosen pengampu mata kuliah. Asisten Praktikum yang terpilih wajib mematuhi seluruh ketentuan yang berlaku selama menjalankan tugas sebagai Asisten Praktikum.",
"en": "The selection results are final and determined by the course lecturer. Selected Practicum Assistants must comply with all applicable regulations while performing their duties as Practicum Assistants."
}
},
{
"tab_id": "labkom",
"tab_number": 8,
"category": "praktikum",
"icon": "fa-desktop",
"title": {
"id": "SOP Peminjaman Laboratorium Komputer",
"en": "Computer Laboratory Borrowing SOP"
},
"description": {
"id": "Mengatur tata cara peminjaman dan penggunaan laboratorium komputer untuk mendukung kegiatan praktikum dan pembelajaran.",
"en": "Regulates the procedures for borrowing and using the computer laboratory to support practicum activities and learning."
},
"purpose": {
"id": "SOP ini bertujuan menjelaskan tata cara peminjaman lab komputer dan memastikan peminjaman mendukung kelancaran proses belajar mengajar di Program Studi Informatika Universitas Tanjungpura.",
"en": "This SOP aims to explain the procedures for borrowing the computer lab and to ensure that borrowing supports the smooth teaching and learning process in the Informatics Study Program at Tanjungpura University."
},
"requirements": [
{ "id": "Melampirkan surat permohonan peminjaman laboratorium", "en": "Attach a laboratory borrowing request letter" },
{ "id": "Terdapat dosen atau asisten praktikum yang bertanggung jawab", "en": "A responsible lecturer or practicum assistant is present" },
{ "id": "Pengguna menaati tata tertib dan ketentuan penggunaan perangkat laboratorium", "en": "Users comply with the rules and regulations for using laboratory equipment" }
],
"procedures": [
{ "id": "Dosen atau mahasiswa mengajukan surat permohonan peminjaman laboratorium kepada pengelola", "en": "The lecturer or student submits a laboratory borrowing request letter to the management" },
{ "id": "Pengguna menyampaikan jadwal penggunaan laboratorium", "en": "The user submits the laboratory usage schedule" },
{ "id": "Pengelola menyusun jadwal penggunaan laboratorium", "en": "The management arranges the laboratory usage schedule" },
{ "id": "Laboratorium digunakan sesuai jadwal dan ketentuan yang berlaku", "en": "The laboratory is used according to the schedule and applicable regulations" }
],
"staff_responsible": [
"👤 Staf Laboratorium Informatika / Informatics Laboratory Staff"
],
"additional_info": {
"id": "Pengguna laboratorium wajib menjaga kebersihan, keamanan, dan kondisi perangkat laboratorium selama kegiatan berlangsung.",
"en": "Laboratory users must maintain the cleanliness, safety, and condition of laboratory equipment throughout the activity."
}
}
]
-82
View File
@@ -1,82 +0,0 @@
[
{
"id": 1,
"name": "Tuning Rahayu, S.Hut.",
"role": {
"id": "Staf Administrasi Informatika",
"en": "Informatics Administrative Staff"
},
"image": "assets/images/staff-female.png",
"location": {
"id": "Ruang Administrasi Prodi Informatika",
"en": "Informatics Study Program Administration Room"
},
"contact_consent": true,
"phone": "6282350250892",
"schedule": [
{
"day": {
"id": "Senin Jumat",
"en": "Monday Friday"
},
"time": "07.3016.00 WIB",
"break": {
"id": "istirahat 12.0013.00 WIB",
"en": "break 12.0013.00 WIB"
}
}
],
"tasks": [
{
"id": "Administrasi mahasiswa",
"en": "Student administration"
},
{
"id": "Informasi berkas akademik",
"en": "Academic document information"
}
]
},
{
"id": 2,
"name": "Mahyus Safutra, A.Md.",
"role": {
"id": "Staf Laboratorium Informatika",
"en": "Informatics Laboratory Staff"
},
"image": "assets/images/staff-male.png",
"location": {
"id": "Laboratorium Informatika",
"en": "Informatics Laboratory"
},
"contact_consent": true,
"phone": "895338672533",
"schedule": [
{
"day": {
"id": "Senin Jumat",
"en": "Monday Friday"
},
"time": "07.3016.00 WIB",
"break": {
"id": "istirahat 12.0013.00 WIB",
"en": "break 12.0013.00 WIB"
}
}
],
"tasks": [
{
"id": "Pengelolaan laboratorium",
"en": "Laboratory management"
},
{
"id": "Dukungan praktikum",
"en": "Practicum support"
},
{
"id": "Peminjaman ruangan dan alat",
"en": "Room and equipment borrowing"
}
]
}
]
+39 -176
View File
@@ -1,6 +1,5 @@
<!DOCTYPE html>
<html lang="id" class="scroll-smooth">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -8,12 +7,9 @@
<link rel="stylesheet" href="../../assets/global/output.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Roboto+Condensed:ital,wght@0,100..900;1,100..900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"
rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Roboto+Condensed:ital,wght@0,100..900;1,100..900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="stylesheet" href="./style.css">
<style>
<style>
/* ===== MOBILE NAV STABILITY FIX =====
Prevents logo bar from collapsing/shifting on Android when
address bar hides/shows and triggers viewport reflow.
@@ -297,10 +293,9 @@
}
</style>
</head>
<body class="text-slate-800 antialiased bg-white selection:bg-untan-yellow selection:text-black">
<a href="#main-content" class="skip-link">Langsung ke Konten Utama</a>
<a href="#main-content" class="skip-link" data-i18n="skip_to_content">Langsung ke Konten Utama</a>
<div id="nav-wrapper"
class="fixed top-0 left-0 right-0 z-50 transition-shadow duration-300 shadow-sm border-b border-slate-200 will-change-auto">
@@ -378,7 +373,7 @@
<div class="nav-menu-item relative" data-menu="tentang">
<button
class="nav-menu-btn px-4 py-2.5 text-sm font-bold text-slate-800 hover:text-untan-blue rounded-sm transition-all flex items-center gap-1.5 uppercase tracking-wide">
<span>Tentang Kami</span>
<span data-i18n="nav_about">Tentang Kami</span>
<i class="fas fa-chevron-down text-[10px] opacity-70 transition-transform duration-300"></i>
</button>
</div>
@@ -387,7 +382,7 @@
<div class="nav-menu-item relative" data-menu="program">
<button
class="nav-menu-btn px-4 py-2.5 text-sm font-bold text-slate-800 hover:text-untan-blue rounded-sm transition-all flex items-center gap-1.5 uppercase tracking-wide">
<span>Program</span>
<span data-i18n="nav_program">Program</span>
<i class="fas fa-chevron-down text-[10px] opacity-70 transition-transform duration-300"></i>
</button>
</div>
@@ -396,7 +391,7 @@
<div class="nav-menu-item relative" data-menu="sdm">
<button
class="nav-menu-btn px-4 py-2.5 text-sm font-bold text-slate-800 hover:text-untan-blue rounded-sm transition-all flex items-center gap-1.5 uppercase tracking-wide">
<span>SDM</span>
<span data-i18n="nav_sdm">SDM</span>
<i class="fas fa-chevron-down text-[10px] opacity-70 transition-transform duration-300"></i>
</button>
</div>
@@ -405,7 +400,7 @@
<div class="nav-menu-item relative" data-menu="fasilitas">
<button
class="nav-menu-btn px-4 py-2.5 text-sm font-bold text-slate-800 hover:text-untan-blue rounded-sm transition-all flex items-center gap-1.5 uppercase tracking-wide">
<span>Fasilitas</span>
<span data-i18n="nav_facilities">Fasilitas</span>
<i class="fas fa-chevron-down text-[10px] opacity-70 transition-transform duration-300"></i>
</button>
</div>
@@ -414,7 +409,7 @@
<div class="nav-menu-item relative" data-menu="karya">
<button
class="nav-menu-btn px-4 py-2.5 text-sm font-bold text-slate-800 hover:text-untan-blue rounded-sm transition-all flex items-center gap-1.5 uppercase tracking-wide">
<span>Karya & Agenda</span>
<span data-i18n="nav_works">Karya & Agenda</span>
<i class="fas fa-chevron-down text-[10px] opacity-70 transition-transform duration-300"></i>
</button>
</div>
@@ -423,7 +418,7 @@
<div class="nav-menu-item relative" data-menu="kontak">
<button
class="nav-menu-btn px-4 py-2.5 text-sm font-bold text-slate-800 hover:text-untan-blue rounded-sm transition-all flex items-center gap-1.5 uppercase tracking-wide">
<span>Kontak</span>
<span data-i18n="nav_contact">Kontak</span>
<i class="fas fa-chevron-down text-[10px] opacity-70 transition-transform duration-300"></i>
</button>
</div>
@@ -1144,149 +1139,18 @@
</div>
</div>
<main id="main-content" class="pt-[88px] lg:pt-[160px] min-h-screen bg-white">
<main id="main-content" class="pt-[88px] lg:pt-[160px] min-h-screen bg-white">
<div class="container mx-auto px-4 lg:px-8 py-12">
<div class="mb-8 pb-8 border-b border-slate-200">
<span
class="bg-untan-yellow text-slate-900 text-[10px] font-bold uppercase tracking-widest px-2 py-0.5 inline-block mb-3">Tugas
Halaman Statis</span>
<h1 class="text-3xl lg:text-4xl font-bold text-slate-900 mb-2">Staf Pendukung dan Standar Operasional
Prosedur (SOP)</h1>
<p class="text-slate-500 text-sm">Kelompok 04 &mdash; C4A &nbsp;&middot;&nbsp; Informatika UNTAN 2026
</p>
<span class="bg-untan-yellow text-slate-900 text-[10px] font-bold uppercase tracking-widest px-2 py-0.5 inline-block mb-3">Tugas Halaman Statis</span>
<h1 class="text-3xl lg:text-4xl font-bold text-slate-900 mb-2">Judul Halaman Group 04</h1>
<p class="text-slate-500 text-sm">Kelompok 04 &mdash; C4A &nbsp;&middot;&nbsp; Informatika UNTAN 2026</p>
</div>
<div id="assignment-body" class="prose prose-slate max-w-none">
<!-- ================= START: BAGIAN YANG DIEDIT KELOMPOK ================= -->
<!-- HERO SECTION -->
<section class="c4a-hero reveal">
<div class="c4a-hero-inner">
<div class="c4a-hero-tag" data-i18n="grp_c4a_hero_tag">LAYANAN AKADEMIK &bull; INFORMATIKA UNTAN
</div>
<h1 class="c4a-hero-title" data-i18n-html="grp_c4a_hero_title">
Staf Pendukung &amp; <br />
<span class="highlight-orange">Standar Operasional Prosedur (SOP)</span>
</h1>
<p class="c4a-hero-desc" data-i18n="grp_c4a_hero_desc">Halaman ini menampilkan informasi petugas
layanan akademik serta
prosedur administrasi
mahasiswa
Program Studi Informatika UNTAN.</p>
<div class="hero-stats">
<div class="stat-item">
<div class="stat-number" id="stat-staf">0</div>
<div class="stat-label" data-i18n="grp_c4a_stat_staf">Staf Aktif</div>
</div>
<div class="stat-divider"></div>
<div class="stat-item">
<div class="stat-number" id="stat-sop">0</div>
<div class="stat-label" data-i18n="grp_c4a_stat_sop">Jenis SOP</div>
</div>
</div>
</div>
</section>
<!-- STAFF SECTION -->
<section class="content-section">
<div class="section-header">
<div class="section-tag"><span class="dot"></span> <span data-i18n="grp_c4a_staff_tag">Tim
Kami</span></div>
<h2 class="section-title" data-i18n="grp_c4a_staff_title">Staf Pendukung</h2>
<p class="section-subtitle" data-i18n="grp_c4a_staff_subtitle">Staf yang siap melayani kebutuhan
akademik dan operasional
laboratorium di Prodi Informatika UNTAN.</p>
</div>
<div class="staff-grid" id="staff-container">
<!-- Kartu staf akan diisi secara dinamis melalui main.js berdasarkan data-staff.json -->
</div>
</section>
<!-- SOP SECTION -->
<section class="sop-section">
<div class="section-header">
<div class="section-tag"><span class="dot"></span> <span data-i18n="grp_c4a_sop_tag">Prosedur
Resmi</span></div>
<h2 class="section-title" data-i18n="grp_c4a_sop_title">Standar Operasional Prosedur (SOP)</h2>
<p class="section-subtitle" data-i18n="grp_c4a_sop_subtitle">Standar Operasional Prosedur untuk
berbagai kebutuhan administrasi
akademik mahasiswa. Gunakan pencarian atau filter untuk menemukan SOP yang Anda butuhkan.
</p>
</div>
<!-- SEARCH & FILTER BAR -->
<div class="sop-toolbar reveal">
<div class="sop-search-wrap">
<i class="fas fa-search sop-search-icon"></i>
<input type="search" id="sop-search-input" class="sop-search-input"
data-i18n-placeholder="grp_c4a_sop_search_placeholder"
placeholder="Cari SOP berdasarkan judul..." autocomplete="off" aria-label="Cari SOP" />
<button class="sop-search-clear" id="sop-search-clear" aria-label="Hapus pencarian"
style="display:none;">
<i class="fas fa-times"></i>
</button>
</div>
<div class="sop-filter-chips" id="sop-filter-chips" role="group"
aria-label="Filter kategori SOP">
<button class="sop-chip active" data-filter="semua" id="chip-semua"
data-i18n="grp_c4a_sop_chip_semua">Semua</button>
<button class="sop-chip" data-filter="akademik" id="chip-akademik">
<i class="fas fa-graduation-cap"></i> <span
data-i18n="grp_c4a_sop_chip_akademik">Akademik</span>
</button>
<button class="sop-chip" data-filter="praktikum" id="chip-praktikum">
<i class="fas fa-flask"></i> <span
data-i18n="grp_c4a_sop_chip_praktikum">Praktikum</span>
</button>
</div>
</div>
<!-- RESULT COUNT -->
<div class="sop-result-info" id="sop-result-info" aria-live="polite"></div>
<!-- SOP CARD GRID -->
<div class="sop-card-grid" id="sop-cards-container"></div>
<!-- EMPTY STATE -->
<div class="sop-empty-state" id="sop-empty-state" style="display:none;">
<i class="fas fa-search-minus sop-empty-icon"></i>
<p class="sop-empty-title" data-i18n="grp_c4a_sop_empty_title">SOP tidak ditemukan</p>
<p class="sop-empty-desc" data-i18n="grp_c4a_sop_empty_desc">Coba kata kunci lain atau reset
filter pencarian.</p>
<button class="sop-empty-reset" id="sop-empty-reset">
<i class="fas fa-undo"></i> <span data-i18n="grp_c4a_sop_reset_btn">Reset Pencarian</span>
</button>
</div>
<!-- SOP DETAIL MODAL -->
<div class="sop-modal-overlay" id="sop-modal-overlay" role="dialog" aria-modal="true"
aria-labelledby="sop-modal-title">
<div class="sop-modal-box" id="sop-modal-box">
<button class="sop-modal-close" id="sop-modal-close" aria-label="Tutup detail SOP">
<i class="fas fa-times"></i>
</button>
<div id="sop-modal-content"></div>
</div>
</div>
</section>
<!-- FAQ SECTION -->
<section class="faq-section-wrap">
<div class="faq-section-inner">
<div class="faq-header reveal">
<div class="section-tag"><span class="dot"></span> <span
data-i18n="grp_c4a_faq_tag">Pertanyaan Umum</span></div>
<h2 data-i18n="grp_c4a_faq_title">Pertanyaan yang Sering Diajukan</h2>
<p data-i18n="grp_c4a_faq_subtitle">Temukan jawaban cepat atas pertanyaan umum mahasiswa
seputar layanan akademik prodi.</p>
</div>
<div class="faq-wrap" id="faq-container">
<!-- Item FAQ diisi secara dinamis melalui main.js berdasarkan data-faq.json -->
</div>
</div>
</section>
<script type="module" src="./js/main.js"></script>
<p>Tulis konten halaman kelompok di sini.</p>
<h3>Subjudul</h3>
<p>Gunakan paragraf, list, tabel, gambar, dan elemen konten lainnya.</p>
<!-- ================= END: BAGIAN YANG DIEDIT KELOMPOK ================= -->
</div>
</div>
@@ -1351,8 +1215,8 @@
</div>
<div class="w-full relative z-0 mt-4 pointer-events-none flex justify-center">
<img src="../../assets/global/footer.png" alt="Footer Illustration"
class="w-full h-auto object-cover object-bottom" style="object-position: bottom;">
<img src="../../assets/global/footer.png" alt="Footer Illustration" class="w-full h-auto object-cover object-bottom"
style="object-position: bottom;">
</div>
</footer>
@@ -1448,28 +1312,27 @@
})();
</script>
<script>
(function () {
const mobileBtn = document.getElementById('mobile-menu-button');
const mobileMenu = document.getElementById('mobile-menu');
const closeMobileBtn = document.getElementById('close-mobile-menu');
if (mobileBtn && mobileMenu) {
mobileBtn.addEventListener('click', function () {
const isOpen = !mobileMenu.classList.contains('-translate-x-full');
mobileMenu.classList.toggle('-translate-x-full');
mobileBtn.setAttribute('aria-expanded', isOpen ? 'false' : 'true');
});
}
if (closeMobileBtn && mobileMenu) {
closeMobileBtn.addEventListener('click', function () {
mobileMenu.classList.add('-translate-x-full');
if (mobileBtn) mobileBtn.setAttribute('aria-expanded', 'false');
});
}
})();
</script>
<script>
(function() {
const mobileBtn = document.getElementById('mobile-menu-button');
const mobileMenu = document.getElementById('mobile-menu');
const closeMobileBtn= document.getElementById('close-mobile-menu');
if (mobileBtn && mobileMenu) {
mobileBtn.addEventListener('click', function() {
const isOpen = !mobileMenu.classList.contains('-translate-x-full');
mobileMenu.classList.toggle('-translate-x-full');
mobileBtn.setAttribute('aria-expanded', isOpen ? 'false' : 'true');
});
}
if (closeMobileBtn && mobileMenu) {
closeMobileBtn.addEventListener('click', function() {
mobileMenu.classList.add('-translate-x-full');
if (mobileBtn) mobileBtn.setAttribute('aria-expanded', 'false');
});
}
})();
</script>
<script src="../../assets/global/localization.js"></script>
<script src="../../assets/global/localization.js"></script>
</body>
</html>
-56
View File
@@ -1,56 +0,0 @@
/**
* Berisi fungsi-fungsi untuk mengambil data (Data Fetching).
*/
/**
* Mengambil data staf pendukung dari berkas JSON.
* @returns {Promise<Array<Object>>} Daftar objek data staf
*/
export async function getStaffData() {
try {
const response = await fetch('data/data-staff.json');
if (!response.ok) {
throw new Error('Network response was not ok');
}
return await response.json();
} catch (error) {
console.error('Gagal memuat data staf:', error);
return [];
}
}
/**
* Mengambil data SOP dari berkas JSON.
* @returns {Promise<Array<Object>>} Daftar objek data SOP
*/
export async function getSopData() {
try {
const response = await fetch('data/data-sop.json');
if (!response.ok) {
throw new Error('Network response was not ok');
}
return await response.json();
} catch (error) {
console.error('Gagal memuat data SOP:', error);
return [];
}
}
/**
* Mengambil data FAQ dari berkas JSON.
* @returns {Promise<Array<Object>>} Daftar objek data FAQ
*/
export async function getFaqData() {
try {
const response = await fetch('data/data-faq.json');
if (!response.ok) {
throw new Error('Network response was not ok');
}
return await response.json();
} catch (error) {
console.error('Gagal memuat data FAQ:', error);
return [];
}
}
-71
View File
@@ -1,71 +0,0 @@
/**
* Main Entry Point Kelompok C4A.
* Menghubungkan pengambilan data dengan rendering UI dan interaksi halaman.
*/
import { getStaffData, getSopData, getFaqData } from './data.js';
import {
renderStaffCards,
renderSopCards,
renderFaqSection,
initSopInteraction,
initAnimations,
initFaq,
applySopFilter
} from './ui.js';
// Cache data secara global untuk mendukung rendering UI
let cachedStaffData = [];
let cachedSopData = [];
let cachedFaqData = [];
/**
* Menginisialisasi aplikasi dengan mengambil seluruh data secara pararel,
* merender konten awal, serta mendaftarkan event listener interaksi UI.
* @returns {Promise<void>}
*/
async function initApp() {
try {
const [staffData, sopData, faqData] = await Promise.all([
getStaffData(),
getSopData(),
getFaqData()
]);
cachedStaffData = staffData || [];
cachedSopData = sopData || [];
cachedFaqData = faqData || [];
renderStaffCards(cachedStaffData);
renderSopCards(cachedSopData);
renderFaqSection(cachedFaqData);
initSopInteraction();
initFaq();
// Memberikan sedikit waktu tunda (jeda) agar rendering DOM selesai sebelum memulai animasi.
setTimeout(() => {
initAnimations();
}, 100);
} catch (error) {
console.error('Terjadi kesalahan saat menginisialisasi halaman:', error);
}
}
// Re-render UI saat bahasa berubah (data tetap sama, hanya label UI yang berganti via t())
document.addEventListener('languageChanged', () => {
renderStaffCards(cachedStaffData);
renderSopCards(cachedSopData);
renderFaqSection(cachedFaqData);
initFaq();
applySopFilter();
// Re-inisialisasi observer agar elemen .reveal yang baru di-render mendapat class .visible
setTimeout(() => {
initAnimations();
}, 100);
});
// Jalankan inisialisasi aplikasi saat DOM siap
document.addEventListener('DOMContentLoaded', initApp);
-673
View File
@@ -1,673 +0,0 @@
// --- Helper Fungsi ---
/**
* Helper untuk menerjemahkan string berdasarkan key dari localization.js.
* Menggunakan fallback jika key tidak ditemukan atau localization.js tidak aktif.
*/
function t(key, fallback) {
if (window.t) {
const val = window.t(key);
if (val !== key) return val;
}
return fallback;
}
/**
* Mendapatkan bahasa aktif dari localStorage atau default 'id'.
* @returns {'id'|'en'} Kode bahasa aktif
*/
function getLang() {
return localStorage.getItem('if_untan_lang') || 'id';
}
/**
* Mengambil teks dari field bilingual JSON { id: '...', en: '...' }.
* Jika field bukan objek (string lama / belum diupdate), kembalikan apa adanya.
* @param {string|Object} field - Field data dari JSON
* @returns {string} Teks sesuai bahasa aktif
*/
function loc(field) {
if (!field) return '';
if (typeof field === 'object' && field !== null) {
const lang = getLang();
return field[lang] || field['id'] || '';
}
return field;
}
/**
* Normalisasi nomor telepon ke format standard e.g. 628...
* @param {string} phone - Nomor telepon mentah
* @returns {string} Nomor telepon ternormalisasi
*/
function normalizePhone(phone) {
let digits = String(phone || '').replace(/\D/g, '');
if (digits.startsWith('0')) {
digits = `62${digits.slice(1)}`;
} else if (digits.startsWith('8')) {
digits = `62${digits}`;
}
return digits;
}
/**
* Format nomor telepon untuk tampilan UI
* @param {string} phone - Nomor telepon mentah
* @returns {string} Nomor telepon terformat (e.g. +62 8xx-xxxx-xxxx)
*/
function formatPhoneForDisplay(phone) {
const norm = normalizePhone(phone);
if (!norm) return '-';
return `+${norm.slice(0, 2)} ${norm.slice(2, 5)}-${norm.slice(5, 9)}-${norm.slice(9)}`;
}
/**
* Memvalidasi apakah nomor telepon valid untuk digunakan di WhatsApp.
* @param {string} phone - Nomor telepon mentah
* @returns {boolean} True jika nomor valid (seluler Indonesia 10-15 digit)
*/
function isValidWhatsAppNumber(phone) {
const norm = normalizePhone(phone);
return norm.startsWith('628') && norm.length >= 10 && norm.length <= 15;
}
/**
* Enkripsi nomor telepon menggunakan Base64 untuk keamanan kontak.
* @param {string} phone - Nomor telepon ternormalisasi
* @returns {string} Nomor telepon terenkripsi Base64
*/
function encodePhone(phone) {
return btoa(normalizePhone(phone));
}
/**
* Dekripsi nomor telepon dari Base64.
* @param {string} encoded - Nomor telepon terenkripsi Base64
* @returns {string} Nomor telepon terdekripsi
*/
function decodePhone(encoded) {
try {
return atob(encoded);
} catch (error) {
console.error('Gagal mendekripsi nomor telepon:', error);
return '';
}
}
/**
* Merender daftar staf pendukung ke dalam container HTML.
* @param {Array<Object>} staffs - Array data staf pendukung
*/
export function renderStaffCards(staffs) {
const container = document.getElementById('staff-container');
if (!container) return;
container.innerHTML = '';
if (!staffs || staffs.length === 0) {
container.innerHTML = `<p class="empty-state-msg">${t('grp_c4a_empty_staff', 'Data staf pendukung tidak tersedia saat ini.')}</p>`;
return;
}
staffs.forEach((staff, index) => {
const {
name,
role,
image,
location,
phone,
contact_consent: contactConsent,
schedule,
tasks
} = staff;
const roleText = loc(role);
const locationText = loc(location);
const scheduleHTML = schedule.map(sched =>
`<div class="schedule-item">` +
`<span class="s-day">${loc(sched.day)}</span>` +
`<span class="s-time">${sched.time} <span class="s-break">| ${loc(sched.break)}</span></span>` +
`</div>`
).join('');
const tasksHTML = tasks.map(task => `<li>${loc(task)}</li>`).join('');
const isPhoneValid = isValidWhatsAppNumber(phone);
const encodedPhone = isPhoneValid ? encodePhone(phone) : '';
const displayPhone = isPhoneValid ? formatPhoneForDisplay(phone) : '-';
const hideContactItemClass = contactConsent ? '' : 'is-hidden';
const hideContactActionClass = (contactConsent && isPhoneValid) ? '' : 'is-hidden';
const initial = name ? name.charAt(0).toUpperCase() : '';
const card = document.createElement('div');
card.className = `staff-card reveal reveal-delay-${index + 1}`;
card.innerHTML = `
<div class="staff-card-header">
<div class="staff-avatar">
<span class="staff-initial">${initial}</span>
<img src="${image}" alt="${name}" onerror="this.style.display='none'"/>
</div>
<div>
<div class="staff-name">${name}</div>
<div class="staff-role">${roleText}</div>
</div>
</div>
<div class="staff-card-body">
<div class="staff-info-grid">
<div class="staff-info-item"><span class="icon">📍</span><div><span class="label">${t('grp_c4a_label_location', 'Lokasi')}</span><span class="val">${locationText}</span></div></div>
<div class="staff-info-item">
<span class="icon">🕒</span>
<div style="flex:1">
<span class="label">${t('grp_c4a_label_service_hours', 'Jam Layanan')}</span>
<div class="schedule-list">${scheduleHTML}</div>
</div>
</div>
<div class="staff-info-item staff-contact-item ${hideContactItemClass}"><span class="icon">📞</span><div><span class="label">${t('grp_c4a_label_wa_contact', 'Kontak WhatsApp')}</span><span class="val">${displayPhone}</span></div></div>
</div>
<div class="staff-divider"></div>
<div class="staff-tasks-title">${t('grp_c4a_label_tasks', 'Tugas & Tanggung Jawab')}</div>
<ul class="staff-tasks">${tasksHTML}</ul>
</div>
<div class="staff-card-actions staff-contact-actions ${hideContactActionClass}">
<button class="btn-staff btn-staff-wa" data-wa="${encodedPhone}" aria-label="${t('grp_c4a_label_send_wa', 'Kirim Pesan WA')} ${name}"><svg class="wa-icon" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z"/></svg> ${t('grp_c4a_label_send_wa', 'Kirim Pesan WA')}</button>
</div>
`;
container.appendChild(card);
// Setup WA button click handler dengan dekrips nomor
const waBtn = card.querySelector('[data-wa]');
if (waBtn) {
waBtn.addEventListener('click', () => {
const phoneNum = decodePhone(waBtn.dataset.wa);
if (phoneNum) {
window.open(`https://wa.me/${phoneNum}`, '_blank', 'noopener,noreferrer');
}
});
}
});
}
// ============================================================
// --- Render SOP sebagai Card Grid dengan Search & Filter ---
// ============================================================
const CATEGORY_COLORS = {
akademik: 'sop-cat-akademik',
praktikum: 'sop-cat-praktikum',
};
/**
* Ambil label kategori
* @param {string} category - Kategori SOP ('akademik' atau 'praktikum')
* @returns {string} Label kategori
*/
function getCatLabel(category) {
const map = {
akademik: t('grp_c4a_sop_chip_akademik', 'Akademik'),
praktikum: t('grp_c4a_sop_chip_praktikum', 'Praktikum')
};
return map[category] || t('grp_c4a_sop_chip_akademik', 'Akademik');
}
/**
* Buat HTML untuk satu SOP card di grid.
* @param {Object} sop - Data objek SOP
* @returns {string} String markup HTML kartu SOP
*/
function buildSopCard(sop) {
const {
tab_id: tabId,
category,
icon = 'fa-file-alt',
title,
description,
procedures
} = sop;
const titleText = loc(title);
const descText = loc(description);
const catLabel = getCatLabel(category);
const catClass = CATEGORY_COLORS[category] || '';
return `
<article class="sop-grid-card reveal" data-tab-id="${tabId}" data-category="${category}" data-title="${titleText.toLowerCase()}">
<div class="sgc-header">
<div class="sgc-icon-wrap">
<i class="fas ${icon}"></i>
</div>
<div class="sgc-meta">
<h3 class="sgc-title">${titleText}</h3>
</div>
</div>
<div class="sgc-category-row">
<span class="sgc-category ${catClass}">${catLabel}</span>
</div>
<p class="sgc-desc">${descText}</p>
<div class="sgc-footer">
<div class="sgc-steps">
<i class="fas fa-list-ol"></i>
<span>${procedures.length} ${t('grp_c4a_label_steps', 'Langkah')}</span>
</div>
<button class="sgc-detail-btn" data-tab-id="${tabId}" aria-label="${t('grp_c4a_label_view_detail', 'Lihat Detail')} ${titleText}">
${t('grp_c4a_label_view_detail', 'Lihat Detail')} <i class="fas fa-arrow-right"></i>
</button>
</div>
</article>`;
}
/**
* Buat HTML untuk isi modal detail SOP.
* @param {Object} sop - Data objek SOP
* @returns {string} String markup HTML isi modal
*/
function buildSopModalContent(sop) {
const {
category,
icon = 'fa-file-alt',
title,
description,
purpose,
requirements,
procedures,
staff_responsible: staffResponsible,
additional_info: additionalInfo
} = sop;
const titleText = loc(title);
const descText = loc(description);
const purposeText = loc(purpose);
const additionalInfoText = loc(additionalInfo);
const catLabel = getCatLabel(category);
const catClass = CATEGORY_COLORS[category] || '';
const reqsHTML = requirements.map(req => `<li>${loc(req)}</li>`).join('');
const procsHTML = procedures.map(proc => `<li>${loc(proc)}</li>`).join('');
// staffResponsible bersifat opsional — sembunyikan blok jika tidak ada
const hasStaff = Array.isArray(staffResponsible) && staffResponsible.length > 0;
const staffHTML = hasStaff
? staffResponsible.map(staffName => `<span class="sop-staff-pill">${staffName}</span>`).join('')
: '';
const staffBlockHTML = hasStaff
? `<div class="sop-staff-block">
<div class="sop-block-label">${t('grp_c4a_label_responsible_staff', 'Staf Bertanggung Jawab (Bersifat Opsional)')}</div>
<div class="sop-staff-pills">${staffHTML}</div>
</div>`
: '';
// additionalInfo bersifat opsional — sembunyikan blok jika tidak ada
const additionalInfoHTML = additionalInfoText
? `<div class="sop-info-box">
<span class="iico">️</span>
<p class="sop-info-box-text"><strong>${t('grp_c4a_label_additional_info', 'Informasi Tambahan (Bersifat Opsional):')}</strong> ${additionalInfoText}</p>
</div>`
: '';
return `
<div class="sop-modal-header">
<div class="sop-modal-header-top">
<div class="sop-modal-icon-wrap">
<i class="fas ${icon}"></i>
</div>
<div>
<div class="sop-modal-badges">
<span class="sgc-category ${catClass}">${catLabel}</span>
</div>
<h2 class="sop-modal-title" id="sop-modal-title">${titleText}</h2>
<p class="sop-modal-desc">${descText}</p>
</div>
</div>
</div>
<div class="sop-modal-body">
<div class="sop-block">
<div class="sop-block-label">${t('grp_c4a_label_purpose', 'Tujuan')}</div>
<p class="sop-block-text">${purposeText}</p>
</div>
<div class="sop-two-col">
<div>
<div class="sop-block-label">${t('grp_c4a_label_requirements', 'Persyaratan')}</div>
<ul class="sop-list bullets">${reqsHTML}</ul>
</div>
<div>
<div class="sop-block-label">${t('grp_c4a_label_procedure', 'Alur Prosedur')}</div>
<ul class="sop-list numbered">${procsHTML}</ul>
</div>
</div>
${staffBlockHTML}
${additionalInfoHTML}
</div>`;
}
/**
* Render semua SOP card ke grid.
* @param {Array<Object>} sops - Array objek SOP
*/
export function renderSopCards(sops) {
const container = document.getElementById('sop-cards-container');
if (!container) return;
// simpan data global di element agar bisa diakses filter/search
container._sopData = sops;
// Tampilkan pesan jika data SOP kosong atau gagal dimuat
if (!sops || sops.length === 0) {
container.innerHTML = `<p class="empty-state-msg">${t('grp_c4a_empty_sop', 'Data SOP tidak tersedia saat ini.')}</p>`;
updateResultInfo(0, 0);
return;
}
container.innerHTML = sops.map(buildSopCard).join('');
updateResultInfo(sops.length, sops.length);
}
/** Update teks info jumlah hasil */
function updateResultInfo(shown, total) {
const el = document.getElementById('sop-result-info');
if (!el) return;
if (shown === total) {
const allTemplate = t('grp_c4a_sop_showing_all', 'Menampilkan {total} SOP');
el.textContent = allTemplate.replace('{total}', total);
} else {
const filteredTemplate = t('grp_c4a_sop_showing_filtered', 'Menampilkan {shown} dari {total} SOP');
el.textContent = filteredTemplate.replace('{shown}', shown).replace('{total}', total);
}
}
/**
* Memfilter dan menyaring kartu SOP berdasarkan query pencarian dan chip filter aktif.
*/
export function applySopFilter() {
const container = document.getElementById('sop-cards-container');
if (!container) return;
const query = (document.getElementById('sop-search-input')?.value || '').toLowerCase().trim();
const activeChip = document.querySelector('.sop-chip.active');
const activeFilter = activeChip ? activeChip.dataset.filter : 'semua';
const cards = container.querySelectorAll('.sop-grid-card');
let visibleCount = 0;
cards.forEach(card => {
const title = card.dataset.title || '';
const code = card.dataset.code || '';
const category = card.dataset.category || '';
const matchesSearch = !query || title.includes(query) || code.includes(query);
const matchesFilter = activeFilter === 'semua' || category === activeFilter;
const isVisible = matchesSearch && matchesFilter;
card.style.display = isVisible ? '' : 'none';
if (isVisible) visibleCount++;
});
const emptyState = document.getElementById('sop-empty-state');
if (emptyState) emptyState.style.display = visibleCount === 0 ? 'flex' : 'none';
updateResultInfo(visibleCount, cards.length);
}
/**
* Membuka modal detail untuk SOP tertentu berdasarkan tab_id.
* @param {string} tabId - ID unik tab/SOP yang ingin ditampilkan
*/
function openSopModal(tabId) {
const container = document.getElementById('sop-cards-container');
if (!container || !container._sopData) return;
const sop = container._sopData.find(item => item.tab_id === tabId);
if (!sop) return;
const modalContent = document.getElementById('sop-modal-content');
const modalOverlay = document.getElementById('sop-modal-overlay');
if (!modalContent || !modalOverlay) return;
modalContent.innerHTML = buildSopModalContent(sop);
modalOverlay.classList.add('active');
document.body.style.overflow = 'hidden';
// Gulir isi modal ke paling atas
const modalBox = document.getElementById('sop-modal-box');
if (modalBox) modalBox.scrollTop = 0;
}
/**
* Menutup modal detail SOP yang sedang aktif.
*/
function closeSopModal() {
const modalOverlay = document.getElementById('sop-modal-overlay');
if (modalOverlay) modalOverlay.classList.remove('active');
document.body.style.overflow = '';
}
/**
* Menginisialisasi interaksi halaman SOP (pencarian, filter chip, modal detail).
*/
export function initSopInteraction() {
// Search input
const searchInput = document.getElementById('sop-search-input');
const searchClear = document.getElementById('sop-search-clear');
if (searchInput) {
searchInput.addEventListener('input', () => {
if (searchClear) {
searchClear.style.display = searchInput.value ? 'flex' : 'none';
}
applySopFilter();
});
}
if (searchClear) {
searchClear.addEventListener('click', () => {
if (searchInput) {
searchInput.value = '';
}
searchClear.style.display = 'none';
applySopFilter();
});
}
// Filter chips
document.querySelectorAll('.sop-chip').forEach(chip => {
chip.addEventListener('click', () => {
document.querySelectorAll('.sop-chip').forEach(activeChip => {
activeChip.classList.remove('active');
});
chip.classList.add('active');
applySopFilter();
});
});
// Empty state reset button
const emptyReset = document.getElementById('sop-empty-reset');
if (emptyReset) {
emptyReset.addEventListener('click', () => {
if (searchInput) {
searchInput.value = '';
}
if (searchClear) {
searchClear.style.display = 'none';
}
document.querySelectorAll('.sop-chip').forEach(activeChip => {
activeChip.classList.remove('active');
});
const allChip = document.querySelector('[data-filter="semua"]');
if (allChip) {
allChip.classList.add('active');
}
applySopFilter();
});
}
// Card detail button (event delegation)
const container = document.getElementById('sop-cards-container');
if (container) {
container.addEventListener('click', (event) => {
const btn = event.target.closest('.sgc-detail-btn');
if (btn) {
openSopModal(btn.dataset.tabId);
}
});
}
// Modal close button
const closeBtn = document.getElementById('sop-modal-close');
if (closeBtn) {
closeBtn.addEventListener('click', closeSopModal);
}
// Close on overlay click
const overlay = document.getElementById('sop-modal-overlay');
if (overlay) {
overlay.addEventListener('click', (event) => {
if (event.target === overlay) {
closeSopModal();
}
});
}
// Close on Escape key
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape') {
closeSopModal();
}
});
}
// --- Kompatibilitas Mundur ---
/**
* Stub kompatibilitas mundur untuk renderSopTabs.
* @param {Array<Object>} sops - Array objek SOP
*/
export function renderSopTabs(sops) {
renderSopCards(sops);
}
/**
* Stub kompatibilitas mundur untuk initSopTabsInteraction.
*/
export function initSopTabsInteraction() {
initSopInteraction();
}
// --- Interaksi & Animasi ---
/**
* Menginisialisasi animasi scroll reveal dan counter statistik pada hero section.
*/
export function initAnimations() {
// Scroll Reveal
const revealObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
entry.target.classList.add('visible');
revealObserver.unobserve(entry.target);
});
}, { threshold: 0.1 });
document.querySelectorAll('.reveal').forEach((el) => revealObserver.observe(el));
// Hero Counter
const COUNT_ANIMATION_DURATION_MS = 800;
/**
* Menjalankan animasi hitung angka (counter)
* @param {Element} targetElement - Elemen target angka
* @param {number} targetValue - Nilai akhir counter
*/
function animateCount(targetElement, targetValue) {
if (!targetElement) return;
const stepValue = targetValue / (COUNT_ANIMATION_DURATION_MS / 16);
let currentValue = 0;
const timerId = setInterval(() => {
currentValue += stepValue;
if (currentValue >= targetValue) {
currentValue = targetValue;
clearInterval(timerId);
}
targetElement.textContent = Math.floor(currentValue).toString();
}, 16);
}
const staffCounter = document.getElementById('stat-staf');
const sopCounter = document.getElementById('stat-sop');
// Hitung jumlah data yang berhasil di-render (tidak termasuk template)
const totalStaffCards = document.querySelectorAll('.staff-card').length;
const totalSopCards = document.querySelectorAll('.sop-grid-card').length;
animateCount(staffCounter, totalStaffCards);
animateCount(sopCounter, totalSopCards);
}
// --- FAQ Accordion ---
/**
* Membuka item FAQ yang diklik; menutup item lain yang sebelumnya
* terbuka (perilaku accordion satu-terbuka-sekaligus).
* @param {Element} questionEl - Elemen .faq-question yang diklik
*/
function toggleFaq(questionEl) {
const item = questionEl.closest('.faq-item');
if (!item) return;
const isOpen = item.classList.contains('open');
// Tutup semua item terlebih dahulu
document.querySelectorAll('.faq-item').forEach(faqItem => {
faqItem.classList.remove('open');
});
// Buka item yang diklik hanya jika sebelumnya tertutup
if (!isOpen) {
item.classList.add('open');
}
}
/**
* Menginisialisasi event listener klik untuk setiap accordion FAQ.
*/
export function initFaq() {
document.querySelectorAll('.faq-question').forEach(questionEl => {
questionEl.addEventListener('click', () => toggleFaq(questionEl));
});
}
// --- Render FAQ ---
/**
* Merender section FAQ secara dinamis dari data-faq.json.
* Diinjeksikan ke dalam #faq-container di index.html.
* @param {Array<Object>} faqs - Array objek FAQ dari data-faq.json
*/
export function renderFaqSection(faqs) {
const container = document.getElementById('faq-container');
if (!container) return;
if (!faqs || faqs.length === 0) {
container.innerHTML = `<p class="empty-state-msg">${t('grp_c4a_empty_faq', 'Data FAQ tidak tersedia saat ini.')}</p>`;
return;
}
const delayClasses = ['reveal-delay-1', 'reveal-delay-2', 'reveal-delay-3'];
const itemsHTML = faqs.map((faq, index) => {
const delayClass = delayClasses[index] || '';
return `
<div class="faq-item reveal ${delayClass}">
<div class="faq-question">
<span class="faq-question-text">${loc(faq.question)}</span>
<div class="faq-icon">+</div>
</div>
<div class="faq-answer">
<div class="faq-answer-inner">${loc(faq.answer)}</div>
</div>
</div>`;
}).join('');
container.innerHTML = itemsHTML;
}
+2 -1487
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,3 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.375 1.25C3.71196 1.25 3.07607 1.51339 2.60723 1.98223C2.13839 2.45107 1.875 3.08696 1.875 3.75V11.25C1.875 11.913 2.13839 12.5489 2.60723 13.0178C3.07607 13.4866 3.71196 13.75 4.375 13.75H13.125V1.25H4.375ZM6.875 3.125H11.25V4.375H6.875V3.125ZM3.125 11.25C3.125 10.9185 3.2567 10.6005 3.49112 10.3661C3.72554 10.1317 4.04348 10 4.375 10H11.875V12.5H4.375C4.04348 12.5 3.72554 12.3683 3.49112 12.1339C3.2567 11.8995 3.125 11.5815 3.125 11.25Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 573 B

@@ -0,0 +1,4 @@
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.448 0.105966C15.3089 0.0362835 15.1555 0 15 0C14.8445 0 14.6911 0.0362835 14.552 0.105966L2.552 6.10597L3.448 7.89397L15 2.11997L26.552 7.89597L27.448 6.10797L15.448 0.105966ZM28 12H30V9.99997H0V12H2V28H0V30H8V16H18V30H30V28H28V12Z" fill="black"/>
<path d="M16 30V18H10V30H16Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 409 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.7498 10H11.3332V11.3333H12.7498M12.7498 7.33333H11.3332V8.66667H12.7498M14.1665 12.6667H8.49984V11.3333H9.9165V10H8.49984V8.66667H9.9165V7.33333H8.49984V6H14.1665M7.08317 4.66667H5.6665V3.33333H7.08317M7.08317 7.33333H5.6665V6H7.08317M7.08317 10H5.6665V8.66667H7.08317M7.08317 12.6667H5.6665V11.3333H7.08317M4.24984 4.66667H2.83317V3.33333H4.24984M4.24984 7.33333H2.83317V6H4.24984M4.24984 10H2.83317V8.66667H4.24984M4.24984 12.6667H2.83317V11.3333H4.24984M8.49984 4.66667V2H1.4165V14H15.5832V4.66667H8.49984Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 642 B

@@ -0,0 +1,3 @@
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 0.53125C3.858 0.53125 0.5 4.09912 0.5 8.5C0.5 12.9009 3.858 16.4688 8 16.4688C12.142 16.4688 15.5 12.9009 15.5 8.5C15.5 4.09912 12.142 0.53125 8 0.53125ZM8 15.9375C4.14025 15.9375 1 12.601 1 8.5H15C15 12.601 11.8597 15.9375 8 15.9375Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 366 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 1.33337C10.3536 1.33337 10.6928 1.47385 10.9428 1.7239C11.1929 1.97395 11.3333 2.31309 11.3333 2.66671V6.00004H12.6667C13.0203 6.00004 13.3594 6.14052 13.6095 6.39056C13.8595 6.64061 14 6.97975 14 7.33337V13.9334C14 14.0297 13.981 14.125 13.9442 14.214C13.9073 14.303 13.8533 14.3838 13.7852 14.4519C13.7171 14.52 13.6363 14.574 13.5473 14.6109C13.4583 14.6477 13.363 14.6667 13.2667 14.6667H2.73333C2.63703 14.6667 2.54167 14.6477 2.4527 14.6109C2.36373 14.574 2.28288 14.52 2.21479 14.4519C2.14669 14.3838 2.09268 14.303 2.05582 14.214C2.01897 14.125 2 14.0297 2 13.9334V2.66671C2 2.31309 2.14048 1.97395 2.39052 1.7239C2.64057 1.47385 2.97971 1.33337 3.33333 1.33337H10ZM10 2.66671H3.33333V13.3334H4.66667V11.3334C4.66667 10.8029 4.87738 10.2942 5.25245 9.91916C5.62753 9.54409 6.13623 9.33337 6.66667 9.33337C7.1971 9.33337 7.70581 9.54409 8.08088 9.91916C8.45595 10.2942 8.66667 10.8029 8.66667 11.3334V13.3334H10V2.66671ZM12.6667 7.33337H11.3333V13.3334H12.6667V7.33337ZM6.66667 10.6667C6.50338 10.6667 6.34578 10.7267 6.22375 10.8352C6.10173 10.9437 6.02377 11.0932 6.00467 11.2554L6 11.3334V13.3334H7.33333V11.3334C7.33333 11.1566 7.2631 10.987 7.13807 10.862C7.01305 10.7369 6.84348 10.6667 6.66667 10.6667ZM6.66667 4.00004C6.84348 4.00004 7.01305 4.07028 7.13807 4.1953C7.2631 4.32033 7.33333 4.4899 7.33333 4.66671V5.33337H8C8.17681 5.33337 8.34638 5.40361 8.4714 5.52864C8.59643 5.65366 8.66667 5.82323 8.66667 6.00004C8.66667 6.17685 8.59643 6.34642 8.4714 6.47144C8.34638 6.59647 8.17681 6.66671 8 6.66671H7.33333V7.33337C7.33333 7.51018 7.2631 7.67975 7.13807 7.80478C7.01305 7.9298 6.84348 8.00004 6.66667 8.00004C6.48986 8.00004 6.32029 7.9298 6.19526 7.80478C6.07024 7.67975 6 7.51018 6 7.33337V6.66671H5.33333C5.15652 6.66671 4.98695 6.59647 4.86193 6.47144C4.7369 6.34642 4.66667 6.17685 4.66667 6.00004C4.66667 5.82323 4.7369 5.65366 4.86193 5.52864C4.98695 5.40361 5.15652 5.33337 5.33333 5.33337H6V4.66671C6 4.4899 6.07024 4.32033 6.19526 4.1953C6.32029 4.07028 6.48986 4.00004 6.66667 4.00004Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.75 5.625L7.5 0.625L1.25 5.625V6.875H2.5V13.125H5.625V10.625C5.625 10.1277 5.82254 9.65081 6.17417 9.29917C6.52581 8.94754 7.00272 8.75 7.5 8.75C7.99728 8.75 8.47419 8.94754 8.82583 9.29917C9.17746 9.65081 9.375 10.1277 9.375 10.625V13.125H12.5V6.875H13.75V5.625Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 395 B

@@ -0,0 +1,3 @@
<svg width="29" height="29" viewBox="0 0 29 29" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.2352 0.5V13.4231L27.735 23.2661C28.149 23.7428 28.407 24.316 28.4792 24.9199C28.5514 25.5238 28.435 26.134 28.1433 26.6805C27.8516 27.227 27.3964 27.6875 26.83 28.0094C26.2636 28.3312 25.609 28.5013 24.9412 28.5H4.05878C3.39099 28.5013 2.73635 28.3312 2.16998 28.0094C1.6036 27.6875 1.14845 27.227 0.856745 26.6805C0.565044 26.134 0.448617 25.5238 0.520825 24.9199C0.593034 24.316 0.850952 23.7428 1.26498 23.2661L9.76475 13.4231V0.5M6.21332 0.5H22.7867" stroke="black" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 635 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.55342 5.86L5.33342 6.304V8.66667H4.00009V5.36667H4.01009L7.52209 4.088C7.68475 4.026 7.86209 3.99467 8.04342 4.00067C8.40565 4.01009 8.75599 4.13194 9.04589 4.34934C9.33579 4.56673 9.5509 4.86891 9.66142 5.214C9.78542 5.60289 9.89875 5.86533 10.0014 6.00133C10.3119 6.41489 10.7144 6.75054 11.177 6.98172C11.6396 7.2129 12.1496 7.33328 12.6668 7.33333V8.66667C11.9783 8.66723 11.2983 8.51525 10.6756 8.22165C10.0529 7.92806 9.50301 7.50014 9.06542 6.96867L8.67809 9.16667L10.0001 10.4467V15.3333H8.66675V11.3427L7.30009 10.018L6.66875 12.8833L2.07275 12.0733L2.30475 10.76L5.58742 11.3387L6.55342 5.86ZM9.00009 3.66667C8.64647 3.66667 8.30733 3.52619 8.05728 3.27614C7.80723 3.02609 7.66675 2.68696 7.66675 2.33333C7.66675 1.97971 7.80723 1.64057 8.05728 1.39052C8.30733 1.14048 8.64647 1 9.00009 1C9.35371 1 9.69285 1.14048 9.9429 1.39052C10.1929 1.64057 10.3334 1.97971 10.3334 2.33333C10.3334 2.68696 10.1929 3.02609 9.9429 3.27614C9.69285 3.52619 9.35371 3.66667 9.00009 3.66667Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,3 @@
<svg width="35" height="35" viewBox="0 0 35 35" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.6877 8.00625C19.4492 8.24564 19.166 8.43572 18.8541 8.56564C18.5422 8.69557 18.2078 8.76279 17.8699 8.76346C17.1875 8.76483 16.5326 8.49508 16.0491 8.01354C15.5657 7.53201 15.2933 6.87814 15.2919 6.19578C15.2905 5.51342 15.5603 4.85847 16.0418 4.375M17.5002 8.75V13.125M17.5002 13.125C19.4678 15.3764 21.8178 17.2624 24.4418 18.6958C27.8252 20.5333 29.1668 22.3563 29.1668 25.2729C29.0721 27.1412 28.5746 28.9669 27.7085 30.625H7.29183C6.4257 28.9669 5.92823 27.1412 5.8335 25.2729C5.8335 22.4146 7.17516 20.5333 10.5585 18.6958C13.1825 17.2624 15.5326 15.3764 17.5002 13.125Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 775 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 749 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 488 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 816 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 984 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 910 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 919 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 844 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 551 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 958 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

File diff suppressed because it is too large Load Diff
+270
View File
@@ -0,0 +1,270 @@
(function () {
const dict = {
id: {
// --- Hero Section ---
skp_hero_desc: "Kuliah di kampus unggulan Kalimantan Barat dengan fasilitas lengkap, lingkungan nyaman dan kehidupan mahasiswa yang terjangkau",
// --- About Section ---
skp_about_eyebrow: "Tentang Untan",
skp_about_title: "Kampus Unggul di Kalimantan Barat",
skp_about_desc_1: "Universitas Tanjungpura merupakan perguruan tinggi negeri terkemuka di Kalimantan Barat yang berkomitmen menghadirkan pendidikan berkualitas.",
skp_about_desc_2: "UNTAN terus mendorong pengembangan riset, inovasi, dan kolaborasi di berbagai bidang.",
skp_about_desc_3: "Didukung lingkungan belajar yang nyaman, mahasiswa memiliki ruang untuk berkembang secara akademik maupun nonakademik.",
skp_about_link_text: "Kunjungi Website Resmi",
// --- Facilities Section ---
skp_fac_eyebrow: "Fasilitas Kampus",
skp_fac_main_title: "Fasilitas Kampus untuk Pengalaman Belajar yang Nyaman",
skp_fac_title_perpus: "Perpustakaan UNTAN",
skp_fac_desc_perpus: "Koleksi literatur digital dan fisik terlengkap dengan ruang baca yang tenang dan modern.",
skp_fac_title_rusunawa: "Rusunawa",
skp_fac_desc_rusunawa: "Hunian mahasiswa yang aman, terjangkau, dan berlokasi strategis di dalam lingkungan kampus.",
skp_fac_title_rs: "Rumah Sakit Untan & Klinik Pratama",
skp_fac_desc_rs: "Layanan kesehatan prima bagi seluruh sivitas akademika dengan tenaga medis profesional.",
skp_fac_title_gkb: "Gedung Kuliah Bersama",
skp_fac_desc_gkb: "Ruang kelas interaktif dengan dukungan teknologi multimedia terkini untuk proses belajar mengajar.",
skp_fac_title_bahasa: "UPT Bahasa",
skp_fac_desc_bahasa: "Pusat pelatihan bahasa asing dan sertifikasi internasional untuk menunjang karir global.",
skp_fac_title_olahraga: "Area Olahraga",
skp_fac_desc_olahraga: "Fasilitas olahraga outdoor dan indoor lengkap untuk menjaga kebugaran dan menyalurkan bakat.",
skp_fac_title_masjid: "Masjid Muhtadin UNTAN",
skp_fac_desc_masjid: "Pusat ibadah yang nyaman dan inklusif bagi sivitas akademika UNTAN dengan suasana religius yang menenangkan.",
skp_fac_title_lab: "Laboraturium Kampus UNTAN",
skp_fac_desc_lab: "Fasilitas laboratorium modern UNTAN yang mendukung riset, praktikum, dan inovasi mahasiswa.",
skp_fac_title_audit: "Auditorium UNTAN",
skp_fac_desc_audit: "Ruang serbaguna representatif UNTAN untuk seminar, acara akademik, dan kegiatan besar kampus.",
// --- Campus Section ---
skp_campus_eyebrow: "Kehidupan Kampus",
skp_campus_title: "Aktivitas Mahasiswa & Organisasi Kampus",
skp_campus_subtitle: "Mahasiswa UNTAN memiliki berbagai kesempatan untuk berkembang melalui organisasi, UKM, komunitas, dan kegiatan ekstrakurikuler.",
skp_campus_cat_org: "Organisasi",
skp_campus_cat_ukm: "UKM",
skp_campus_cat_com: "Komunitas",
skp_campus_dir_bem_title: "BEM UNTAN",
skp_campus_dir_bem_desc: "Badan Eksekutif Mahasiswa tingkat universitas yang menjadi wadah aspirasi dan pergerakan mahasiswa untuk perubahan positif di kampus.",
skp_campus_dir_dpm_title: "DPM UNTAN",
skp_campus_dir_dpm_desc: "Dewan Perwakilan Mahasiswa yang menjalankan fungsi legislasi dan pengawasan terhadap kebijakan-kebijakan organisasi mahasiswa di universitas.",
skp_campus_dir_hmif_title: "HMIF UNTAN",
skp_campus_dir_hmif_desc: "Organisasi mahasiswa di bawah Program Studi Informatika yang menjadi wadah pengembangan akademik, kepemimpinan, dan jejaring bagi anggotanya.",
skp_campus_dir_cafa_title: "CAFATIFA",
skp_campus_dir_cafa_desc: "Organisasi ini menjadi wadah pelayanan, pembinaan iman, dan pengembangan karakter bagi mahasiswa Katolik di fakultas tersebut.",
skp_campus_dir_mapala_title: "MAPALA UNTAN",
skp_campus_dir_mapala_desc: "Wadah pecinta alam mahasiswa UNTAN yang membentuk karakter tangguh, peduli lingkungan, dan berjiwa petualang.",
skp_campus_dir_lisma_title: "LISMA UNTAN",
skp_campus_dir_lisma_desc: "Wadah mahasiswa yang mengembangkan minat literasi, diskusi, dan pemikiran kritis secara kolaboratif.",
skp_campus_dir_lsmi_title: "LSMI UNTAN",
skp_campus_dir_lsmi_desc: "Unit kegiatan mahasiswa yang mewadahi pembinaan keagamaan, dakwah, dan pengembangan spiritual mahasiswa.",
skp_campus_dir_semut_title: "SARANG SEMUT",
skp_campus_dir_semut_desc: "Komunitas mahasiswa yang menumbuhkan budaya literasi, diskusi kritis, dan pemikiran intelektual di lingkungan kampus.",
skp_campus_dir_aiesec_title: "AIESEC",
skp_campus_dir_aiesec_desc: "Organisasi kepemudaan internasional di UNTAN yang mengembangkan kepemimpinan, pengalaman global, dan kolaborasi lintas budaya mahasiswa.",
// --- Pontianak City Section ---
skp_pontianak_eyebrow: "Kota Pontianak",
skp_pontianak_title: "Live In Pontianak",
skp_pontianak_desc: "Pontianak bukan sekadar ibu kota Kalimantan Barat, tetapi tempat yang nyaman untuk tinggal, ramah bagi mahasiswa, dan bersahabat di kantong. Kota ini menawarkan akses fasilitas umum yang lengkap, ragam kuliner yang menggoda, serta suasana belajar yang aktif dan penuh semangat.",
// --- Destination Section ---
skp_dest_eyebrow: "Wisata & Destinasi",
skp_dest_title: "Tempat Menarik di Pontianak",
skp_dest_open_maps: "Buka Peta",
skp_dest_khatulistiwa_name: "Tugu Khatulistiwa",
skp_dest_khatulistiwa_desc: "Ikon kota yang menandakan posisi garis lintang nol derajat di bumi.",
skp_dest_waterfront_name: "Waterfront",
skp_dest_waterfront_desc: "Area pedestrian tepi sungai Kapuas yang populer untuk menikmati senja.",
skp_dest_qubu_name: "Qubu Resort",
skp_dest_qubu_desc: "Kawasan taman wisata terpadu dan resort bintang 4 di Kubu Raya.",
skp_dest_radank_name: "Rumah Radakng",
skp_dest_radank_desc: "Rumah adat suku Dayak dan merupakan simbol harmoni budaya.",
skp_dest_museum_name: "Museum",
skp_dest_museum_desc: "Koleksi Geografika/Geologika berupa peta dan jenis batu-batuan.",
skp_dest_amalzone_name: "Amalzone",
skp_dest_amalzone_desc: "Destinasi wisata alam dan rekreasi keluarga yang populer.",
skp_dest_mangrove_name: "Hutan Mangrove",
skp_dest_mangrove_desc: "Pemandangan alami yang menakjubkan jalur trekking kayu.",
skp_dest_mujahidin_name: "Masjid Mujahidin",
skp_dest_mujahidin_desc: "Masjid terbesar di Provinsi Kalimantan Barat.",
// --- Culinary Section ---
skp_cul_eyebrow: "Cafe & Kuliner",
skp_cul_title: "Kuliner dan Coffee Shop Favorit Mahasiswa",
skp_cul_cat_rm: "Rumah Makan",
skp_cul_cat_cs: "Coffee Shop",
skp_cul_gebuk_desc: "Ayam gepuk pedas gurih meresap, bikin nambah nasi terus.",
skp_cul_sari_desc: "Masakan hangat rumahan menggoda, sekali coba langsung jatuh cinta.",
skp_cul_mom_desc: "Menu rumahan enak dengan porsi besar.",
skp_cul_shine_desc: "Nasi topping kekinian dengan tampilan menarik dan topping yang melimpah.",
skp_cul_melepar_desc: "Ayam crispy gurih pedas favorit mahasiswa dengan ayam renyah pedas menggigit.",
skp_cul_umi_desc: "Ayam geprek pedas murah dan mengenyangkan dengan pedasnya nampol mantap.",
skp_cul_cw_desc: "CW Coffee awalnya berdiri pada tahun 2015 dengan nama Cyber World iCafe, sebuah kafe internet (warnet) berkecepatan tinggi yang buka 24 jam.",
skp_cul_nordu_desc: "NORDU Coffee & Eatery (populer dikenal sebagai Kopi Nomor Dua) menawarkan tempat yang nyaman untuk bekerja (Work From Home) dengan menu kopi dan hidangan yang ramah di kantong.",
skp_cul_kami_desc: "Toko Kami didirikan pada November 2021 oleh seorang pemuda kreatif bernama Yudistira Nusantara. Nama 'Toko Kami' merupakan sebuah akronim dari Toko Kudapan dan Aneka Minuman Indonesia.",
skp_cul_lokale_desc: "Lokale didirikan oleh Asmako pada tahun 2017. Berawal dari kecintaan pemiliknya terhadap racikan kopi, Lokale hadir untuk menciptakan cita rasa kopi modern yang pas dengan lidah masyarakat.",
skp_cul_weng_desc: "Weng Coffee adalah salah satu legenda hidup warung kopi di Kota Khatulistiwa yang sudah berdiri sejak tahun 1970.",
skp_cul_tropic_desc: "Tropic Cafe Pontianak merupakan salah satu pelopor coffee shop modern berskala besar di Pontianak yang mengusung konsep 24 jam nonstop dengan harga super ramah kantong.",
// --- Living Cost Section ---
skp_cost_eyebrow: "Biaya Hidup Mahasiswa",
skp_cost_title: "Perkiraan Biaya Hidup Mahasiswa di Pontianak",
skp_cost_desc: "Perencanaan keuangan yang baik membantu mahasiswa menjalani masa studi dengan lebih nyaman di Pontianak.",
skp_cost_note: "*Data yang didapat bersumber dari survey internal",
skp_cost_period: "per bulan",
skp_cost_type_home: "Hunian",
skp_cost_type_food: "Makan",
skp_cost_type_transport: "Transport",
},
en: {
// --- Hero Section ---
skp_hero_desc: "Study at the leading campus in West Kalimantan with complete facilities, comfortable environment, and affordable student life",
// --- About Section ---
skp_about_eyebrow: "About Untan",
skp_about_title: "A Leading Campus in West Kalimantan",
skp_about_desc_1: "Universitas Tanjungpura is a leading public university in West Kalimantan committed to delivering quality education.",
skp_about_desc_2: "UNTAN continues to drive research development, innovation, and collaboration across various fields.",
skp_about_desc_3: "Supported by a comfortable learning environment, students have the opportunity to grow both academically and non-academically.",
skp_about_link_text: "Visit Official Website",
// --- Facilities Section ---
skp_fac_eyebrow: "Campus Facilities",
skp_fac_main_title: "Campus Facilities for a Comfortable Learning Experience",
skp_fac_title_perpus: "UNTAN Library",
skp_fac_desc_perpus: "The most complete collection of digital and physical literature with a quiet and modern reading room.",
skp_fac_title_rusunawa: "Student Dormitory (Rusunawa)",
skp_fac_desc_rusunawa: "Safe, affordable, and strategically located student housing within the campus environment.",
skp_fac_title_rs: "Untan Hospital & Pratama Clinic",
skp_fac_desc_rs: "Excellent health services for the entire academic community with professional medical staff.",
skp_fac_title_gkb: "Shared Lecture Building (GKB)",
skp_fac_desc_gkb: "Interactive classrooms with the latest multimedia technology support for the teaching and learning process.",
skp_fac_title_bahasa: "Language Center (UPT Bahasa)",
skp_fac_desc_bahasa: "Foreign language training and international certification center to support global careers.",
skp_fac_title_olahraga: "Sports Area",
skp_fac_desc_olahraga: "Complete outdoor and indoor sports facilities to maintain fitness and channel talents.",
skp_fac_title_masjid: "Muhtadin Mosque UNTAN",
skp_fac_desc_masjid: "A comfortable and inclusive place of worship for the UNTAN academic community with a soothing religious atmosphere.",
skp_fac_title_lab: "UNTAN Campus Laboratory",
skp_fac_desc_lab: "Modern UNTAN laboratory facilities that support student research, practicums, and innovation.",
skp_fac_title_audit: "UNTAN Auditorium",
skp_fac_desc_audit: "A representative multi-purpose hall for seminars, academic events, and major campus activities.",
// --- Campus Section ---
skp_campus_eyebrow: "Campus Life",
skp_campus_title: "Student Activities & Campus Organizations",
skp_campus_subtitle: "UNTAN students have various opportunities to grow through organizations, Student Activity Units (UKM), communities, and extracurricular activities.",
skp_campus_cat_org: "Organizations",
skp_campus_cat_ukm: "Student Units",
skp_campus_cat_com: "Communities",
skp_campus_dir_bem_title: "BEM UNTAN",
skp_campus_dir_bem_desc: "The university-level Student Executive Board that acts as a forum for student aspirations and movements for positive campus change.",
skp_campus_dir_dpm_title: "DPM UNTAN",
skp_campus_dir_dpm_desc: "The Student Representative Council that performs legislative and supervisory functions over student organization policies at the university.",
skp_campus_dir_hmif_title: "HMIF UNTAN",
skp_campus_dir_hmif_desc: "A student organization under the Informatics Study Program that serves as a hub for academic development, leadership, and networking for its members.",
skp_campus_dir_cafa_title: "CAFATIFA",
skp_campus_dir_cafa_desc: "This organization provides a forum for service, faith formation, and character building for Catholic students in the faculty.",
skp_campus_dir_mapala_title: "MAPALA UNTAN",
skp_campus_dir_mapala_desc: "A community for UNTAN student nature lovers that shapes a resilient character, environmental awareness, and an adventurous spirit.",
skp_campus_dir_lisma_title: "LISMA UNTAN",
skp_campus_dir_lisma_desc: "A student forum that develops interest in literacy, discussion, and critical thinking collaboratively.",
skp_campus_dir_lsmi_title: "LSMI UNTAN",
skp_campus_dir_lsmi_desc: "A student activity unit that handles religious guidance, Islamic outreach (da'wah), and spiritual development for students.",
skp_campus_dir_semut_title: "SARANG SEMUT",
skp_campus_dir_semut_desc: "A student community that fosters a culture of literacy, critical discussion, and intellectual thought within the campus environment.",
skp_campus_dir_aiesec_title: "AIESEC",
skp_campus_dir_aiesec_desc: "An international youth organization at UNTAN that develops leadership, global experience, and cross-cultural collaboration among students.",
// --- Pontianak City Section ---
skp_pontianak_eyebrow: "Pontianak City",
skp_pontianak_title: "Live In Pontianak",
skp_pontianak_desc: "Pontianak is not just the capital of West Kalimantan, but a comfortable place to live, student-friendly, and very affordable. The city offers complete access to public facilities, a tempting variety of culinary options, and an active, vibrant learning environment.",
// --- Destination Section ---
skp_dest_eyebrow: "Tourism & Destinations",
skp_dest_title: "Interesting Places in Pontianak",
skp_dest_open_maps: "Open Maps",
skp_dest_khatulistiwa_name: "Equator Monument",
skp_dest_khatulistiwa_desc: "The city icon marking the zero-degree latitude position on earth.",
skp_dest_waterfront_name: "Waterfront",
skp_dest_waterfront_desc: "A popular pedestrian area by the Kapuas River to enjoy the sunset.",
skp_dest_qubu_name: "Qubu Resort",
skp_dest_qubu_desc: "An integrated tourism park area and a 4-star resort in Kubu Raya.",
skp_dest_radank_name: "Radakng House",
skp_dest_radank_desc: "The traditional longhouse of the Dayak tribe, a symbol of cultural harmony.",
skp_dest_museum_name: "Museum",
skp_dest_museum_desc: "Geographical and geological collections featuring maps and various types of rocks.",
skp_dest_amalzone_name: "Amalzone",
skp_dest_amalzone_desc: "A popular nature tourism and family recreation destination.",
skp_dest_mangrove_name: "Mangrove Forest",
skp_dest_mangrove_desc: "Stunning natural scenery along a wooden trekking pathway.",
skp_dest_mujahidin_name: "Mujahidin Mosque",
skp_dest_mujahidin_desc: "The largest mosque in the West Kalimantan Province.",
// --- Culinary Section ---
skp_cul_eyebrow: "Cafe & Culinary",
skp_cul_title: "Students' Favorite Culinary and Coffee Shops",
skp_cul_cat_rm: "Eateries",
skp_cul_cat_cs: "Coffee Shops",
skp_cul_gebuk_desc: "Spicy and savory pounded chicken that makes you want to keep adding rice.",
skp_cul_sari_desc: "Tempting home-cooked warmth, fall in love at first bite.",
skp_cul_mom_desc: "Delicious home-style meals with generous portions.",
skp_cul_shine_desc: "Trendy rice bowls with attractive presentation and abundant toppings.",
skp_cul_melepar_desc: "Crispy, savory, and spicy chicken, a student favorite with a crunchy, fiery bite.",
skp_cul_umi_desc: "Cheap, filling, and spicy smashed chicken with an intensely satisfying kick.",
skp_cul_cw_desc: "CW Coffee originally started in 2015 as Cyber World iCafe, a high-speed 24-hour internet cafe.",
skp_cul_nordu_desc: "NORDU Coffee & Eatery (popularly known as Kopi Nomor Dua) offers a comfortable place to work (WFH) with student-friendly coffee and meals.",
skp_cul_kami_desc: "Toko Kami was founded in November 2021 by a creative youth named Yudistira Nusantara. The name is an acronym for Indonesian Snack and Beverage Shop.",
skp_cul_lokale_desc: "Lokale was founded by Asmako in 2017. Rooted in the owner's passion for coffee brewing, Lokale brings modern coffee flavors tailored to the local palate.",
skp_cul_weng_desc: "Weng Coffee is one of the living legends of coffee shops in the Equator City, operating since 1970.",
skp_cul_tropic_desc: "Tropic Cafe Pontianak is a pioneer of large-scale modern coffee shops in Pontianak, featuring a 24/7 non-stop concept with highly affordable prices.",
// --- Living Cost Section ---
skp_cost_eyebrow: "Student Living Cost",
skp_cost_title: "Estimated Living Cost for Students in Pontianak",
skp_cost_desc: "Good financial planning helps students go through their study period more comfortably in Pontianak.",
skp_cost_note: "*Data obtained is sourced from an internal survey",
skp_cost_period: "per month",
skp_cost_type_home: "Housing",
skp_cost_type_food: "Food",
skp_cost_type_transport: "Transport",
},
};
function currentLang() {
if (typeof window.getLang === 'function') {
const l = window.getLang();
if (l === 'id' || l === 'en') return l;
}
try {
const saved = localStorage.getItem('if_untan_lang');
if (saved === 'id' || saved === 'en') return saved;
} catch (e) { /* localStorage unavailable */ }
const nav = (navigator.language || 'id').toLowerCase();
return nav.startsWith('en') ? 'en' : 'id';
}
function skpT(key) {
const lang = currentLang();
return (dict[lang] && dict[lang][key]) || dict.id[key] || null;
}
function applySkp(root) {
const scope = root || document;
scope.querySelectorAll('[data-skp-i18n]').forEach(el => {
const val = skpT(el.getAttribute('data-skp-i18n'));
if (val != null) el.textContent = val;
});
scope.querySelectorAll('[data-skp-i18n-placeholder]').forEach(el => {
const val = skpT(el.getAttribute('data-skp-i18n-placeholder'));
if (val != null) el.placeholder = val;
});
}
window.skpT = skpT;
window.applySkp = applySkp;
document.addEventListener('languageChanged', () => applySkp());
document.addEventListener('DOMContentLoaded', () => applySkp());
})();
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More