diff --git a/.gitignore b/.gitignore index 3bdd52e..26b2088 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,9 @@ node_modules/ dist/ .DS_Store +.env +.env.local +groups/**/.env +groups/**/.env.local +README.md +i18n.js \ No newline at end of file diff --git a/assets/global/localization.js b/assets/global/localization.js index dfcc686..d404144 100644 --- a/assets/global/localization.js +++ b/assets/global/localization.js @@ -6,7 +6,6 @@ const translations = { id: { - // --- Common / Shared --- skip_to_content: "Langsung ke Konten Utama", btn_more: "Selengkapnya", btn_view_all: "Lihat Semua", @@ -17,16 +16,12 @@ const translations = { no_data: "Data tidak ditemukan.", coming_soon: "Segera Hadir", follow_us: "Ikuti Kami", - - // --- Navbar Top --- nav_about: "Tentang Kami", nav_program: "Program", nav_sdm: "SDM", nav_facilities: "Fasilitas", nav_works: "Karya & Agenda", nav_contact: "Kontak", - - // --- Mega Menu: Tentang --- mega_about_title: "Mengenal Informatika UNTAN", mega_about_desc: "Terakreditasi Unggul, mencetak lulusan kompeten di bidang teknologi informasi sejak 1993.", link_history: "Sejarah & Tonggak", @@ -37,8 +32,6 @@ const translations = { link_vision_desc: "Arah dan tujuan pengembangan prodi", link_partnership: "Kerjasama & MoU", link_partnership_desc: "Mitra industri dan akademik", - - // --- Mega Menu: Program --- mega_program_obe: "Kurikulum Berbasis OBE", mega_program_obe_desc: "Outcome-Based Education dengan 4 jalur konsentrasi: AI, Software Engineering, Network, dan Information Systems.", btn_krs_sim: "Simulasi KRS", @@ -53,23 +46,15 @@ const translations = { link_guideline_desc: "Buku panduan akademik", link_statistics: "Informatika Angka", link_statistics_desc: "Statistik & data", - - // --- Mega Menu: SDM --- mega_sdm_title: "Tim Pengajar Profesional", mega_sdm_desc: "30+ dosen berkualifikasi S2/S3 dari universitas terkemuka dalam dan luar negeri.", btn_all_lecturers: "Lihat Semua Dosen", label_expertise: "Kelompok Keahlian", - - // --- SDM: Kelompok Keahlian --- kk_ai: "Kecerdasan Buatan", kk_se: "Rekayasa Perangkat Lunak", kk_network: "Jaringan & Keamanan", kk_is: "Sistem Informasi", - - // --- Navigation --- nav_home: "Beranda", - - // --- Mega Menu: Fasilitas --- mega_lab: "Laboratorium", mega_lab_desc: "8 lab modern dengan peralatan terkini", mega_dc: "Data Center", @@ -77,8 +62,6 @@ const translations = { mega_eresources: "E-Resources", mega_eresources_desc: "Akses jurnal & database ilmiah", mega_hub_desc: "Platform PaaS untuk mahasiswa", - - // --- Mega Menu: Karya & Agenda --- mega_research_title: "Riset Unggulan", mega_sinta_title: "Indeks SINTA Terbaik", mega_sinta_desc: "Publikasi dosen terindeks Scopus, WoS, dan jurnal nasional terakreditasi.", @@ -93,26 +76,18 @@ const translations = { link_seminar_desc: "Event akademik & ilmiah", link_achievements: "Prestasi", link_achievements_desc: "Pencapaian dosen & mahasiswa", - - // --- Mega Menu: Kontak --- mega_contact_title: "Hubungi Kami", link_official_contact: "Kontak Resmi", link_contact_desc: "Direktori kontak staf", link_feedback: "Saran & Masukan", link_feedback_desc: "Kirim feedback Anda", link_social_media: "Media Sosial", - - // --- Hero --- hero_badge: "Unggul & Inovatif", hero_title: "Membentuk Arsitek Masa Depan Digital", hero_desc: "Program Studi Informatika Universitas Tanjungpura mencetak talenta digital yang siap bersaing di era revolusi industri 4.0 dengan kurikulum berbasis kompetensi.", hero_cta_primary: "Jelajahi Program", hero_cta_secondary: "Profil Lengkap", - - // --- Marquee --- marquee_connecting: "Menghubungkan ke server...", - - // --- Badges/Tags --- badge_latest_info: "INFO TERBARU", badge_achievement: "PRESTASI & REKOGNISI", badge_works_ideas: "KARYA & GAGASAN", @@ -123,8 +98,6 @@ const translations = { badge_eresources: "e-Resources Informatika", badge_penelitian: "PENELITIAN DOSEN", badge_pkm: "PENGABDIAN KEPADA MASYARAKAT", - - // --- Section Headers --- section_rankings: "Rankings", section_academic_services: "Layanan Akademik", section_news: "Kabar Informatika", @@ -150,8 +123,6 @@ const translations = { section_penelitian_desc: "Riset dan publikasi ilmiah dari dosen Informatika UNTAN.", section_pkm: "Pengabdian Kepada Masyarakat", section_pkm_desc: "Program pengabdian untuk membantu masyarakat melalui teknologi informasi.", - - // --- CTA Links --- cta_all_rubrics: "Semua Rubrik", cta_all_stories: "Semua Cerita", cta_visit_instagram: "Kunjungi Instagram", @@ -168,8 +139,6 @@ const translations = { cta_all_karya: "Lihat Semua Karya", btn_profile_prodi: "Profil Prodi", btn_research_innovation: "Inovasi Riset", - - // --- Karya & Gagasan --- link_all_karya: "Semua Karya", link_all_karya_desc: "Lihat seluruh karya dalam satu halaman", breadcrumb_karya: "Karya & Gagasan", @@ -180,8 +149,6 @@ const translations = { karya_search_placeholder: "Cari judul karya...", karya_empty: "Belum ada konten karya yang sesuai filter.", btn_load_more: "Muat Lebih Banyak", - - // --- Layanan Akademik Links --- link_calendar: "Kalender Akademik", link_support_staff: "Staf Pendukung", link_scholarship: "Info Beasiswa", @@ -189,8 +156,6 @@ const translations = { link_student_achievement: "Prestasi Mahasiswa", link_alumni: "Alumni & Testimoni", link_kontributor: "Jadi Kontributor", - - // --- Filters --- filter_artikel: "Artikel", filter_pengabdian: "Pengabdian", filter_mahasiswa: "Mahasiswa", @@ -204,16 +169,10 @@ const translations = { filter_all_kbk: "Semua KBK", filter_all_position: "Semua Jabatan", loading_lecturer: "Memuat Data Dosen...", - - // --- App Showcase --- app_coming_soon: "Aplikasi Lainnya", app_coming_soon_sub: "Akan Menyusul", - - // --- Footer --- footer_follow_us: "Ikuti Kami", footer_copyright: "Program Studi Informatika — Universitas Tanjungpura", - - // --- content.html --- breadcrumb_home: "Beranda", breadcrumb_news: "Kabar Informatika", breadcrumb_penelitian: "Penelitian", @@ -231,8 +190,6 @@ const translations = { label_pelaksana: "Pelaksana", label_tahun: "Tahun", label_lokasi: "Lokasi", - - // --- kurikulum.html --- kur_page_title: "Simulasi Studi Berbasis IPS", kur_page_desc1: "Simulasi nyata: IPS Semester lalu menentukan jatah SKS semester depan.", kur_page_desc2: "Jika nilai E, gunakan opsi \"Ulang di Tahun Depan\" (Klik Kanan) untuk memperbaiki.", @@ -275,8 +232,6 @@ const translations = { kur_toast_target_oob: "Maaf, semester tujuan ({semester}) di luar jangkauan simulasi (Maks. Semester 8).", kur_toast_exists: "Mata kuliah ini sudah ada di Semester {semester}.", kur_toast_added: "Mata kuliah ditambahkan ke Semester {semester}.", - - // --- academic-guidelines.html --- ag_badge: "PANDUAN AKADEMIK", ag_desc: "Halaman ini merangkum dokumen pedoman akademik, rencana pembelajaran, SOP, format administrasi, dan referensi jurnal untuk kebutuhan pembelajaran di Informatika UNTAN.", ag_general: "General Academic Guidelines", @@ -288,22 +243,16 @@ const translations = { ag_final_project: "Final Project Guidelines", ag_forms: "Forms / Registration / Administration", ag_journal: "Journal Reference", - - // --- graduate-profile.html --- gp_badge: "PROFIL LULUSAN 2026", gp_desc: "Ringkasan profil lulusan dan Capaian Pembelajaran Lulusan (CPL) untuk kurikulum Informatika UNTAN 2026.", gp_heading: "Profil Lulusan", gp_table_code: "Kode", gp_table_outcome: "Ringkasan Capaian", - - // --- people-based-on-kk.html --- pkk_badge: "DOSEN BERDASARKAN KELOMPOK KEAHLIAN", pkk_kk_ai: "KK Kecerdasan Buatan", pkk_kk_rpl: "KK Rekayasa Perangkat Lunak", pkk_kk_net: "KK Jaringan & Keamanan", pkk_kk_si: "KK Sistem Informasi", - - // --- subject-and-courses.html --- sc_badge: "KURIKULUM & MATA KULIAH", sc_desc: "Ringkasan struktur mata kuliah dan pemetaan CPL pada Kurikulum Informatika UNTAN.", sc_table_cpl: "CPL", @@ -317,8 +266,6 @@ const translations = { sc_stage_specialization: "Peminatan & Integrasi", sc_stage_professional: "Profesional", sc_electives: "Elective Courses", - - // --- ifuntanhub.html --- hub_nav_aria: "Navigasi utama", hub_login_aria: "Login ke dashboard", hub_docs_aria: "Lihat dokumentasi", @@ -389,8 +336,6 @@ const translations = { hub_lab_address: "Lab Komputer Lt. 2,
Gedung Teknik Informatika UNTAN,
Pontianak, Kalimantan Barat.", hub_footer_copyright: "© 2026 JURUSAN INFORMATIKA UNTAN. SEMUA SISTEM SIAP.", hub_footer_tagline: "DIRANCANG UNTUK SARJANA & PEMBANGUN.", - - // --- Luco Chatbot --- luco_greeting: "Hai! Ada yang bisa Luco bantu? 💬", luco_name: "Luco", luco_role: "Asisten Informatika", @@ -405,8 +350,6 @@ const translations = { luco_aria_open: "Buka chat dengan Luco", luco_aria_close: "Tutup chat", luco_aria_send: "Kirim pesan", - - // --- profil.html --- profile_badge: "PROFIL PRODI", profile_title: "Profil Program Studi Informatika", profile_subtitle: "Mengenal lebih dekat visi, misi, tujuan, dan struktur organisasi Program Studi Informatika Universitas Tanjungpura.", @@ -445,8 +388,6 @@ const translations = { org_kk: "Kelompok Keahlian (KK)", org_support_staff: "Staf Pendukung", see_also: "Lihat juga", - - // --- struktur-org.html --- breadcrumb_struktur: "Struktur Organisasi", struktur_badge: "STRUKTUR ORGANISASI", struktur_page_title: "Struktur Organisasi Jurusan", @@ -472,8 +413,6 @@ const translations = { profil_sekprodi: "Sekretaris Program Studi", profil_kepala_lab: "Kepala Laboratorium", profil_koordinator: "Koordinator Akademik", - - // --- fasilitas.html --- fasilitas_page_title: "Fasilitas & Laboratorium", fasilitas_intro: "Program Studi Informatika UNTAN dilengkapi dengan fasilitas laboratorium modern untuk mendukung kegiatan pembelajaran dan penelitian.", lab_network_title: "Laboratorium Jaringan Komputer", @@ -488,8 +427,6 @@ const translations = { lab_multimedia_desc: "Laboratorium untuk pengembangan konten multimedia, game, dan realitas virtual.", lab_head: "Kepala Lab", lab_equipment: "Peralatan Utama", - - // --- riset.html --- nav_research: "Riset", riset_title: "Riset & Pengabdian", riset_subtitle: "Daftar penelitian dan kegiatan pengabdian kepada masyarakat dosen Informatika UNTAN", @@ -503,8 +440,6 @@ const translations = { riset_stat_shown: "Ditampilkan", riset_load_more: "Muat Lebih Banyak", riset_empty: "Tidak ada data yang cocok dengan pencarian.", - - // --- kontributor.html --- kontrib_page_title: "Kirim Karya - Informatika UNTAN", kontrib_hero_badge: "PROGRAM KONTRIBUTOR", kontrib_hero_title: "Bagikan Karya Tulis Anda", @@ -570,6 +505,587 @@ const translations = { kontrib_guideline_4: "Hindari plagiarisme. Karya harus orisinal.", kontrib_guideline_5: "Tim redaksi berhak menyunting konten untuk keperluan editorial.", + // Jellyfish.Corp + grp_jellyfish_hero_title: "Sekilas Informatika", + grp_jellyfish_hero_subtitle: "Universitas Tanjungpura", + grp_jellyfish_hero_desc: "Mencetak talenta digital unggulan dari Kalimantan Barat untuk Indonesia dan dunia. Bergabunglah bersama kami dalam perjalanan membangun masa depan teknologi.", + grp_jellyfish_btn_register: "Daftar Sekarang", + grp_jellyfish_btn_learn: "Pelajari Lebih ↓", + grp_jellyfish_stat_founded: "Tahun Berdirinya", + grp_jellyfish_stat_students: "Mahasiswa Aktif", + + grp_jellyfish_about_badge: "Tentang Prodi", + grp_jellyfish_about_title: "Mengenal Program Studi Informatika UNTAN", + grp_jellyfish_about_intro: "Program Studi Informatika Universitas Tanjungpura hadir sejak 2004 dan merupakan salah satu pusat pengembangan talenta digital di Kalimantan Barat. Seiring berkembangnya era digital, kami terus berinovasi untuk menjadi yang terdepan di bidang teknologi informasi yang terus maju.", + grp_jellyfish_vision_label: "🎯 Visi", + grp_jellyfish_vision_text: "Menjadi program studi unggul, inovatif, dan berdaya saing global, dengan fokus pada informatika cerdas untuk mendukung keberlanjutan dan pemberdayaan masyarakat melalui solusi teknologi berbasis kearifan lokal.", + grp_jellyfish_mission_label: "📋 Misi", + grp_jellyfish_mission_1_title: "Pendidikan OBE Unggul", + grp_jellyfish_mission_1_desc: "Menyelenggarakan pendidikan berbasis informatika cerdas yang unggul dan inovatif melalui pendekatan OBE untuk menghasilkan lulusan yang kompeten, adaptif, beretika, dan berdaya saing global.", + grp_jellyfish_mission_2_title: "Penelitian untuk SDGs", + grp_jellyfish_mission_2_desc: "Mengembangkan penelitian unggul di bidang informatika cerdas yang mendukung pencapaian Tujuan Pembangunan Berkelanjutan (SDGs) dan relevan dengan tantangan lokal maupun global.", + grp_jellyfish_mission_3_title: "Pengabdian Berbasis Kearifan Lokal", + grp_jellyfish_mission_3_desc: "Menerapkan dan mengembangkan solusi teknologi berbasis kearifan lokal dalam kegiatan pengabdian kepada masyarakat untuk memberdayakan komunitas secara inklusif dan berkelanjutan.", + grp_jellyfish_mission_4_title: "Kemitraan Strategis", + grp_jellyfish_mission_4_desc: "Membangun kemitraan strategis dengan industri, pemerintah, dan institusi akademik untuk memperkuat ekosistem riset, inovasi, dan penerapan informatika cerdas yang memberikan dampak nyata.", + grp_jellyfish_mission_5_title: "Budaya Akademik Beretika", + grp_jellyfish_mission_5_desc: "Menumbuhkan budaya akademik yang beretika, kolaboratif, kreatif, dan berorientasi pada pembelajaran sepanjang hayat untuk mempersiapkan SDM unggul menghadapi disrupsi teknologi.", + grp_jellyfish_timeline_title: "Tonggak Sejarah", + + grp_jellyfish_stats_badge: "Data & Statistik", + grp_jellyfish_stats_title: "Informatika UNTAN dalam Angka", + grp_jellyfish_stats_subtitle: "Bukan sekadar angka, ini adalah cerita nyata dari perjalanan panjang Informatika UNTAN.", + + grp_jellyfish_achievement_badge: "Prestasi", + grp_jellyfish_achievement_title: "Pencapaian Membanggakan", + grp_jellyfish_achievement_subtitle: "Dari panggung nasional hingga kolaborasi internasional — Informatika UNTAN hadir, bersaing, dan meninggalkan jejak.", + + grp_jellyfish_studyclub_badge: "Komunitas", + grp_jellyfish_studyclub_title: "Study Club", + grp_jellyfish_studyclub_subtitle: "Study Club hadir sebagai wadah pengembangan kompetensi mahasiswa melalui peminatan yang terstruktur dan berbasis komunitas. Setiap study club dirancang untuk memperdalam keahlian spesifik di bidang informatika.", + + grp_jellyfish_career_badge: "Karir", + grp_jellyfish_career_title: "Masa Depan Lulusan Informatika", + grp_jellyfish_career_headline: "Lulusan Informatika UNTAN bukan hanya pencari kerja — mereka pencipta solusi.", + grp_jellyfish_career_desc: "Lulusan Prodi Informatika UNTAN diserap secara luas oleh industri teknologi, instansi pemerintah, BUMN, dan startup. Dengan bekal kompetensi digital yang kuat, karir cemerlang menanti di berbagai sektor strategis.", + + grp_jellyfish_alumni_badge: "Alumni", + grp_jellyfish_alumni_title: "Kisah Sukses Alumni", + grp_jellyfish_alumni_subtitle: "Mereka adalah bukti nyata bahwa Informatika UNTAN adalah batu loncatan menuju karir gemilang — dari Pontianak, ke panggung dunia.", + + grp_jellyfish_infra_badge: "Infrastruktur", + grp_jellyfish_infra_title: "Infrastruktur dan Fasilitas", + grp_jellyfish_infra_subtitle: "Fasilitas lengkap dan modern untuk mendukung proses belajar, penelitian, dan inovasi mahasiswa.", + + grp_jellyfish_partner_badge: "Kerjasama", + grp_jellyfish_partner_title: "Mitra Strategis Informatika UNTAN", + grp_jellyfish_partner_subtitle: "Dibangun di atas kepercayaan dan kolaborasi dengan perusahaan teknologi, instansi pemerintah, dan lembaga pendidikan terkemuka.", + + grp_jellyfish_cta_badge: "Bergabung Bersama Kami", + grp_jellyfish_cta_title: "Mulai Perjalanan Digital Anda", + grp_jellyfish_cta_highlight: "Bersama Kami", + grp_jellyfish_cta_desc: "Jadilah bagian dari komunitas informatika terbaik di Kalimantan Barat. Daftarkan diri Anda sekarang dan wujudkan impian karir teknologi bersama Informatika UNTAN.", + grp_jellyfish_btn_registration: "Daftar Mahasiswa Baru", + grp_jellyfish_btn_whatsapp: "💬 Chat WhatsApp", + + grp_jellyfish_timeline_2004_title: "Pendirian Program Studi", + grp_jellyfish_timeline_2004_desc: "Penerimaan mahasiswa angkatan pertama diselenggarakan pada tanggal 18 Mei melalui mekanisme seleksi internal dan perpindahan studi dari Program Studi Teknik Elektro.", + grp_jellyfish_timeline_2005_title: "SNMPTN Pertama", + grp_jellyfish_timeline_2005_desc: "Penerimaan mahasiswa baru mulai diintegrasikan secara nasional melalui jalur Seleksi Nasional Masuk Perguruan Tinggi Negeri (SNMPTN).", + grp_jellyfish_timeline_2006_title: "Akreditasi C", + grp_jellyfish_timeline_2006_desc: "Program studi memperoleh peringkat Akreditasi C dari BAN-PT sebagai langkah awal penjaminan mutu akademik secara sistematis.", + grp_jellyfish_timeline_2008_title: "Kelas Malam & SPOTA", + grp_jellyfish_timeline_2008_desc: "Pembukaan program kelas sore/malam serta pengembangan SPOTA (Sistem Pendukung Outline Tugas Akhir) sebagai aplikasi inovatif perdana program studi.", + grp_jellyfish_timeline_2016_title: "Akreditasi B", + grp_jellyfish_timeline_2016_desc: "Peningkatan standar mutu akademik yang ditandai dengan pencapaian peringkat Akreditasi B dari BAN-PT.", + grp_jellyfish_timeline_2017_title: "Nama Resmi Berubah", + grp_jellyfish_timeline_2017_desc: "Perubahan nomenklatur program studi dari Teknik Informatika menjadi Informatika, serta penyesuaian gelar akademik lulusan dari Sarjana Teknik (S.T.) menjadi Sarjana Komputer (S.Kom.).", + grp_jellyfish_timeline_2019_title: "Angkatan PPAPK Terakhir", + grp_jellyfish_timeline_2019_desc: "Penyelenggaraan penerimaan mahasiswa jalur non-reguler (PPAPK) angkatan terakhir sebelum resmi dinonaktifkan.", + grp_jellyfish_timeline_2021_title: "EdoXid Lahir", + grp_jellyfish_timeline_2021_desc: "Peluncuran EdoXid, sistem informasi manajemen sidang tugas akhir berbasis digital guna memfasilitasi kebutuhan akademik selama masa pandemi.", + grp_jellyfish_timeline_2023_title: "Guru Besar Pertama", + grp_jellyfish_timeline_2023_desc: "Pengukuhan Guru Besar pertama Program Studi Informatika UNTAN, yaitu Prof. Dr. Herry Sujaini, S.T., M.T.", + grp_jellyfish_timeline_2025_title: "Kaprodi Ke-5", + grp_jellyfish_timeline_2025_desc: "Pelantikan Ibu Eva Faja Ripanti, S.Kom., M.M.S.I., Ph.D. sebagai Ketua Program Studi Informatika yang kelima.", + + badge_competition: "Kompetisi", + badge_publication: "Publikasi", + badge_community: "Pengabdian Masyarakat", + badge_innovation: "Inovasi Aplikasi", + label_finalist: "Finalis", + label_team_best: "Tim Regional Terbaik", + label_honor: "Honor", + label_participant: "PESERTA", + label_still_active: "💡 Masih aktif", + label_faculty_scale: "🚀 Skala Fakultas", + + grp_jellyfish_prest_compfest_title: "Finalis 10 Besar Competitive Programming Contest (COMPFEST) 17", + grp_jellyfish_prest_compfest_desc: "Berhasil meraih posisi 10 besar nasional pada COMPFEST 17, kompetisi pemrograman yang diselenggarakan oleh Universitas Indonesia (UI).", + grp_jellyfish_prest_gemastik18_title: "Tim Regional Terbaik GEMASTIK XVIII bidang Competitive Programming", + grp_jellyfish_prest_gemastik18_desc: "Berhasil menjadi Tim Regional Terbaik pada GEMASTIK XVIII pada bidang Competitive Programming", + grp_jellyfish_prest_icpc_title: "International Collegiate Programming Contest predikat Honor", + grp_jellyfish_prest_icpc_desc: "Tim competitive programming Informatika UNTAN berhasil lolos ke babak regional ICPC Asia Jakarta 2025.", + grp_jellyfish_prest_gemastik17_title: "Tim Regional Terbaik GEMASTIK XVII bidang Competitive Programming", + grp_jellyfish_prest_gemastik17_desc: "Berhasil menjadi Tim Regional Terbaik pada GEMASTIK XVII pada bidang Competitive Programming", + grp_jellyfish_prest_mtq_title: "LOMBA MTQ TINGKAT PROVINSI KALIMANTAN BARAT PESERTA CABANG TAHFIDZ HADITS GOLONGAN 100 HADIST DENGAN SANAD PUTRI", + grp_jellyfish_prest_mtq_desc: "Berpastisipasi pada MTQ tingkat provinsi Kalimantan Barat cabang Tahfidz Hadits 100 Hadits Bersanad Putri", + grp_jellyfish_prest_crm_title: "Customer Relationship Management (CRM) System pada Peningkatan Layanan Pengguna E-Learning", + grp_jellyfish_prest_crm_desc: "Penulis: EF Ripanti, EE Pratama, F Asrin. Penelitian ini mengkaji penerapan sistem CRM untuk meningkatkan kualitas layanan dan kepuasan pengguna pada platform e-learning.", + grp_jellyfish_prest_dbscan_title: "REKOMENDASI AREA PEMASARAN BAGI PEDAGANG SAYUR KELILING MENGGUNAKAN METODE DBSCAN DAN VISUALISASI HEATMAP", + grp_jellyfish_prest_dbscan_desc: "Penulis: DA Nugraha, IY Sholva, EE Pratama. Penelitian ini memanfaatkan metode DBSCAN dan visualisasi heatmap untuk merekomendasikan area pemasaran yang potensial bagi pedagang sayur keliling.", + grp_jellyfish_prest_kmeans_title: "Data mining untuk pengelompokan saham pada sektor energi dengan metode k-means", + grp_jellyfish_prest_kmeans_desc: "Penulis: AS Sukamto, W Setiawan, EE Pratama. Penelitian ini menerapkan algoritma K-Means untuk mengelompokkan saham sektor energi berdasarkan karakteristik data yang serupa guna mendukung analisis investasi.", + grp_jellyfish_prest_kodingmu_title: "KODINGMU PLATFORM BELAJAR COMPUTATIONAL THINKING BAGI SISWA SMAN 1 MEMPAWAH HILIR", + grp_jellyfish_prest_kodingmu_desc: "Kegiatan pengabdian yang bertujuan meningkatkan kemampuan berpikir komputasional dan pemecahan masalah siswa melalui pelatihan serta pemanfaatan platform pembelajaran Kodingmu berbasis aktivitas pemrograman yang interaktif dan menyenangkan.", + grp_jellyfish_prest_spota_title: "SPOTA (2008)", + grp_jellyfish_prest_spota_desc: "Sistem Pendukung Outline Tugas Akhir — lahir dari ide dosen, dibangun mahasiswa, masih digunakan hingga kini. Inovasi yang melampaui zamannya.", + grp_jellyfish_prest_edoxid_title: "EdoXid (2021)", + grp_jellyfish_prest_edoxid_desc: "Sistem pengajuan sidang digital — hadir menjawab pandemi, kini naik kelas ke skala Fakultas MIPA Universitas Tanjungpura.", + + grp_jellyfish_btn_filter_publikasi: "Publikasi & Pengabdian", + grp_jellyfish_btn_filter_aplikasi: "Aplikasi Unggulan", + grp_jellyfish_mhs_aktif: "Mahasiswa Aktif", + grp_jellyfish_tahun_akademik: "Tahun Akademik 2024/2025", + grp_jellyfish_total_alumni: "Total Alumni", + grp_jellyfish_data_total_alumni: "Data sejak tahun 2022", + grp_jellyfish_bulan_tunggu: "6 Bulan", + grp_jellyfish_waktu_tunggu_kerja: "Waktu Tunggu Kerja", + grp_jellyfish_rata_lulusan: "Rata-rata lulusan terserap", + grp_jellyfish_dosen_tetap: "Dosen Tetap", + grp_jellyfish_desc_dosen: "Berpengalaman dan bersertifikasi di bidangnya masing-masing.", + grp_jellyfish_jurnal_ilmiah: "Jurnal Ilmiah", + grp_jellyfish_jurnal: "400+ Jurnal", + grp_jellyfish_rasio_dosen: "Rasio Dosen : Mahasiswa", + grp_jellyfish_guru_besar: "Guru Besar", + grp_jellyfish_jlh_guru_besar: "1 Guru Besar", + grp_jellyfish_nama_guru_besar: "Prof. Dr. Herry Sujaini, S.T., M.T. — Guru Besar pertama Informatika UNTAN (sejak 2023).", + grp_jellyfish_rasio_standar: "Memenuhi standar BAN-PT", + grp_jellyfish_arise: "Program kerja ini berfokus pada peminatan competitive programming. Mahasiswa dilatih untuk mengasah kemampuan pemecahan masalah dan algoritma melalui kompetisi pemrograman bergengsi tingkat nasional maupun internasional.", + grp_jellyfish_software: "Berfokus pada peminatan software development. Materi yang diajarkan meliputi fundamental dasar web, version control (GitHub), software development life cycle, Entity Relationship Diagram, tech-stack, dan use case.", + grp_jellyfish_minerva: "Berfokus pada peminatan data mining. Mahasiswa akan mendalami teknik penggalian data, analisis statistik, dan penerapannya dalam berbagai domain industri untuk menghasilkan insight yang bernilai.", + grp_jellyfish_join_studyclub: "Bergabung dengan Study Club", + grp_jellyfish_studyclub: "Study club ini terbuka bagi seluruh mahasiswa aktif Prodi Informatika UNTAN. Pilih peminatan yang sesuai dengan minat dan tujuan karirmu.", + grp_jellyfish_study_info: "Info Lebih Lanjut →", + grp_jellyfish_btn_filter_semua: "Semua", + grp_jellyfish_btn_filter_kompetisi: "Kompetisi", + grp_jellyfish_bidang_karir: "Bidang Karir", + + grp_jellyfish_akademisi: "Akademisi / Dosen", + grp_jellyfish_wirausaha: "Wirausaha Digital", + grp_jellyfish_mandiri: "Mandiri, Remote Work", + grp_jellyfish_if_akt1: "Informatika, Angkatan 2005", + grp_jellyfish_if_akt2: "Informatika, Angkatan 2018", + grp_jellyfish_if_akt3: "Informatika, Angkatan 2006", + grp_jellyfish_kerja1: "Dosen UM Pontianak", + grp_jellyfish_alumni_desc1: "Alumni Informatika UNTAN angkatan 2005 yang membangun fondasi kuat di bidang komputasi, melanjutkan studi magister di Marmara University melalui beasiswa Türkiye Bursları, berkarier sebagai dosen di Universitas Muhammadiyah Pontianak, dan kini menempuh doktoral di Universidad de Granada dengan fokus pada kecerdasan buatan untuk sistem transportasi cerdas dan informatika kesehatan.", + grp_jellyfish_alumni_desc2: "Alumni Informatika UNTAN dengan kemampuan kuat dalam pemrograman dan problem solving, melanjutkan studi master di Saarland University Jerman dengan riset kecerdasan buatan bertaraf internasional, lalu berkarier sebagai Software Developer di perusahaan FinTech Travixy Frankfurt, mengembangkan platform pembiayaan yang menggerakkan transaksi bernilai miliaran Euro.", + grp_jellyfish_alumni_desc3: "Alumni Informatika UNTAN angkatan 2006 yang kini berkarier sebagai Product Team di AI Rudder, berbekal pengalaman pembelajaran berbasis proyek selama kuliah, melanjutkan studi S2 di China melalui beasiswa, serta berkontribusi dalam industri kecerdasan buatan dengan perspektif global.", + grp_jellyfish_infra_lab_name: "8 Laboratorium", + grp_jellyfish_infra_lab_desc: "Lab Pemrograman, Jaringan, Multimedia, IoT, AI, Basis Data, Mobile Dev, dan Keamanan Siber — ber-AC, High-Speed PC.", + grp_jellyfish_infra_lab_stat: "8 Lab Aktif & Berteknologi", + grp_jellyfish_infra_dc_name: "Data Center", + grp_jellyfish_infra_dc_desc: "Server on-premise dengan infrastruktur cloud lokal berkapasitas tinggi untuk penelitian dan deployment aplikasi.", + grp_jellyfish_infra_dc_stat: "99.9% Uptime · Redundant", + grp_jellyfish_infra_hub_name: "IFUNTANHUB", + grp_jellyfish_infra_hub_desc: "Platform PaaS eksklusif mahasiswa untuk deploy aplikasi, hosting proyek, akses GPU computing, dan kolaborasi tim.", + grp_jellyfish_infra_hub_stat: "Gratis · GPU Access", + grp_jellyfish_infra_eresources_name: "E-Resources", + grp_jellyfish_infra_eresources_desc: "Akses jurnal internasional Scopus, IEEE Xplore, ACM Digital Library, Springer, dan database ilmiah lainnya.", + grp_jellyfish_infra_eresources_stat: "IEEE · Scopus · ACM", + grp_jellyfish_license_label: "Lisensi Perangkat Lunak Tersedia:", + + grp_jellyfish_labs: "Lab Pemrograman, Jaringan, Multimedia, IoT, AI, Basis Data, Mobile Dev, dan Keamanan Siber — ber-AC, High-Speed PC.", + grp_jellyfish_labs2: "8 Lab Aktif & Berteknologi", + grp_jellyfish_data_center: "Server on-premise dengan infrastruktur cloud lokal berkapasitas tinggi untuk penelitian dan deployment aplikasi.", + grp_jellyfish_ifuntanhub: "Platform PaaS eksklusif mahasiswa untuk deploy aplikasi, hosting proyek, akses GPU computing, dan kolaborasi tim.", + grp_jellyfish_free: "Gratis · GPU Access", + grp_jellyfish_resources: "Akses jurnal internasional Scopus, IEEE Xplore, ACM Digital Library, Springer, dan database ilmiah lainnya.", + grp_jellyfish_license: "Lisensi Perangkat Lunak Tersedia:", + + grp_jellyfish_industri: "Industri & Swasta", + grp_jellyfish_pemerintah: "Instansi Pemerintah", + grp_jellyfish_perguruan_tinggi: "Perguruan Tinggi & Lembaga Riset", + + grp_jellyfish_info_kontak: "Informasi Kontak", + grp_jellyfish_alamat: "Alamat", + grp_jellyfish_tanggal: "Senin – Jumat, 08.00 – 16.00 WIB", + grp_jellyfish_telepon: "Telepon / Fax", + + grp_limaem_hero_badge: "KOMUNITAS & KEPEMIMPINAN", + grp_limaem_hero_title: "Kehidupan Mahasiswa & Himpunan (HMIF)", + grp_limaem_hero_desc: "Membangun ekosistem kolaboratif di mana teknologi bertemu dengan kreativitas, membentuk calon pemimpin industri masa depan.", + grp_limaem_hero_btn: "Lihat Struktur Kepengurusan", + + grp_limaem_about_title: "Ruang Tumbuh Bagi Mahasiswa Informatika", + grp_limaem_about_p1: "Himpunan Mahasiswa Informatika (HMIF) Universitas Tanjungpura bukan sekadar organisasi, melainkan rumah bagi para pemimpin teknologi. Kami percaya bahwa pendidikan formal adalah fondasi, namun komunitas adalah akselerator.", + grp_limaem_about_p2: "Melalui berbagai inisiatif, kami menjembatani kesenjangan antara kurikulum akademik dan kebutuhan industri, sambil tetap menjaga semangat kekeluargaan dan integritas ilmiah.", + grp_limaem_about_stat_students: "Mahasiswa Aktif", + grp_limaem_about_stat_partners: "MITRA", + grp_limaem_about_badge: "Inklusivitas & Kolaborasi", + + grp_limaem_partners_title: "Partner dan Kolaborator", + + grp_limaem_divisi_title: "Divisi Kepengurusan HMIF", + grp_limaem_divisi_desc: "Struktur organisasi yang dirancang untuk efisiensi dan pengembangan kapasitas anggota di berbagai bidang strategis.", + grp_limaem_divisi_b1_label: "Bidang 1", + grp_limaem_divisi_b1_title: "Pengembangan Sumber Daya Mahasiswa dan Kaderisasi", + grp_limaem_divisi_b1_desc: "Divisi ini menyelenggarakan kaderisasi terstruktur, mentoring berkala, dan pelatihan kepemimpinan serta soft-skill. Program kerja mencakup onboarding anggota baru, workshop kepemimpinan, proyek kolaboratif, dan kegiatan pengabdian yang melatih tanggung jawab sosial. Fokusnya adalah menyiapkan kader yang kompeten dan menjaga regenerasi organisasi.", + grp_limaem_divisi_b2_label: "Bidang 2", + grp_limaem_divisi_b2_title: "Media Komunikasi dan Konten Digital", + grp_limaem_divisi_b2_desc: "Divisi ini mengelola strategi komunikasi dan produksi konten HMIF, termasuk perencanaan editorial, manajemen media sosial, serta dokumentasi acara. Program kerjanya meliputi pembuatan materi promosi, publikasi kegiatan, dan penguatan identitas digital agar informasi organisasi tersampaikan konsisten dan menarik.", + grp_limaem_divisi_b3_label: "Bidang 3", + grp_limaem_divisi_b3_title: "Inovasi dan Kompetisi Teknologi", + grp_limaem_divisi_b3_desc: "Divisi ini memfasilitasi persiapan lomba dan pengembangan proyek teknis melalui coaching clinic, bootcamp berbasis proyek, serta study club untuk Competitive Programming, Software Development, dan Data/ML. Kegiatan termasuk seleksi internal, bimbingan kompetitif, dan inisiatif digitalisasi seperti pengembangan website dan dashboard untuk mendukung ekosistem inovasi HMIF.", + grp_limaem_divisi_b4_label: "Bidang 4", + grp_limaem_divisi_b4_title: "Pengembangan Organisasi", + grp_limaem_divisi_b4_desc: "Divisi ini memperkuat tata kelola dan kapabilitas internal melalui pelatihan pengurus, penyusunan SOP sederhana, dan evaluasi proses kerja. Program kerjanya mencakup pengembangan budaya organisasi, peningkatan efektivitas koordinasi, dan kegiatan peningkatan profesionalisme sehingga operasi HMIF menjadi lebih terstruktur dan efisien.", + grp_limaem_divisi_b5_label: "Bidang 5", + grp_limaem_divisi_b5_title: "Sosial Masyarakat", + grp_limaem_divisi_b5_desc: "Divisi ini merancang dan melaksanakan program pengabdian serta aksi sosial yang berdampak, termasuk pendampingan komunitas dan bantuan kemanusiaan. Kegiatan meliputi identifikasi kebutuhan lapangan, pelaksanaan terstruktur, dan evaluasi dampak untuk memastikan keberlanjutan dan manfaat nyata bagi masyarakat.", + grp_limaem_divisi_b6_label: "Bidang 6", + grp_limaem_divisi_b6_title: "Kewirausahaan dan Kemitraan", + grp_limaem_divisi_b6_desc: "Divisi ini mengembangkan unit usaha organisasi, merancang skema pendanaan kreatif, dan menjalin kemitraan strategis untuk mendukung keberlanjutan program HMIF. Unit kerjanya meliputi riset peluang usaha, penyusunan proposal kemitraan, dan pengelolaan sponsor untuk meningkatkan kemandirian finansial dan akses jaringan profesional.", + + grp_limaem_kegiatan_title: "Kegiatan Unggulan", + grp_limaem_kegiatan_subtitle: "Kegiatan tahunan yang menjadi ikon Informatika UNTAN.", + grp_limaem_kegiatan_c1_title: "Silaturahmi Keluarga Besar Informatika", + grp_limaem_kegiatan_c1_desc: "Kegiatan buka bersama seluruh keluarga Informatika Untan yang selalu diadakan setiap tahunnya.", + grp_limaem_kegiatan_c2_title: "GEMASTIK", + grp_limaem_kegiatan_c2_desc: "Ajang kompetisi IT bergengsi tingkat nasional tahunan dari kementerian. Wadah bagi mahasiswa Informatika UNTAN untuk unjuk gigi di bidang programming, cyber security, data mining, hingga UI/UX.", + grp_limaem_kegiatan_c3_title: "Perayaan 17 Agustus", + grp_limaem_kegiatan_c3_desc: "Momen perayaan Hari Kemerdekaan Indonesia yang diisi dengan berbagai perlombaan menarik. Ajang seru-seruan bersama untuk mempererat rasa nasionalisme dan kekeluargaan mahasiswa Informatika.", + grp_limaem_kegiatan_c4_title: "Bootcamp HMIF", + grp_limaem_kegiatan_c4_desc: "Program intensif pengembangan skill teknis mahasiswa Informatika bersama mentor profesional dari industri.", + grp_limaem_kegiatan_c5_title: "UPC", + grp_limaem_kegiatan_c5_desc: "Kompetisi Pemograman untuk mengasah skill coding mahasiswa Informatika.", + grp_limaem_kegiatan_c6_title: "Study Excursion", + grp_limaem_kegiatan_c6_desc: "Study Excursion menjadi pengalaman berharga bagi mahasiswa semester 5 untuk belajar di luar kampus, memperluas wawasan, mengenal suasana baru, serta menciptakan kenangan berkesan bersama selama perjalanan.", + grp_limaem_kegiatan_c7_title: "INFORMERS", + grp_limaem_kegiatan_c7_desc: "Perayaan ulang tahun Informatika UNTAN, momen kebersamaan dan kebanggaan seluruh keluarga besar Informatika.", + + grp_limaem_sc_title: "STUDY CLUB", + grp_limaem_sc_arise_title: "ARISE", + grp_limaem_sc_arise_desc: "Belajar logika dan algoritma bersama, di mana kalian bisa berkembang dan membuka peluang mengikuti kompetisi pemrograman di berbagai level.", + grp_limaem_sc_minerva_title: "MINERVA", + grp_limaem_sc_minerva_desc: "Gali potensi tersembunyi dari tumpukan data! Di sini kita akan bereksplorasi dengan algoritma Machine Learning, membedah teknik klasifikasi, hingga melatih model prediktif untuk menghasilkan insight yang bermakna.", + grp_limaem_sc_sd_title: "Software Development (Segera Hadir)", + grp_limaem_sc_sd_desc: "Wujudkan idemu jadi produk digital yang nyata! Kita akan ngoding bareng dari merancang frontend yang interaktif, membangun backend yang tangguh, serta mengintegrasikan berbagai API untuk menciptakan aplikasi web masa kini.", + + grp_limaem_galeri_title: "Galeri Keseruan", + + grp_limaem_cta_title: "Ada yang Ingin Ditanyakan Seputar HMIF?", + grp_limaem_cta_desc: "Bergabunglah dengan HMIF untuk memperluas jaringan, mengasah skill, dan menciptakan dampak nyata.", + grp_limaem_cta_btn: "Hubungi Kami", + grp_limaem_cta_info1_label: "Sekretariat", + grp_limaem_cta_info1_val: "Gedung Fakultas Teknik Lt. 2, Jl. Prof. Dr. H. Hadari Nawawi", + grp_limaem_cta_info2_label: "Jam Operasional", + grp_limaem_cta_info2_val: "Senin - Jumat, 08:00 - 17:00 WIB", + grp_limaem_cta_info3_label: "Media Sosial", + grp_limaem_cta_info3_val: "@hmif.untan, YouTube: HMIF UNTAN TV", + ape_breadcrumb_profile: "Profil", + ape_breadcrumb_scholarship: "Beasiswa", + ape_hero_badge: "Program Beasiswa", + ape_hero_title: "Beasiswa Studi &
Penelitian", + ape_hero_subtitle: "Teknik Informatika Universitas Tanjungpura", + ape_hero_desc: "Temukan berbagai peluang beasiswa untuk mendukung perjalanan akademik dan penelitian Anda. Program Studi Informatika UNTAN berkomitmen membantu mahasiswa meraih prestasi terbaik.", + ape_hero_cta: "Lihat Daftar Beasiswa", + ape_stats_total: "Total Mahasiswa Penerima
Beasiswa Semester Ini", + ape_stats_s1: "Total Penerima Beasiswa
Khusus S1", + ape_stats_studi: "Total Penerima Beasiswa
Studi Lanjut", + ape_dir_label: "Daftar Beasiswa", + ape_dir_title: "Daftar Beasiswa", + ape_dir_desc: "Temukan beragam beasiswa dari berbagai sumber resmi yang tersedia untuk mahasiswa aktif, alumni, dan calon mahasiswa Program Studi Informatika UNTAN.", + ape_dir_guide: "Terima Bantuan Beasiswa?", + ape_filter_s1: "Khusus S1", + ape_filter_studi: "Studi Lanjut", + ape_search_placeholder: "Cari Nama Beasiswa...", + ape_testimonial_title: "Apa Kata Mereka?", + ape_testimonial_desc: "Cerita inspiratif dari para penerima beasiswa Program Studi Informatika UNTAN", + ape_cta_title: "Ingin Mengetahui Info Lebih Lanjut?", + ape_cta_desc: "Hubungi admin prodi untuk informasi terbaru tentang beasiswa dan program akademik.", + ape_cta_btn1: "DAFTAR BEASISWA", + ape_cta_btn2: "Hubungi Kami", + grp_habisiniseminar_page_title: "Penerimaan Mahasiswa Baru Informatika UNTAN | Kelompok 02", + grp_habisiniseminar_brand: "Informatika UNTAN Penerimaan Mahasiswa Baru", + grp_habisiniseminar_badge: "Informasi Jalur Seleksi", + grp_habisiniseminar_hero_title_1: "Penerimaan Mahasiswa Baru", + grp_habisiniseminar_hero_title_2: "Informatika UNTAN", + grp_habisiniseminar_hero_description: "Informasi dan jadwal pendaftaran mahasiswa baru melalui jalur SNBP, SNBT, dan Jalur Mandiri UNTAN", + grp_habisiniseminar_meta_group: "Kelompok 02", + grp_habisiniseminar_about_title: "Seleksi Program Sarjana", + grp_habisiniseminar_about_desc: "Program Studi Informatika Universitas Tanjungpura membuka kesempatan bagi putra-putri terbaik bangsa untuk bergabung melalui berbagai jalur seleksi. Kami mencari talenta yang siap menjadi inovator teknologi masa depan.", + grp_habisiniseminar_path_snbp_desc: "Seleksi Nasional Berdasarkan Prestasi", + grp_habisiniseminar_path_snbt_desc: "Seleksi Nasional Berdasarkan Tes", + grp_habisiniseminar_path_smmptn_desc: "Seleksi Mandiri Wilayah Barat", + grp_habisiniseminar_path_mandiri_desc: "Seleksi Mandiri Universitas Tanjungpura", + grp_habisiniseminar_info_jalur_title: "Informasi Jalur Seleksi", + grp_habisiniseminar_sidebar_heading: "Pilih Jalur", + grp_habisiniseminar_sidebar_data_date: "*Data yang digunakan per tanggal Mei 2026", + grp_habisiniseminar_sidebar_link_general: "Informasi Umum", + grp_habisiniseminar_sidebar_link_persyaratan: "Persyaratan", + grp_habisiniseminar_sidebar_link_mekanisme: "Mekanisme", + grp_habisiniseminar_sidebar_link_jadwal: "Jadwal", + grp_habisiniseminar_sidebar_link_biaya: "Biaya Pendaftaran & Pendidikan", + grp_habisiniseminar_sidebar_link_beasiswa: "Beasiswa", + grp_habisiniseminar_snbp_title: "SNBP – Seleksi Nasional Berdasarkan Prestasi", + grp_habisiniseminar_info_snbp: "Seleksi Nasional Berdasarkan Prestasi (SNBP) merupakan jalur penerimaan mahasiswa baru Perguruan Tinggi Negeri (PTN) yang diselenggarakan secara nasional oleh SNPMB. SNBP dilaksanakan melalui mekanisme seleksi berdasarkan hasil belajar di jenjang pendidikan menengah, yang meliputi nilai rapor, prestasi akademik maupun nonakademik, serta kriteria tambahan yang ditetapkan masing-masing PTN. Skema ini bertujuan memberikan kesempatan kepada peserta didik yang memiliki rekam jejak prestasi unggul dan konsisten selama masa studi. SNBP menjadi bagian integral dari upaya pemerintah dalam menciptakan proses seleksi yang objektif, transparan, dan mendorong penguatan pembelajaran di sekolah.", + grp_habisiniseminar_acc_snbp_req_general: "Persyaratan Umum Mendaftar SNBP", + grp_habisiniseminar_acc_snbp_ketentuan: "Ketentuan SNPMB", + grp_habisiniseminar_acc_snbp_req_accepted: "Persyaratan Umum Diterima SNBP", + grp_habisiniseminar_snbp_req_general_body: "", + grp_habisiniseminar_snbp_ketentuan_body: "", + grp_habisiniseminar_snbp_req_accepted_body: "", + grp_habisiniseminar_snbp_mekanisme_text: "

Seleksi Nasional Berdasarkan Prestasi (SNBP) di Universitas Tanjungpura mempertimbangkan performa akademik siswa secara menyeluruh melalui nilai rapor, serta pencapaian prestasi sebagai nilai tambah dalam proses seleksi. Komponen yang dinilai meliputi:

", + grp_habisiniseminar_tl_snbp_1: "Pengumuman kuota sekolah dan masa sanggah", + grp_habisiniseminar_tl_snbp_2: "Registrasi akun dan pengisian PDSS oleh sekolah", + grp_habisiniseminar_tl_snbp_3: "Registrasi akun SNPMB siswa", + grp_habisiniseminar_tl_snbp_4: "Pendaftaran SNBP", + grp_habisiniseminar_tl_snbp_5: "Pengumuman hasil SNBP", + grp_habisiniseminar_tl_snbp_6: "Unduh kartu peserta SNBP", + grp_habisiniseminar_snbp_fee_desc: "UKT dibagi dalam beberapa kategori yang disesuaikan dengan kemampuan ekonomi orang tua mahasiswa dan dibayarkan tiap semester.", + grp_habisiniseminar_fee_highlight_snbp: "Mahasiswa yang diterima melalui SNBP dengan menggunakan KIP Kuliah akan diverifikasi dan berpeluang mendapatkan pembebasan UKT sesuai ketentuan KIP Kuliah dan UNTAN.", + grp_habisiniseminar_snbp_beasiswa_desc: "Para mahasiswa bisa memperoleh beasiswa dari berbagai sumber dengan mekanisme pengajuan sesuai ketentuan lembaga pemberi beasiswa.", + grp_habisiniseminar_snbt_title: "SNBT – Seleksi Nasional Berdasarkan Tes", + grp_habisiniseminar_info_snbt: "Seleksi Nasional Berdasarkan Tes (SNBT) merupakan jalur penerimaan mahasiswa baru di Perguruan Tinggi Negeri (PTN) yang menggunakan ujian terstandar secara nasional. Peserta akan mengikuti serangkaian tes untuk mengukur potensi akademik, kemampuan bernalar, serta kesiapan belajar di perguruan tinggi. Penilaian dalam SNBT sepenuhnya didasarkan pada hasil tes yang dikerjakan peserta, sehingga kelulusan tidak ditentukan oleh faktor seperti kondisi ekonomi, asal sekolah (negeri atau swasta), maupun lokasi daerah, melainkan murni berdasarkan skor UTBK. Dengan demikian, seluruh peserta dinilai menggunakan standar yang sama sehingga proses seleksi berlangsung secara adil dan transparan. SNBT menjadi salah satu instrumen utama SNPMB dalam menjamin akses pendidikan tinggi yang meritokratis, akuntabel, dan inklusif.", + grp_habisiniseminar_acc_snbt_req_general: "Persyaratan Umum Mendaftar SNBT", + grp_habisiniseminar_acc_snbt_utbk: "Ketentuan SNPMB", + grp_habisiniseminar_acc_snbt_req_prodi: "Persyaratan Umum Diterima SNBT", + grp_habisiniseminar_snbt_req_general_body: "", + grp_habisiniseminar_snbt_ketentuan_body: "", + grp_habisiniseminar_snbt_req_accepted_body: "", + grp_habisiniseminar_snbt_mekanisme_full: "

Seleksi Nasional Berdasarkan Tes (SNBT) dilaksanakan melalui Ujian Tulis Berbasis Komputer (UTBK), yaitu tes terstandar untuk mengukur potensi kognitif, penalaran matematika, serta kemampuan literasi dalam bahasa Indonesia dan bahasa Inggris. UTBK bertujuan untuk menilai kemampuan berpikir logis, pemecahan masalah, serta kemampuan belajar yang tinggi sehingga dapat memahami materi yang akan diajarkan pada program studi spesifik. Selain itu, diharapkan terbentuknya kesempatan yang sama kepada semua calon mahasiswa dengan latar belakang ekonomi maupun kualitas pembelajaran sekolah asal yang berbeda.

Penalaran matematika dalam bagian ini bertujuan menilai kemampuan peserta dalam menggunakan konsep dan prosedur matematika untuk menyelesaikan masalah kontekstual, serta mengevaluasi dan merefleksikan informasi yang diperoleh agar dapat digunakan secara efektif dalam kehidupan sehari-hari. Instrumen tes untuk seleksi masuk PTN disusun oleh para ahli di bidang penilaian pendidikan dengan mempertimbangkan aspek teknis pelaksanaan, termasuk alokasi waktu pengerjaan dan tingkat kesulitan soal, sehingga hasil tes dapat mencerminkan kemampuan peserta secara objektif dan terukur.

", + grp_habisiniseminar_tl_snbt_1: "Registrasi Akun SNPMB Siswa", + grp_habisiniseminar_tl_snbt_2: "Pendaftaran SNBT", + grp_habisiniseminar_tl_snbt_3: "Pembayaran Biaya UTBK", + grp_habisiniseminar_tl_snbt_4: "Pelaksanaan UTBK", + grp_habisiniseminar_tl_snbt_5: "Pengumuman hasil SNBT", + grp_habisiniseminar_tl_snbt_6: "Masa unduh sertifikat UTBK", + grp_habisiniseminar_fee_highlight_snbt: "Mahasiswa yang diterima melalui SNBT dengan menggunakan KIP Kuliah akan diverifikasi dan berpeluang mendapatkan pembebasan UKT sesuai ketentuan KIP Kuliah dan UNTAN.", + grp_habisiniseminar_snbt_beasiswa_desc: "Mahasiswa SNBT juga berhak mengajukan berbagai program beasiswa yang tersedia sesuai ketentuan masing-masing penyelenggara.", + grp_habisiniseminar_smmptn_title: "SMMPTN Barat – Seleksi Mandiri Wilayah Barat", + grp_habisiniseminar_info_smmptn: "Seleksi Mandiri Masuk Perguruan Tinggi Negeri Wilayah Barat (SMMPTN-Barat) merupakan jalur seleksi mandiri yang diselenggarakan secara bersama oleh sejumlah perguruan tinggi negeri di wilayah barat Indonesia dengan menggunakan ujian berbasis komputer sebagai dasar penilaian. Jalur ini memberikan kesempatan tambahan bagi lulusan SLTA yang belum berhasil diterima melalui jalur Seleksi Nasional Berdasarkan Prestasi (SNBP) maupun Seleksi Nasional Berdasarkan Tes (SNBT), baik karena keterbatasan kuota maupun kurang tepat dalam memilih program studi dan perguruan tinggi negeri. Selain itu, SMMPTN-Barat juga menjadi alternatif bagi peserta yang belum memiliki akun SNPMB untuk tetap dapat mengikuti seleksi masuk perguruan tinggi negeri.", + grp_habisiniseminar_smmptn_persyaratan_list: "", + grp_habisiniseminar_smmptn_mekanisme_full: "

Mekanisme seleksi SMM PTN-Barat dilaksanakan melalui ujian tertulis berbasis komputer yang dirancang untuk mengukur kemampuan akademik dan kesiapan calon mahasiswa dalam menempuh pendidikan tinggi. Ujian ini menggunakan pendekatan tes terstandar yang berfokus pada kemampuan kognitif, literasi, dan penalaran, sehingga dapat memastikan peserta memiliki kemampuan berpikir kritis, pemecahan masalah, serta kapasitas belajar yang baik. Adapun jenis materi utama dalam ujian ini, sebagai berikut:

", + grp_habisiniseminar_tl_smmptn_1: "Pendaftaran online", + grp_habisiniseminar_tl_smmptn_2: "Pelaksanaan UTBK SMMPTN", + grp_habisiniseminar_tl_smmptn_3: "Pengumuman Hasil Seleksi SMMPTN", + grp_habisiniseminar_smmptn_beasiswa_desc: "Berbagai beasiswa tersedia bagi mahasiswa yang diterima melalui jalur SMMPTN-Barat, dapat diajukan setelah proses penerimaan selesai.", + grp_habisiniseminar_mandiri_title: "Mandiri UNTAN – Seleksi Mandiri Universitas Tanjungpura", + grp_habisiniseminar_info_mandiri: "Seleksi Mandiri UNTAN merupakan jalur penerimaan mahasiswa baru yang diselenggarakan secara langsung oleh Universitas Tanjungpura. Jalur ini menjadi alternatif bagi calon mahasiswa yang ingin melanjutkan pendidikan di UNTAN melalui mekanisme seleksi yang ditetapkan oleh universitas sesuai dengan ketentuan yang berlaku.", + grp_habisiniseminar_mandiri_persyaratan_list: "", + grp_habisiniseminar_mandiri_mekanisme_full: "

Penerimaan mahasiswa baru Program Diploma dan Sarjana melalui jalur Seleksi Mandiri UNTAN didasarkan pada hasil Ujian Tulis Berbasis Komputer (UTBK) dan/atau tes tambahan sesuai dengan program studi yang dipilih. Berikut adalah rincian ujian dan tes tambahan yang dilakukan:

Peserta yang tidak hadir pada saat UTBK dan/atau Tes Tambahan dianggap mengundurkan diri dari proses seleksi Jalur Mandiri UNTAN. Peserta jalur afirmasi dan/atau jalur prestasi dan penelusuran bakat dilakukan verifikasi melalui wawancara.

", + grp_habisiniseminar_tl_mandiri_1: "Pendaftaran Seleksi Mandiri UNTAN", + grp_habisiniseminar_tl_mandiri_2: "Pembuatan PIN", + grp_habisiniseminar_tl_mandiri_3: "Pelaksanaan UTBK Jalur Seleksi Mandiri", + grp_habisiniseminar_tl_mandiri_4: "Pelaksanaan Tes Tambahan", + grp_habisiniseminar_tl_mandiri_5: "Wawancara Kategori Jalur Afirmasi, Prestasi, dan Penelusuran Bakat", + grp_habisiniseminar_tl_mandiri_6: "Pengumuman Hasil Seleksi Mandiri", + grp_habisiniseminar_mandiri_beasiswa_desc: "Mahasiswa jalur Mandiri UNTAN dapat mengakses berbagai program beasiswa yang tersedia sesuai kriteria dan mekanisme pengajuan masing-masing penyelenggara.", + grp_habisiniseminar_section_persyaratan: "Persyaratan", + grp_habisiniseminar_section_mekanisme: "Mekanisme", + grp_habisiniseminar_section_jadwal: "Jadwal Pendaftaran", + grp_habisiniseminar_section_biaya: "Biaya Pendaftaran & Pendidikan", + grp_habisiniseminar_section_beasiswa: "Beasiswa", + grp_habisiniseminar_chart_title: "Grafik Peminat dan Daya Tampung per Tahun", + grp_habisiniseminar_chart_legend_applicants: "Jumlah Peminat", + grp_habisiniseminar_chart_legend_capacity: "Daya Tampung", + grp_habisiniseminar_acc_biaya_pendaftaran: "Biaya Pendaftaran", + grp_habisiniseminar_acc_biaya_pendidikan: "Biaya Pendidikan", + grp_habisiniseminar_acc_biaya_ipi: "Iuran Pengembangan Institusi (IPI)", + grp_habisiniseminar_fee_selection: "Pembayaran Seleksi", + grp_habisiniseminar_fee_snbt_utbk: "Biaya UTBK", + grp_habisiniseminar_fee_smmptn: "Biaya Ujian SMMPTN", + grp_habisiniseminar_fee_smmptn_payment_note: "Biaya pendaftaran dibayarkan pada saat proses pendaftaran secara online melalui bank mitra yang telah ditunjuk yaitu Bank Mandiri, Bank BNI, Bank BTN, dan Bank BSI. Biaya pendaftaran yang telah dibayarkan TIDAK DAPAT ditarik kembali dengan alasan apa pun.", + grp_habisiniseminar_fee_mandiri_utbk: "Biaya Ujian Tulis Berbasis Komputer (UTBK) Jalur Mandiri", + grp_habisiniseminar_fee_mandiri_keterampilan: "Biaya Tambahan Ujian Keterampilan (Program Studi Pendidikan Seni Pertunjukan, Pendidikan Jasmani, dan Pendidikan Kepelatihan Olahraga (PKO))", + grp_habisiniseminar_fee_mandiri_wawancara: "Biaya Tambahan Wawancara (Program Studi Kedokteran)", + grp_habisiniseminar_fee_mandiri_psikologi: "Biaya Tambahan Tes Psikologi (Program Studi Kedokteran)", + grp_habisiniseminar_fee_mandiri_payment_note: "Seluruh biaya dibayarkan melalui host to host atau teller Bank Kalbar. Penting untuk dicatat bahwa biaya yang sudah dibayarkan tidak dapat diubah atau ditarik kembali dengan alasan apapun.", + grp_habisiniseminar_fee_col_group: "Golongan UKT", + grp_habisiniseminar_fee_col_cost: "Biaya (Rp)", + grp_habisiniseminar_fee_col_ipi_group: "Kelompok IPI", + grp_habisiniseminar_fee_desc_ukt: "Uang Kuliah Tunggal (UKT) dikenakan kepada seluruh mahasiswa baru Program Diploma dan Sarjana dan dibayarkan setiap semester selama masa studi sesuai dengan kalender akademik Universitas Tanjungpura.", + grp_habisiniseminar_fee_highlight_ukt: "Besaran UKT terbagi dalam beberapa kelompok yang ditentukan berdasarkan kemampuan ekonomi mahasiswa, orang tua, atau pihak lain yang membiayai mahasiswa.", + grp_habisiniseminar_fee_ipi_desc: "Iuran Pengembangan Institusi (IPI) dikenakan khusus bagi mahasiswa yang diterima melalui jalur SMM PTN-Barat dan jalur Mandiri. Pembayaran IPI dilakukan satu kali selama masa studi dan dapat diangsur hingga empat semester.", + grp_habisiniseminar_fee_ipi_highlight: "Mahasiswa yang diterima melalui jalur SNBP dan SNBT tidak dikenakan IPI.", + grp_habisiniseminar_scholar_kip: "Kartu Indonesia Pintar Kuliah", + grp_habisiniseminar_scholar_kipk: "KIP-Kuliah", + grp_habisiniseminar_scholar_bca: "Beasiswa BCA", + grp_habisiniseminar_scholar_djarum: "Djarum Beasiswa Plus", + grp_habisiniseminar_scholar_genbi: "Beasiswa Bank Indonesia", + grp_habisiniseminar_scholar_adik: "Beasiswa ADik Papua & 3T", + grp_habisiniseminar_scholar_kip_desc: "KIP-Kuliah ditujukan secara eksklusif hanya untuk yang tidak mampu secara ekonomi", + grp_habisiniseminar_scholar_bca_desc: "Beasiswa dalam bentuk Program Pendidikan Teknik Informatika (PPTI) yang diselenggarakan oleh BCA.", + grp_habisiniseminar_scholar_djarum_desc: "Beasiswa pengembangan talenta digital dari Djarum Foundation untuk mahasiswa semester 4 ke atas.", + grp_habisiniseminar_scholar_genbi_desc: "Beasiswa dari Bank Indonesia yang memberikan bantuan pendidikan dan pengembangan diri bagi mahasiswa berprestasi.", + grp_habisiniseminar_scholar_adik_desc: "Beasiswa afirmasi untuk mahasiswa asli Papua, Papua Barat, dan daerah 3T (Terdepan, Terluar, Tertinggal).", + grp_habisiniseminar_scholar_kipk_desc: "Program pemerintah untuk mahasiswa tidak mampu secara ekonomi namun berprestasi.", + grp_habisiniseminar_scholar_bca_snbt_desc: "Program Pendidikan Teknik Informatika (PPTI) yang diselenggarakan oleh BCA untuk mahasiswa terpilih.", + grp_habisiniseminar_scholar_unggulan_desc: "Beasiswa dari Kemendikbud untuk mahasiswa berprestasi di tingkat nasional maupun internasional.", + grp_habisiniseminar_scholar_untan_smmptn_desc: "Diberikan kepada mahasiswa baru dengan nilai seleksi tertinggi dari jalur SMMPTN-Barat.", + grp_habisiniseminar_scholar_bca_mandiri_desc: "Program Pendidikan Teknik Informatika (PPTI) dari BCA untuk mahasiswa dengan prestasi akademik unggul.", + grp_habisiniseminar_scholar_untan_mandiri_desc: "Beasiswa dari UNTAN untuk mahasiswa baru dengan nilai seleksi tertinggi pada jalur Mandiri.", + grp_habisiniseminar_btn_detail: "Lihat Detail", + grp_habisiniseminar_faq_title: "Pertanyaan Umum tentang Penerimaan Mahasiswa Baru", + grp_habisiniseminar_faq_sub: "Temukan jawaban atas pertanyaan Anda", + grp_habisiniseminar_faq_q1: "Bagaimana cara mengetahui hasil seleksi?", + grp_habisiniseminar_faq_a1: "Hasil seleksi diumumkan secara online melalui portal resmi sesuai jadwal yang telah ditentukan.", + grp_habisiniseminar_faq_q2: "Apa konsekuensi jika tidak melakukan registrasi ulang atau pembayaran tepat waktu?", + grp_habisiniseminar_faq_a2: "Keterlambatan registrasi atau pembayaran dapat menyebabkan status kelulusan dibatalkan atau dianggap mengundurkan diri.", + grp_habisiniseminar_faq_q3: "Apa yang harus dilakukan setelah dinyatakan lulus seleksi?", + grp_habisiniseminar_faq_a3: "Calon mahasiswa wajib melakukan registrasi ulang dan memenuhi seluruh persyaratan administrasi sesuai ketentuan. Informasi lebih lanjut dapat diakses melalui website atau media sosial resmi Informatika UNTAN.", + grp_habisiniseminar_faq_q4: "Berapa pilihan peminatan yang tersedia pada program studi ini?", + grp_habisiniseminar_faq_a4: "Program studi ini menyediakan 4 peminatan, yaitu Kecerdasan Buatan (AI), Rekayasa Perangkat Lunak, Jaringan & Keamanan, serta Sistem Informasi. Informasi mengenai kelompok peminatan dapat diakses di sini.", + grp_habisiniseminar_faq_q5: "Apakah mahasiswa akan mendapatkan pengalaman praktik atau proyek selama kuliah?", + grp_habisiniseminar_faq_a5: "Ya, mahasiswa akan mengikuti berbagai praktikum, tugas proyek, serta kerja kelompok yang dirancang untuk meningkatkan keterampilan teknis dan pemecahan masalah secara nyata.", + grp_habisiniseminar_faq_q6: "Bagaimana suasana dan kehidupan kampus di Informatika UNTAN?", + grp_habisiniseminar_faq_a6: "Lingkungan kampus mendukung kegiatan akademik dan non-akademik, dengan berbagai organisasi mahasiswa dan komunitas teknologi. Untuk mengetahui lebih lanjut mengenai kehidupan kampus dan aktivitas mahasiswa, silakan kunjungi halaman terkait di sini.", + grp_habisiniseminar_faq_q7: "Apakah lulusan Informatika UNTAN memiliki peluang kerja yang baik?", + grp_habisiniseminar_faq_a7: "Lulusan memiliki peluang kerja yang luas di berbagai bidang seperti pengembangan perangkat lunak, analisis data, keamanan jaringan, hingga kewirausahaan di bidang teknologi.", + grp_habisiniseminar_faq_q8: "Apakah mahasiswa Informatika UNTAN dapat mengikuti lomba atau kompetisi?", + grp_habisiniseminar_faq_a8: "Ya, mahasiswa didorong untuk berpartisipasi dalam berbagai lomba dan kompetisi di bidang teknologi, baik tingkat lokal, nasional, maupun internasional. Informasi mengenai lomba dan prestasi mahasiswa dapat diakses di sini.", + grp_habisiniseminar_month_jan: "Januari", + grp_habisiniseminar_month_feb: "Februari", + grp_habisiniseminar_month_mar: "Maret", + grp_habisiniseminar_month_apr: "April", + grp_habisiniseminar_month_mei: "Mei", + grp_habisiniseminar_month_jun: "Juni", + grp_habisiniseminar_month_jul: "Juli", + grp_habisiniseminar_month_des: "Desember", + grp_habisiniseminar_month_des_jan: "Desember – Januari", + grp_habisiniseminar_month_jan_feb: "Januari – Februari", + grp_habisiniseminar_month_jan_apr: "Januari – April", + grp_habisiniseminar_month_feb_apr: "Februari – April", + grp_habisiniseminar_month_feb_mar: "Februari – Maret", + grp_habisiniseminar_month_mar_apr: "Maret – April", + grp_habisiniseminar_month_mar_mei: "Mar – Mei", + grp_habisiniseminar_month_apr_mei: "April – Mei", + grp_habisiniseminar_month_mei_jun: "Mei – Juni", + grp_habisiniseminar_month_jun_jul: "Juni – Juli", + grp_m4n1fest_hero_label: "PROGRAM STUDI INFORMATIKA · UNTAN PONTIANAK", + grp_m4n1fest_hero_title_1: "Alumni", + grp_m4n1fest_hero_title_2: "Informatika", + grp_m4n1fest_hero_subtitle: "UNTAN Pontianak", + grp_m4n1fest_hero_desc: "Lulusan Informatika UNTAN kini berkarya di berbagai penjuru Indonesia dan dunia. Temukan jejak mereka, inspirasi dari perjalanan mereka, dan jadilah bagian dari jaringan ini.", + grp_m4n1fest_hero_btn_stats: "Lihat Statistik Alumni", + grp_m4n1fest_hero_btn_join: "Gabung Grup IKA", + grp_m4n1fest_hero_stat_alumni: "TOTAL ALUMNI", + grp_m4n1fest_hero_stat_alumni_desc: "data diperoleh dari alumni terdata di tracer study", + grp_m4n1fest_hero_stat_batch: "ANGKATAN", + grp_m4n1fest_hero_stat_company: "PERUSAHAAN", + grp_m4n1fest_eyebrow_achievers: "High Achievers", + grp_m4n1fest_title_achievers: "Alumni Informatika", + grp_m4n1fest_sub_achievers: "Mereka membuktikan bahwa lulusan Informatika UNTAN mampu bersaing dan berkarya di level tertinggi.", + grp_m4n1fest_ach1_batch: "Angkatan 2005", + grp_m4n1fest_ach1_pos: "Dosen Tetap, Univ Muhammadiyah Pontianak & Peneliti, Universidad de Granada", + grp_m4n1fest_ach1_ach1: "M.Sc. Computer Engineering, Marmara University", + grp_m4n1fest_ach1_ach2: "Ph.D. in Info & Comm Tech, Universidad de Granada", + grp_m4n1fest_ach1_ach3: "Berpengalaman dalam penelitian AI lintas negara", + grp_m4n1fest_ach2_batch: "Informatika UNTAN", + grp_m4n1fest_ach2_pos: "Software Developer, Traxpay GmbH, Jerman", + grp_m4n1fest_ach2_ach1: "M.Sc. Computer Science, Saarland University", + grp_m4n1fest_ach2_ach2: "Berkarir di industri FinTech Eropa", + grp_m4n1fest_ach2_ach3: "Pakar sistem pembayaran digital & integrasi ERP", + grp_m4n1fest_ach3_batch: "Angkatan 2009", + grp_m4n1fest_ach3_pos: "Product Manager, CX-Oriented Tech, Ai Tech", + grp_m4n1fest_ach3_ach1: "AI Practitioner berfokus pada Voice AI", + grp_m4n1fest_ach3_ach2: "Merancang produk teknologi berbasis Customer Experience", + grp_m4n1fest_ach3_ach3: "Inovator solusi AI di berbagai platform", + grp_m4n1fest_eyebrow_career: "Career Path", + grp_m4n1fest_title_career: "Alumni Bekerja di Mana?", + grp_m4n1fest_sub_career: "Sebaran industri tempat alumni berkarir.", + grp_m4n1fest_career_gov: "Instansi pemerintah", + grp_m4n1fest_career_private: "Perusahaan swasta", + grp_m4n1fest_career_entrepreneur: "Wiraswasta/perusahaan sendiri", + grp_m4n1fest_career_soes: "BUMN/BUMD", + grp_m4n1fest_career_ngo: "Organisasi non-profit/LSM", + grp_m4n1fest_career_note: "* Data diperoleh dari data Tracer Study Untan. Diperbarui terakhir Mei 2026.", + grp_m4n1fest_career_logo_title: "Perusahaan Tempat Alumni Berkarir", + grp_m4n1fest_logo_bca: "BCA", + grp_m4n1fest_logo_bri: "BRI", + grp_m4n1fest_logo_telkom: "Telkom Indonesia", + grp_m4n1fest_logo_pln: "PLN", + grp_m4n1fest_logo_indofood: "Indofood Fortuna", + grp_m4n1fest_logo_astra: "Astra Daihatsu", + grp_m4n1fest_logo_unilever: "Unilever", + grp_m4n1fest_logo_grab: "Grab", + grp_m4n1fest_logo_telkomsel: "Telkomsel", + grp_m4n1fest_logo_garuda: "Garuda Indonesia", + grp_m4n1fest_logo_bulog: "Perum BULOG", + grp_m4n1fest_logo_others: "Lainnya", + grp_m4n1fest_eyebrow_skill: "Skill Distribution", + grp_m4n1fest_title_skill: "Skill yang Dikuasai Alumni", + grp_m4n1fest_sub_skill: "Distribusi skill berdasarkan data form kontribusi alumni — gambaran kompetensi nyata lulusan Informatika UNTAN.", + grp_m4n1fest_skill_not_listed: "Belum Terdata di Sini?", + grp_m4n1fest_skill_not_listed_desc: "Bantu kami melengkapi database skill alumni Informatika UNTAN. Kontribusikan data keahlian Anda untuk memetakan potensi nyata dari lulusan di berbagai sektor industri.", + grp_m4n1fest_skill_btn_contrib: "Isi Form Kontribusi", + grp_m4n1fest_eyebrow_stats: "Statistik Alumni", + grp_m4n1fest_title_stats: "Jumlah Alumni berdasarkan Tahun Kelulusan", + grp_m4n1fest_sub_stats: "Data wisudawan dan lulusan berdasarkan periode semester dan tahun kelulusan.", + grp_m4n1fest_table_head_period: "Periode", + grp_m4n1fest_table_head_amount: "Jumlah", + grp_m4n1fest_table_source: "Sumber : BAK UNTAN", + grp_m4n1fest_chart_title: "Grafik Jumlah Lulusan", + grp_m4n1fest_eyebrow_testi: "Testimoni Alumni", + grp_m4n1fest_title_testi: "Kata Mereka tentang Informatika UNTAN", + grp_m4n1fest_testi1_text: "\"Bagi calon mahasiswa Informatika Untan, saya berpesan untuk terus mengembangkan diri dan tidak cepat merasa puas. Perkuliahan adalah titik awal untuk membuka berbagai peluang yang lebih luas. Fokuslah pada bidang yang diminati, tingkatkan keahlian spesifik, perluas jejaring, serta kuasai bahasa asing sebagai nilai tambah. Dengan komitmen dan kemauan yang kuat, peluang untuk berkarir maupun melanjutkan studi di tingkat global akan semakin terbuka.\"", + grp_m4n1fest_testi1_role: "Product Manager, Ai Practitioner · Angkatan 2009", + grp_m4n1fest_testi2_text: "\"Fondasi yang kalian bangun di Informatika Untan itu sangat berharga. Kemampuan berpikir algoritmik dan memecahkan masalah yang diasah selama kuliah adalah bekal yang akan terus relevan di mana pun kalian berkarier. Jangan takut bermimpi besar. Jika saya — yang memulai dari Pontianak — bisa sampai di Frankfurt dan berkontribusi di industri FinTech Eropa, kalian pun bisa. Yang penting adalah terus belajar, berani mengambil kesempatan, dan jangan pernah meremehkan kualitas pendidikan yang kalian terima di Untan.\"", + grp_m4n1fest_testi2_role: "Software Developer, Traxpay GmbH", + grp_m4n1fest_testi3_text: "\"Fondasi yang kita peroleh dari kampus dan Himpunan merupakan wadah terbaik untuk belajar ketangguhan. Jadikanlah pengalaman tersebut sebagai sarana untuk terus terbuka terhadap perubahan. Jika kita mampu beradaptasi dengan baik, batas kemampuan kita tidak lagi ditentukan oleh geografi, melainkan seberapa jauh kita berani melangkah.\"", + grp_m4n1fest_testi3_role: "Dosen Tetap, UMP & Peneliti, Universidad de Granada", + grp_m4n1fest_eyebrow_events: "Kegiatan Tahunan", + grp_m4n1fest_title_events: "Temui Kami di Setiap Tahun", + grp_m4n1fest_sub_events: "HMIF Informatika UNTAN menyelenggarakan dua kegiatan tahunan besar yang selalu mengundang dan mempertemukan para alumni.", + grp_m4n1fest_event1_title: "SKBI Ramadhan", + grp_m4n1fest_event1_meta: "Silaturahmi Keluarga Besar Informatika", + grp_m4n1fest_event1_desc: "SKBI Ramadhan adalah acara silaturahmi yang mempertemukan seluruh keluarga besar Informatika UNTAN — mahasiswa aktif, dosen, dan alumni dari berbagai angkatan. Kegiatan ini menjadi momen silaturahmi yang hangat di bulan Ramadhan, mempererat tali persaudaraan alumni dengan civitas akademika melalui kegiatan sosial, buka bersama, dan networking.", + grp_m4n1fest_event1_note: "Info lengkap di Instagram HMIF", + grp_m4n1fest_event2_title: "INFORMERS", + grp_m4n1fest_event2_meta: "Informatic Anniversary", + grp_m4n1fest_event2_desc: "INFORMERS adalah perayaan Hari Ulang Tahun Program Studi Informatika UNTAN. Acara besar ini mewadahi berbagai kegiatan kompetisi akademik maupun non-akademik tingkat mahasiswa, pameran inovasi IT, hiburan musik, hingga malam puncak yang mengumpulkan seluruh civitas akademika dan alumni dalam semangat dedikasi dan kebersamaan.", + grp_m4n1fest_event2_note: "Pantau IG untuk jadwal & cara daftar", + grp_m4n1fest_eyebrow_join: "Gabung IKA", + grp_m4n1fest_title_join: "Bergabung dengan Grup Alumni", + grp_m4n1fest_sub_join: "Daftarkan dirimu untuk bergabung ke grup WhatsApp Ikatan Keluarga Alumni (IKA) Informatika UNTAN. Terhubung dengan ratusan alumni dan dapatkan info lowongan, kegiatan, dan kolaborasi.", + grp_m4n1fest_step1_head: "Isi Form Pendaftaran", + grp_m4n1fest_step1_desc: "Masukkan nama, NIM, nomor HP, email, status pekerjaan, dan 3 skill utamamu.", + grp_m4n1fest_step2_head: "Verifikasi Admin IKA", + grp_m4n1fest_step2_desc: "Admin IKA akan memverifikasi bahwa kamu adalah alumni Informatika UNTAN dalam 1–2 hari kerja.", + grp_m4n1fest_step3_head: "Masuk Grup WhatsApp IKA", + grp_m4n1fest_step3_desc: "Setelah terverifikasi, kamu akan diundang ke grup WhatsApp IKA dan langsung terhubung dengan ratusan alumni!", + grp_m4n1fest_wa_badge: "Grup IKA Informatika UNTAN", + grp_m4n1fest_wa_title: "IKA Informatika UNTAN", + grp_m4n1fest_wa_sub: "500+ anggota aktif", + grp_m4n1fest_form_title: "Form Pendaftaran IKA", + grp_m4n1fest_form_sub: "Isi data di bawah untuk bergabung ke grup WhatsApp alumni.", + grp_m4n1fest_form_nama: "Nama Lengkap *", + grp_m4n1fest_form_nim: "NIM", + grp_m4n1fest_form_angkatan: "Angkatan *", + grp_m4n1fest_form_hp: "No. HP / WhatsApp *", + grp_m4n1fest_form_email: "Email Aktif *", + grp_m4n1fest_form_status: "Status Saat Ini *", + grp_m4n1fest_form_status_opt1: "— Pilih Status —", + grp_m4n1fest_form_status_opt2: "Bekerja", + grp_m4n1fest_form_status_opt3: "Wirausaha / Freelance", + grp_m4n1fest_form_status_opt4: "Melanjutkan Studi", + grp_m4n1fest_form_status_opt5: "Mencari Kerja", + grp_m4n1fest_form_instansi: "Tempat Bekerja / Instansi (opsional)", + grp_m4n1fest_form_skill_title: "3 Skill Utama yang Dikuasai *", + grp_m4n1fest_form_skill_sub: "Pilih 3 skill yang paling kamu kuasai.", + grp_m4n1fest_form_skill_opt1: "— Pilih —", + grp_m4n1fest_form_persetujuan: "Persetujuan *", + grp_m4n1fest_form_persetujuan_txt: "Saya menyetujui bahwa data yang saya masukkan adalah benar and bersedia diverifikasi oleh admin IKA Informatika UNTAN.", + grp_m4n1fest_form_btn: "Daftar Sekarang", + grp_m4n1fest_form_msg: "Data langsung tersimpan · Proses 1–2 hari kerja", + grp_m4n1fest_eyebrow_faq: "FAQ", + grp_m4n1fest_title_faq: "Pertanyaan Seputar Alumni", + grp_m4n1fest_faq_q1: "Bagaimana cara bergabung dengan grup alumni IKA?", + grp_m4n1fest_faq_a1: "Isi form pendaftaran di section \"Gabung IKA\" di atas dengan nama lengkap, angkatan, nomor WhatsApp, dan email aktifmu. Setelah diverifikasi admin dalam 1–2 hari kerja, kamu akan diundang ke grup WhatsApp IKA Informatika UNTAN.", + grp_m4n1fest_faq_q2: "Berapa lama proses verifikasi pendaftaran IKA?", + grp_m4n1fest_faq_a2: "Proses verifikasi biasanya memakan waktu 1–2 hari kerja. Admin IKA akan mengecek bahwa kamu adalah alumni Informatika UNTAN berdasarkan nama dan angkatan yang diisi.", + grp_m4n1fest_faq_q3: "Apa manfaat bergabung ke grup alumni IKA?", + grp_m4n1fest_faq_a3: "Di grup IKA kamu dapat mengakses info lowongan kerja dari sesama alumni, info kegiatan tahunan seperti SKBI Ramadhan dan INFORMERS, kolaborasi proyek, berbagi pengalaman karir, serta membangun koneksi profesional dengan ratusan alumni dari berbagai angkatan dan industri.", + grp_m4n1fest_faq_q4: "Apakah ada info lowongan kerja atau magang di IKA?", + grp_m4n1fest_faq_a4: "Ya! Salah satu manfaat utama grup IKA adalah alumni yang sudah bekerja sering berbagi info lowongan dari perusahaan mereka. Banyak anggota IKA yang mendapat pekerjaan pertama atau berpindah karir melalui referral dari sesama alumni di grup ini.", + grp_m4n1fest_faq_q5: "Apakah ada program kerja sama industri dari Prodi Informatika?", + grp_m4n1fest_faq_a5: "Prodi Informatika UNTAN memiliki program Kerja Praktik (KP) wajib yang menghubungkan mahasiswa dengan industri. Info terkini tentang kerja sama industri bisa diikuti melalui Instagram @informatika.untan.", + grp_m4n1fest_faq_q6: "Bagaimana cara mengikuti kegiatan SKBI Ramadhan atau INFORMERS?", + grp_m4n1fest_faq_a6: "Info pendaftaran dan kegiatan SKBI Ramadhan dapat ditemukan di Instagram @hmif_ftuntan. Untuk INFORMERS, pantau Instagram @informershmif. Sebagai anggota grup IKA, kamu juga akan mendapatkan notifikasi langsung setiap kali ada kegiatan yang melibatkan alumni.", + grp_m4n1fest_faq_q7: "Siapa saja yang bisa mendaftar ke IKA?", + grp_m4n1fest_faq_a7: "Seluruh alumni Program Studi Informatika Universitas Tanjungpura dari semua angkatan dapat mendaftar ke IKA. Tidak ada batasan angkatan, status pekerjaan, atau domisili. Selama kamu adalah lulusan Informatika UNTAN, kamu berhak bergabung!", + grp_m4n1fest_eyebrow_contact: "Kontak & Sosial Media", + grp_m4n1fest_title_contact: "Temui Kami di Instagram", + grp_m4n1fest_sub_contact: "Ikuti akun Instagram resmi kami untuk informasi terbaru seputar kegiatan alumni, info lowongan, dan update Prodi Informatika UNTAN.", + grp_m4n1fest_ig_main_label: "Instagram Resmi Prodi", + grp_m4n1fest_ig_main_desc: "Update akademik, kegiatan, dan info alumni", + grp_m4n1fest_ig_hmif_desc: "HMIF & SKBI Ramadhan", + grp_m4n1fest_ig_informers_desc: "INFORMERS", + grp_m4n1fest_cta_box_title: "Siap bergabung?", + grp_m4n1fest_cta_box_desc: "Scroll ke atas dan isi form pendaftaran IKA sekarang. Gratis, cepat, dan langsung terhubung dengan ratusan alumni!", + grp_m4n1fest_cta_box_btn: "Daftar IKA Sekarang", + // --- Group C4A: Staf Pendukung & SOP --- grp_c4a_hero_tag: "LAYANAN AKADEMIK \u2022 INFORMATIKA UNTAN", grp_c4a_hero_title: "Staf Pendukung &
Standar Operasional Prosedur (SOP)", @@ -610,9 +1126,7 @@ const translations = { grp_c4a_sop_showing_all: "Menampilkan {total} SOP", grp_c4a_sop_showing_filtered: "Menampilkan {shown} dari {total} SOP", }, - en: { - // --- Common / Shared --- skip_to_content: "Skip to Main Content", btn_more: "Read More", btn_view_all: "View All", @@ -623,16 +1137,12 @@ const translations = { no_data: "No data found.", coming_soon: "Coming Soon", follow_us: "Follow Us", - - // --- Navbar Top --- nav_about: "About Us", nav_program: "Programs", nav_sdm: "Human Resources", nav_facilities: "Facilities", nav_works: "Works & Events", nav_contact: "Contact", - - // --- Mega Menu: Tentang --- mega_about_title: "About Informatics UNTAN", mega_about_desc: "Excellently Accredited, producing competent graduates in information technology since 1993.", link_history: "History & Milestones", @@ -643,8 +1153,6 @@ const translations = { link_vision_desc: "Direction and goals of program development", link_partnership: "Partnerships & MoU", link_partnership_desc: "Industry and academic partners", - - // --- Mega Menu: Program --- mega_program_obe: "OBE-Based Curriculum", mega_program_obe_desc: "Outcome-Based Education with 4 concentration tracks: AI, Software Engineering, Network, and Information Systems.", btn_krs_sim: "Course Enrollment Simulation", @@ -659,23 +1167,15 @@ const translations = { link_guideline_desc: "Academic guidebook", link_statistics: "Informatics in Numbers", link_statistics_desc: "Statistics & data", - - // --- Mega Menu: SDM --- mega_sdm_title: "Professional Teaching Team", mega_sdm_desc: "30+ lecturers with Master/Doctoral qualifications from leading domestic and international universities.", btn_all_lecturers: "View All Lecturers", label_expertise: "Expertise Groups", - - // --- SDM: Kelompok Keahlian --- kk_ai: "Artificial Intelligence", kk_se: "Software Engineering", kk_network: "Network & Security", kk_is: "Information Systems", - - // --- Navigation --- nav_home: "Home", - - // --- Mega Menu: Fasilitas --- mega_lab: "Laboratories", mega_lab_desc: "8 modern labs with the latest equipment", mega_dc: "Data Center", @@ -683,8 +1183,6 @@ const translations = { mega_eresources: "E-Resources", mega_eresources_desc: "Access to journals & scientific databases", mega_hub_desc: "PaaS Platform for students", - - // --- Mega Menu: Karya & Agenda --- mega_research_title: "Featured Research", mega_sinta_title: "Best SINTA Index", mega_sinta_desc: "Lecturer publications indexed in Scopus, WoS, and accredited national journals.", @@ -699,26 +1197,18 @@ const translations = { link_seminar_desc: "Academic & scientific events", link_achievements: "Achievements", link_achievements_desc: "Lecturer & student achievements", - - // --- Mega Menu: Kontak --- mega_contact_title: "Contact Us", link_official_contact: "Official Contact", link_contact_desc: "Staff contact directory", link_feedback: "Feedback & Suggestions", link_feedback_desc: "Send your feedback", link_social_media: "Social Media", - - // --- Hero --- hero_badge: "Excellence & Innovation", hero_title: "Shaping the Architects of Digital Future", hero_desc: "Informatics Study Program at Tanjungpura University produces digital talents ready to compete in the era of Industry 4.0 with a competency-based curriculum.", hero_cta_primary: "Explore Programs", hero_cta_secondary: "Full Profile", - - // --- Marquee --- marquee_connecting: "Connecting to server...", - - // --- Badges/Tags --- badge_latest_info: "LATEST NEWS", badge_achievement: "ACHIEVEMENTS & RECOGNITION", badge_works_ideas: "WORKS & IDEAS", @@ -729,8 +1219,6 @@ const translations = { badge_eresources: "Informatics e-Resources", badge_penelitian: "FACULTY RESEARCH", badge_pkm: "COMMUNITY SERVICE", - - // --- Section Headers --- section_rankings: "Rankings", section_academic_services: "Academic Services", section_news: "Informatics News", @@ -756,8 +1244,6 @@ const translations = { section_penelitian_desc: "Research and scientific publications from Informatics UNTAN faculty.", section_pkm: "Community Service", section_pkm_desc: "Community service programs using information technology.", - - // --- CTA Links --- cta_all_rubrics: "All Rubrics", cta_all_stories: "All Stories", cta_visit_instagram: "Visit Instagram", @@ -774,8 +1260,6 @@ const translations = { cta_all_karya: "See All Works", btn_profile_prodi: "Program Profile", btn_research_innovation: "Research Innovation", - - // --- Karya & Ideas --- link_all_karya: "All Works", link_all_karya_desc: "View all works in one page", breadcrumb_karya: "Works & Ideas", @@ -786,8 +1270,6 @@ const translations = { karya_search_placeholder: "Search works title...", karya_empty: "No works match the selected filter.", btn_load_more: "Load More", - - // --- Layanan Akademik Links --- link_calendar: "Academic Calendar", link_support_staff: "Support Staff", link_scholarship: "Scholarship Info", @@ -795,8 +1277,6 @@ const translations = { link_student_achievement: "Student Achievements", link_alumni: "Alumni & Testimonials", link_kontributor: "Become a Contributor", - - // --- Filters --- filter_artikel: "Articles", filter_pengabdian: "Community Service", filter_mahasiswa: "Students", @@ -810,16 +1290,10 @@ const translations = { filter_all_kbk: "All Research Groups", filter_all_position: "All Positions", loading_lecturer: "Loading Lecturer Data...", - - // --- App Showcase --- app_coming_soon: "More Apps", app_coming_soon_sub: "Coming Soon", - - // --- Footer --- footer_follow_us: "Follow Us", footer_copyright: "Informatics Study Program — Tanjungpura University", - - // --- content.html --- breadcrumb_home: "Home", breadcrumb_news: "Informatics News", breadcrumb_penelitian: "Research", @@ -837,8 +1311,6 @@ const translations = { label_pelaksana: "Implementer", label_tahun: "Year", label_lokasi: "Location", - - // --- kurikulum.html --- kur_page_title: "IPS-Based Study Simulation", kur_page_desc1: "Real simulation: Previous semester GPA determines the credit quota for next semester.", kur_page_desc2: "If grade is E, use the \"Retake Next Year\" option (Right Click) to improve.", @@ -881,8 +1353,6 @@ const translations = { kur_toast_target_oob: "Sorry, target semester ({semester}) is outside simulation range (Max Semester 8).", kur_toast_exists: "This course already exists in Semester {semester}.", kur_toast_added: "Course added to Semester {semester}.", - - // --- academic-guidelines.html --- ag_badge: "ACADEMIC GUIDELINES", ag_desc: "This page summarizes academic guidelines, learning plans, SOPs, administrative formats, and journal references for learning needs at Informatics UNTAN.", ag_general: "General Academic Guidelines", @@ -894,22 +1364,16 @@ const translations = { ag_final_project: "Final Project Guidelines", ag_forms: "Forms / Registration / Administration", ag_journal: "Journal Reference", - - // --- graduate-profile.html --- gp_badge: "GRADUATE PROFILE 2026", gp_desc: "Summary of graduate profile and Program Learning Outcomes (PLO) for Informatics UNTAN curriculum 2026.", gp_heading: "Graduate Profile", gp_table_code: "Code", gp_table_outcome: "Outcome Summary", - - // --- people-based-on-kk.html --- pkk_badge: "LECTURERS BY EXPERTISE GROUP", pkk_kk_ai: "AI Expertise Group", pkk_kk_rpl: "Software Engineering Expertise Group", pkk_kk_net: "Network & Security Expertise Group", pkk_kk_si: "Information Systems Expertise Group", - - // --- subject-and-courses.html --- sc_badge: "CURRICULUM & COURSES", sc_desc: "Summary of course structure and PLO mapping in Informatics UNTAN Curriculum.", sc_table_cpl: "PLO", @@ -923,8 +1387,6 @@ const translations = { sc_stage_specialization: "Specialization & Integration", sc_stage_professional: "Professional", sc_electives: "Elective Courses", - - // --- ifuntanhub.html --- hub_nav_aria: "Main navigation", hub_login_aria: "Login to dashboard", hub_docs_aria: "View documentation", @@ -995,8 +1457,6 @@ const translations = { hub_lab_address: "Computer Lab 2nd Floor,
Informatics Engineering Building UNTAN,
Pontianak, West Kalimantan.", hub_footer_copyright: "© 2026 UNTAN INFORMATICS DEPARTMENT. ALL SYSTEMS GO.", hub_footer_tagline: "DESIGNED FOR SCHOLARS & BUILDERS.", - - // --- Luco Chatbot --- luco_greeting: "Hi! Can Luco help you? 💬", luco_name: "Luco", luco_role: "Informatics Assistant", @@ -1011,8 +1471,6 @@ const translations = { luco_aria_open: "Open chat with Luco", luco_aria_close: "Close chat", luco_aria_send: "Send message", - - // --- profil.html --- profile_badge: "PROGRAM PROFILE", profile_title: "Informatics Study Program Profile", profile_subtitle: "Learn more about the vision, mission, goals, and organizational structure of the Informatics Study Program at Tanjungpura University.", @@ -1051,8 +1509,6 @@ const translations = { org_kk: "Expertise Groups (KK)", org_support_staff: "Supporting Staff", see_also: "See also", - - // --- struktur-org.html --- breadcrumb_struktur: "Organizational Structure", struktur_badge: "ORGANIZATIONAL STRUCTURE", struktur_page_title: "Department Organizational Structure", @@ -1078,8 +1534,6 @@ const translations = { profil_sekprodi: "Secretary of Study Program", profil_kepala_lab: "Laboratory Head", profil_koordinator: "Academic Coordinator", - - // --- fasilitas.html --- fasilitas_page_title: "Facilities & Laboratories", fasilitas_intro: "The Informatics Study Program at UNTAN is equipped with modern laboratory facilities to support learning and research activities.", lab_network_title: "Computer Network Laboratory", @@ -1094,8 +1548,6 @@ const translations = { lab_multimedia_desc: "Laboratory for multimedia content development, games, and virtual reality.", lab_head: "Lab Head", lab_equipment: "Main Equipment", - - // --- riset.html --- nav_research: "Research", riset_title: "Research & Community Service", riset_subtitle: "List of research and community service activities by Informatics UNTAN lecturers", @@ -1109,8 +1561,6 @@ const translations = { riset_stat_shown: "Displayed", riset_load_more: "Load More", riset_empty: "No data matches your search.", - - // --- kontributor.html --- kontrib_page_title: "Submit Your Work - Informatics UNTAN", kontrib_hero_badge: "CONTRIBUTOR PROGRAM", kontrib_hero_title: "Share Your Writing", @@ -1176,6 +1626,588 @@ const translations = { kontrib_guideline_4: "Avoid plagiarism. Work must be original.", kontrib_guideline_5: "The editorial team reserves the right to edit content for editorial purposes.", + // Jellyfish.Corp + grp_jellyfish_hero_title: "Informatika Overview", + grp_jellyfish_hero_subtitle: "Tanjungpura University", + grp_jellyfish_hero_desc: "Producing outstanding digital talents from West Kalimantan for Indonesia and the world. Join us on the journey to build the future of technology.", + grp_jellyfish_btn_register: "Register Now", + grp_jellyfish_btn_learn: "Learn More ↓", + grp_jellyfish_stat_founded: "Year Founded", + grp_jellyfish_stat_students: "Active Students", + + grp_jellyfish_about_badge: "About the Program", + grp_jellyfish_about_title: "Get to Know Informatika Study Program UNTAN", + grp_jellyfish_about_intro: "The Informatika Study Program at Tanjungpura University has been active since 2004 and is one of the centers for developing digital talents in West Kalimantan. As the digital era evolves, we continue to innovate to be at the forefront of rapidly advancing information technology.", + grp_jellyfish_vision_label: "🎯 Vision", + grp_jellyfish_vision_text: "To become an excellent, innovative, and globally competitive study program, focusing on intelligent informatics to support sustainability and community empowerment through technology solutions based on local wisdom.", + grp_jellyfish_mission_label: "📋 Mission", + grp_jellyfish_mission_1_title: "Excellent OBE Education", + grp_jellyfish_mission_1_desc: "Deliver excellent and innovative education based on intelligent informatics through an OBE approach to produce graduates who are competent, adaptable, ethical, and globally competitive.", + grp_jellyfish_mission_2_title: "Research for SDGs", + grp_jellyfish_mission_2_desc: "Develop leading research in the field of intelligent informatics that supports the achievement of Sustainable Development Goals (SDGs) and is relevant to local and global challenges.", + grp_jellyfish_mission_3_title: "Community Service Based on Local Wisdom", + grp_jellyfish_mission_3_desc: "Apply and develop technology solutions based on local wisdom in community service activities to empower communities inclusively and sustainably.", + grp_jellyfish_mission_4_title: "Strategic Partnerships", + grp_jellyfish_mission_4_desc: "Build strategic partnerships with industry, government, and academic institutions to strengthen the ecosystem of research, innovation, and application of intelligent informatics that has real impact.", + grp_jellyfish_mission_5_title: "Ethical Academic Culture", + grp_jellyfish_mission_5_desc: "Foster an ethical, collaborative, creative academic culture oriented toward lifelong learning to prepare excellent human resources facing technology disruption.", + grp_jellyfish_timeline_title: "Milestones", + + grp_jellyfish_stats_badge: "Data & Statistics", + grp_jellyfish_stats_title: "Informatika UNTAN in Numbers", + grp_jellyfish_stats_subtitle: "Not just numbers—these are real stories from the long journey of Informatika UNTAN.", + + grp_jellyfish_achievement_badge: "Achievements", + grp_jellyfish_achievement_title: "Outstanding Achievements", + grp_jellyfish_achievement_subtitle: "From national stages to international collaboration — Informatika UNTAN is present, competing, and leaving a mark.", + + grp_jellyfish_studyclub_badge: "Community", + grp_jellyfish_studyclub_title: "Study Club", + grp_jellyfish_studyclub_subtitle: "Study Club is a platform for developing student competencies through structured interests and community-based initiatives. Each study club is designed to deepen specific expertise in informatics.", + + grp_jellyfish_career_badge: "Career", + grp_jellyfish_career_title: "Future of Informatika Graduates", + grp_jellyfish_career_headline: "Informatika UNTAN graduates are not just job seekers — they are solution creators.", + grp_jellyfish_career_desc: "Informatika UNTAN graduates are absorbed widely by technology industries, government agencies, SOEs, and startups. With strong digital competencies, brilliant careers await in various strategic sectors.", + + grp_jellyfish_alumni_badge: "Alumni", + grp_jellyfish_alumni_title: "Alumni Success Stories", + grp_jellyfish_alumni_subtitle: "They are proof that Informatika UNTAN is a stepping stone to a brilliant career — from Pontianak, to the world stage.", + + grp_jellyfish_infra_badge: "Infrastructure", + grp_jellyfish_infra_title: "Infrastructure and Facilities", + grp_jellyfish_infra_subtitle: "Complete and modern facilities to support student learning, research, and innovation processes.", + + grp_jellyfish_partner_badge: "Partnerships", + grp_jellyfish_partner_title: "Strategic Partners of Informatika UNTAN", + grp_jellyfish_partner_subtitle: "Built on trust and collaboration with leading technology companies, government agencies, and educational institutions.", + + grp_jellyfish_cta_badge: "Join Us", + grp_jellyfish_cta_title: "Start Your Digital Journey", + grp_jellyfish_cta_highlight: "With Us", + grp_jellyfish_cta_desc: "Become part of the best informatics community in West Kalimantan. Register now and realize your technology career dreams with Informatika UNTAN.", + grp_jellyfish_btn_registration: "Register as New Student", + grp_jellyfish_btn_whatsapp: "💬 Chat on WhatsApp", + + grp_jellyfish_timeline_2004_title: "Program Establishment", + grp_jellyfish_timeline_2004_desc: "The first batch of students was admitted on May 18 through internal selection and transfer from the Electrical Engineering Study Program.", + grp_jellyfish_timeline_2005_title: "First SNMPTN", + grp_jellyfish_timeline_2005_desc: "New student admissions were integrated nationally through the Seleksi Nasional Masuk Perguruan Tinggi Negeri (SNMPTN).", + grp_jellyfish_timeline_2006_title: "Accreditation C", + grp_jellyfish_timeline_2006_desc: "The study program obtained Accreditation C rating from BAN-PT as the first step of systematic academic quality assurance.", + grp_jellyfish_timeline_2008_title: "Evening Classes & SPOTA", + grp_jellyfish_timeline_2008_desc: "Opening of evening program classes and development of SPOTA (Final Project Outline Support System) as the first innovative application of the study program.", + grp_jellyfish_timeline_2016_title: "Accreditation B", + grp_jellyfish_timeline_2016_desc: "Improvement of academic quality standards marked by achieving Accreditation B rating from BAN-PT.", + grp_jellyfish_timeline_2017_title: "Official Name Change", + grp_jellyfish_timeline_2017_desc: "Change of study program nomenclature from Information Technology Engineering to Informatics, and adjustment of graduate degree title from Bachelor of Engineering (S.T.) to Bachelor of Computer Science (S.Kom.).", + grp_jellyfish_timeline_2019_title: "Last PPAPK Cohort", + grp_jellyfish_timeline_2019_desc: "Implementation of non-regular student admission (PPAPK) as the last cohort before official deactivation.", + grp_jellyfish_timeline_2021_title: "EdoXid Launch", + grp_jellyfish_timeline_2021_desc: "Launch of EdoXid, a digital information management system for final project examinations to support academic needs during the pandemic.", + grp_jellyfish_timeline_2023_title: "First Professor", + grp_jellyfish_timeline_2023_desc: "Inauguration of the first Professor of Informatika Study Program UNTAN, namely Prof. Dr. Herry Sujaini, S.T., M.T.", + grp_jellyfish_timeline_2025_title: "5th Head of Study Program", + grp_jellyfish_timeline_2025_desc: "Inauguration of Eva Faja Ripanti, S.Kom., M.M.S.I., Ph.D. as the fifth Head of Informatika Study Program.", + + badge_competition: "Competition", + badge_publication: "Publication", + badge_community: "Community Service", + badge_innovation: "Application Innovation", + label_finalist: "Finalist", + label_team_best: "Best Regional Team", + label_honor: "Honor", + label_participant: "PARTICIPANT", + label_still_active: "💡 Still Active", + label_faculty_scale: "🚀 Faculty-wide Scale", + + grp_jellyfish_prest_compfest_title: "Top 10 Finalist Competitive Programming Contest (COMPFEST) 17", + grp_jellyfish_prest_compfest_desc: "Successfully achieved top 10 national position at COMPFEST 17, a programming competition organized by Universitas Indonesia (UI).", + grp_jellyfish_prest_gemastik18_title: "Best Regional Team GEMASTIK XVIII Competitive Programming Category", + grp_jellyfish_prest_gemastik18_desc: "Successfully became the Best Regional Team at GEMASTIK XVIII in the Competitive Programming category.", + grp_jellyfish_prest_icpc_title: "International Collegiate Programming Contest with Honor Distinction", + grp_jellyfish_prest_icpc_desc: "Informatika UNTAN's competitive programming team successfully advanced to the ICPC Asia Jakarta 2025 regional round.", + grp_jellyfish_prest_gemastik17_title: "Best Regional Team GEMASTIK XVII Competitive Programming Category", + grp_jellyfish_prest_gemastik17_desc: "Successfully became the Best Regional Team at GEMASTIK XVII in the Competitive Programming category.", + grp_jellyfish_prest_mtq_title: "WEST KALIMANTAN PROVINCIAL MTQ COMPETITION PARTICIPANT TAHFIDZ HADITS 100 HADITS WITH CHAIN FEMALE CATEGORY", + grp_jellyfish_prest_mtq_desc: "Participated in the West Kalimantan provincial MTQ competition in the Tahfidz Hadits 100 Hadits with Chain Female category.", + grp_jellyfish_prest_crm_title: "Customer Relationship Management (CRM) System to Improve E-Learning User Services", + grp_jellyfish_prest_crm_desc: "Authors: EF Ripanti, EE Pratama, F Asrin. This research examines the application of CRM systems to improve service quality and user satisfaction on e-learning platforms.", + grp_jellyfish_prest_dbscan_title: "MARKETING AREA RECOMMENDATIONS FOR MOBILE VEGETABLE VENDORS USING DBSCAN METHOD AND HEATMAP VISUALIZATION", + grp_jellyfish_prest_dbscan_desc: "Authors: DA Nugraha, IY Sholva, EE Pratama. This research uses DBSCAN method and heatmap visualization to recommend potential marketing areas for mobile vegetable vendors.", + grp_jellyfish_prest_kmeans_title: "Data mining for stock clustering in the energy sector using k-means method", + grp_jellyfish_prest_kmeans_desc: "Authors: AS Sukamto, W Setiawan, EE Pratama. This research applies K-Means algorithm to cluster energy sector stocks based on similar data characteristics to support investment analysis.", + grp_jellyfish_prest_kodingmu_title: "KODINGMU COMPUTATIONAL THINKING LEARNING PLATFORM FOR SMAN 1 MEMPAWAH HILIR STUDENTS", + grp_jellyfish_prest_kodingmu_desc: "A community service program aimed at improving computational thinking and problem-solving skills of students through training and the use of the interactive Kodingmu learning platform based on programming activities.", + grp_jellyfish_prest_spota_title: "SPOTA (2008)", + grp_jellyfish_prest_spota_desc: "Final Project Outline Support System — born from faculty idea, built by students, still in use today. An innovation ahead of its time.", + grp_jellyfish_prest_edoxid_title: "EdoXid (2021)", + grp_jellyfish_prest_edoxid_desc: "Digital examination submission system — emerged to meet pandemic needs, now scaled up to the Faculty of Mathematics and Natural Sciences of Tanjungpura University.", + + grp_jellyfish_btn_filter_publikasi: "Publications & Community Service", + grp_jellyfish_btn_filter_aplikasi: "Application Innovation", + grp_jellyfish_mhs_aktif: "Active Students", + grp_jellyfish_tahun_akademik: "Academic Year 2024/2025", + grp_jellyfish_total_alumni: "Alumni Total", + grp_jellyfish_data_total_alumni: "Data since 2022", + grp_jellyfish_bulan_tunggu: "6 Months", + grp_jellyfish_waktu_tunggu_kerja: "Employment Wait Time", + grp_jellyfish_rata_lulusan: "Average graduate absorption", + grp_jellyfish_dosen_tetap: "Permanent Lecturers", + grp_jellyfish_desc_dosen: "Experienced and certified in their respective fields.", + grp_jellyfish_jurnal_ilmiah: "Scientific Journals", + grp_jellyfish_jurnal: "400+ Journals", + grp_jellyfish_rasio_dosen: "Lecturer and Student Ratio", + grp_jellyfish_guru_besar: "Professor", + grp_jellyfish_jlh_guru_besar: "1 Professor", + grp_jellyfish_nama_guru_besar: "Prof. Dr. Herry Sujaini, S.T., M.T. — the first Professor of Informatika UNTAN (since 2023).", + grp_jellyfish_rasio_standar: "Meets BAN-PT standards", + grp_jellyfish_arise: "This activity program focuses on competitive programming. Students are trained to sharpen problem-solving and algorithm skills through prestigious national and international programming competitions.", + grp_jellyfish_software: "Focused on software development. The materials cover basic web fundamentals, version control (GitHub), software development life cycle, Entity Relationship Diagram, tech stack, and use cases.", + grp_jellyfish_minerva: "Focused on data mining. Students will deepen data extraction techniques, statistical analysis, and application in various industry domains to produce valuable insights.", + grp_jellyfish_join_studyclub: "Join a Study Club", + grp_jellyfish_studyclub: "This study club is open to all active Informatika UNTAN students. Choose a specialization that matches your interests and career goals.", + grp_jellyfish_study_info: "More Info →", + grp_jellyfish_btn_filter_semua: "All", + grp_jellyfish_btn_filter_kompetisi: "Competition", + grp_jellyfish_bidang_karir: "Career Field", + + grp_jellyfish_akademisi: "Academia / Lecturer", + grp_jellyfish_wirausaha: "Digital Entrepreneurship", + grp_jellyfish_mandiri: "Independent, Remote Work", + grp_jellyfish_if_akt1: "Informatics, Class of 2005", + grp_jellyfish_if_akt2: "Informatics, Class of 2018", + grp_jellyfish_if_akt3: "Informatics, Class of 2006", + grp_jellyfish_kerja1: "Lecturer at UM Pontianak", + grp_jellyfish_alumni_desc1: "A 2005 Informatika UNTAN alumnus who built a strong computing foundation, continued master's studies at Marmara University on a Türkiye Bursları scholarship, worked as a lecturer at Muhammadiyah University of Pontianak, and is now pursuing a doctoral degree at Universidad de Granada with a focus on AI for smart transportation systems and health informatics.", + grp_jellyfish_alumni_desc2: "An Informatika UNTAN alumnus with strong programming and problem-solving skills, who continued master's studies at Saarland University in Germany on international AI research, then worked as a Software Developer at FinTech Travixy Frankfurt, developing financing platforms that power billion-euro transactions.", + grp_jellyfish_alumni_desc3: "A 2006 Informatika UNTAN alumnus currently working on the Product Team at AI Rudder, leveraging project-based learning experience from college, continuing postgraduate studies in China on scholarship, and contributing to the global AI industry.", + grp_jellyfish_infra_lab_name: "8 Laboratories", + grp_jellyfish_infra_lab_desc: "Programming, Networking, Multimedia, IoT, AI, Database, Mobile Dev, and Cybersecurity labs — air-conditioned, high-speed PCs.", + grp_jellyfish_infra_lab_stat: "8 Active & High-Tech Labs", + grp_jellyfish_infra_dc_name: "Data Center", + grp_jellyfish_infra_dc_desc: "On-premise servers with high-capacity local cloud infrastructure for research and application deployment.", + grp_jellyfish_infra_dc_stat: "99.9% Uptime · Redundant", + grp_jellyfish_infra_hub_name: "IFUNTANHUB", + grp_jellyfish_infra_hub_desc: "Exclusive student PaaS platform for app deployment, project hosting, GPU access, and team collaboration.", + grp_jellyfish_infra_hub_stat: "Free · GPU Access", + grp_jellyfish_infra_eresources_name: "E-Resources", + grp_jellyfish_infra_eresources_desc: "Access to international journals like Scopus, IEEE Xplore, ACM Digital Library, Springer, and other research databases.", + grp_jellyfish_infra_eresources_stat: "IEEE · Scopus · ACM", + grp_jellyfish_license_label: "Available Software Licenses:", + + grp_jellyfish_labs: "Programming, Networking, Multimedia, IoT, AI, Database, Mobile Dev, and Cybersecurity labs — air-conditioned, high-speed PCs.", + grp_jellyfish_labs2: "8 Active & High-Tech Labs", + grp_jellyfish_data_center: "On-premise servers with high-capacity local cloud infrastructure for research and application deployment.", + grp_jellyfish_ifuntanhub: "Exclusive student PaaS platform for app deployment, project hosting, GPU access, and team collaboration.", + grp_jellyfish_free: "Free · GPU Access", + grp_jellyfish_resources: "Access to international journals like Scopus, IEEE Xplore, ACM Digital Library, Springer, and other research databases.", + grp_jellyfish_license: "Available Software Licenses:", + + grp_jellyfish_industri: "Industry & Private Sector", + grp_jellyfish_pemerintah: "Government Agencies", + grp_jellyfish_perguruan_tinggi: "Higher Education & Research Institutes", + + grp_jellyfish_info_kontak: "Contact Information", + grp_jellyfish_alamat: "Address", + grp_jellyfish_tanggal: "Monday – Friday, 08.00 – 16.00 WIB", + grp_jellyfish_telepon: "Phone / Fax", + kontrib_guideline_5: "The editorial team reserves the right", + ape_breadcrumb_profile: "Profile", + ape_breadcrumb_scholarship: "Scholarship", + ape_hero_badge: "Scholarship Program", + ape_hero_title: "Study & Research
Scholarships", + ape_hero_subtitle: "Informatics Engineering, Tanjungpura University", + ape_hero_desc: "Discover various scholarship opportunities to support your academic and research journey. The Informatics Study Program at UNTAN is committed to helping students achieve excellence.", + ape_hero_cta: "View Scholarships", + ape_stats_total: "Total Scholarship
Recipients This Semester", + ape_stats_s1: "Total Undergraduate
Scholarship Recipients", + ape_stats_studi: "Total Postgraduate
Scholarship Recipients", + ape_dir_label: "Scholarship Directory", + ape_dir_title: "Scholarship Directory", + ape_dir_desc: "Find various scholarships from official sources available for active students, alumni, and prospective students of the UNTAN Informatics Study Program.", + ape_dir_guide: "Need Scholarship Aid?", + ape_filter_s1: "Undergraduate", + ape_filter_studi: "Postgraduate", + ape_search_placeholder: "Search Scholarship Name...", + ape_testimonial_title: "What They Say?", + ape_testimonial_desc: "Inspiring stories from scholarship recipients at UNTAN Informatics Study Program", + ape_cta_title: "Want to Know More?", + ape_cta_desc: "Contact the program admin for the latest scholarship and academic program information.", + ape_cta_btn1: "SCHOLARSHIP LIST", + ape_cta_btn2: "Contact Us", + grp_habisiniseminar_page_title: "New Student Admission Informatics UNTAN | Group 02", + grp_habisiniseminar_brand: "Informatics UNTAN New Student Admission", + grp_habisiniseminar_badge: "Selection Path Information", + grp_habisiniseminar_hero_title_1: "New Student Admission", + grp_habisiniseminar_hero_title_2: "Informatics UNTAN", + grp_habisiniseminar_hero_description: "Information and schedule for new student registration via the SNBP, SNBT, and UNTAN Independent Pathways", + grp_habisiniseminar_meta_group: "Group 02", + grp_habisiniseminar_about_title: "Undergraduate Program Selection", + grp_habisiniseminar_about_desc: "The Informatics Study Program at Tanjungpura University opens opportunities for the nation's best students to join through various selection pathways. We seek talented individuals ready to become future technology innovators.", + grp_habisiniseminar_path_snbp_desc: "National Selection Based on Achievement", + grp_habisiniseminar_path_snbt_desc: "National Selection Based on Test", + grp_habisiniseminar_path_smmptn_desc: "Western Region Joint Independent Selection", + grp_habisiniseminar_path_mandiri_desc: "Tanjungpura University Independent Selection", + grp_habisiniseminar_info_jalur_title: "Selection Pathway Information", + grp_habisiniseminar_sidebar_heading: "Choose Pathway", + grp_habisiniseminar_sidebar_data_date: "*Data used as of May 2026", + grp_habisiniseminar_sidebar_link_general: "General Information", + grp_habisiniseminar_sidebar_link_persyaratan: "Requirements", + grp_habisiniseminar_sidebar_link_mekanisme: "Process", + grp_habisiniseminar_sidebar_link_jadwal: "Schedule", + grp_habisiniseminar_sidebar_link_biaya: "Registration & Education Fees", + grp_habisiniseminar_sidebar_link_beasiswa: "Scholarships", + grp_habisiniseminar_snbp_title: "SNBP – National Selection Based on Achievement", + grp_habisiniseminar_info_snbp: "The National Selection Based on Achievement (SNBP) is a state university (PTN) admission pathway organized nationally by SNPMB. SNBP uses a selection mechanism based on secondary-school learning outcomes, including grade reports, academic and non-academic achievements, and additional criteria set by each PTN. This scheme aims to give opportunities to students with outstanding and consistent achievement records. SNBP is an integral part of the government's effort to create an objective, transparent selection process that strengthens school-level learning.", + grp_habisiniseminar_acc_snbp_req_general: "General Requirements to Register for SNBP", + grp_habisiniseminar_acc_snbp_ketentuan: "SNPMB Regulations", + grp_habisiniseminar_acc_snbp_req_accepted: "General Requirements to be Accepted via SNBP", + grp_habisiniseminar_snbp_req_general_body: "", + grp_habisiniseminar_snbp_ketentuan_body: "", + grp_habisiniseminar_snbp_req_accepted_body: "", + grp_habisiniseminar_snbp_mekanisme_text: "

The National Selection Based on Achievement (SNBP) at Tanjungpura University evaluates students' overall academic performance through grade reports, along with achievements that add value to the selection. Evaluated components include:

", + grp_habisiniseminar_tl_snbp_1: "School quota announcement and appeal period", + grp_habisiniseminar_tl_snbp_2: "Account registration and PDSS data entry by schools", + grp_habisiniseminar_tl_snbp_3: "Student SNPMB account registration", + grp_habisiniseminar_tl_snbp_4: "SNBP registration", + grp_habisiniseminar_tl_snbp_5: "SNBP results announcement", + grp_habisiniseminar_tl_snbp_6: "Download SNBP participant card", + grp_habisiniseminar_snbp_fee_desc: "UKT is divided into several categories adjusted to the student's family economic capacity and is paid every semester.", + grp_habisiniseminar_fee_highlight_snbp: "Students admitted through SNBP using KIP-Kuliah will be verified and may be eligible for UKT exemption in accordance with KIP-Kuliah and UNTAN regulations.", + grp_habisiniseminar_snbp_beasiswa_desc: "Students may obtain scholarships from various sources, with application mechanisms according to the rules of each scholarship provider.", + grp_habisiniseminar_snbt_title: "SNBT – National Selection Based on Test", + grp_habisiniseminar_info_snbt: "The National Selection Based on Test (SNBT) is a PTN admission pathway that uses a nationally standardized examination. Participants take a series of tests measuring academic potential, reasoning ability, and readiness for higher education. SNBT assessment is based entirely on test scores — admission is not influenced by economic background, school type (public or private), or regional location. All participants are evaluated on the same standard, ensuring a fair and transparent process. SNBT is one of SNPMB's primary instruments for guaranteeing meritocratic, accountable, and inclusive access to higher education.", + grp_habisiniseminar_acc_snbt_req_general: "General Requirements to Register for SNBT", + grp_habisiniseminar_acc_snbt_utbk: "SNPMB Regulations", + grp_habisiniseminar_acc_snbt_req_prodi: "General Requirements to be Accepted via SNBT", + grp_habisiniseminar_snbt_req_general_body: "", + grp_habisiniseminar_snbt_ketentuan_body: "", + grp_habisiniseminar_snbt_req_accepted_body: "", + grp_habisiniseminar_snbt_mekanisme_full: "

The National Selection Based on Test (SNBT) is conducted through the Computer-Based Written Examination (UTBK), a standardized test measuring cognitive potential, mathematical reasoning, and literacy skills in both Indonesian and English. UTBK aims to assess logical thinking, problem-solving, and high learning readiness so that students can handle the material taught in specific study programs. Equal opportunities are also created for all applicants regardless of their economic background or the quality of their previous school.

The mathematical reasoning section assesses applicants' ability to apply mathematical concepts and procedures to solve contextual problems, and to evaluate and reflect on information so it can be used effectively in daily life. SNBT test instruments are developed by education assessment experts, taking into account technical aspects such as time allocation and difficulty level, so that results objectively and measurably reflect each applicant's true ability.

", + grp_habisiniseminar_tl_snbt_1: "Student SNPMB Account Registration", + grp_habisiniseminar_tl_snbt_2: "SNBT Registration", + grp_habisiniseminar_tl_snbt_3: "UTBK Fee Payment", + grp_habisiniseminar_tl_snbt_4: "UTBK Examination", + grp_habisiniseminar_tl_snbt_5: "SNBT Results Announcement", + grp_habisiniseminar_tl_snbt_6: "UTBK Certificate Download Period", + grp_habisiniseminar_fee_highlight_snbt: "Students admitted through SNBT using KIP-Kuliah will be verified and may be eligible for UKT exemption in accordance with KIP-Kuliah and UNTAN regulations.", + grp_habisiniseminar_snbt_beasiswa_desc: "SNBT students are also eligible to apply for various scholarship programs according to each provider's terms and conditions.", + grp_habisiniseminar_smmptn_title: "SMMPTN West – Western Region Joint Independent Selection", + grp_habisiniseminar_info_smmptn: "The Independent Selection for State Universities in the Western Region (SMMPTN-Barat) is an independent selection process held jointly by several state universities in the western region of Indonesia, utilizing computer-based exams as the basis for assessment. This process provides additional opportunities for high school graduates who have not been accepted through the National Selection Based on Achievement (SNBP) or National Selection Based on Tests (SNBT), either due to limited quotas or inappropriate selection of study programs and state universities. Furthermore, SMMPTN-Barat also serves as an alternative for participants who do not yet have an SNPMB account to still participate in the state university entrance selection process.", + grp_habisiniseminar_smmptn_persyaratan_list: "", + grp_habisiniseminar_smmptn_mekanisme_full: "

The SMMPTN-West selection mechanism is carried out through a computer-based written examination designed to assess the academic ability and higher-education readiness of applicants. The exam uses a standardized testing approach focused on cognitive ability, literacy, and reasoning, ensuring that applicants have critical thinking skills, problem-solving ability, and strong learning capacity. The main types of material covered in the exam are as follows:

", + grp_habisiniseminar_tl_smmptn_1: "Online registration", + grp_habisiniseminar_tl_smmptn_2: "SMMPTN UTBK Examination", + grp_habisiniseminar_tl_smmptn_3: "SMMPTN Selection Results Announcement", + grp_habisiniseminar_smmptn_beasiswa_desc: "Various scholarships are available to students admitted via the SMMPTN-West pathway and may be applied for after the admission process is complete.", + grp_habisiniseminar_mandiri_title: "Mandiri UNTAN – Tanjungpura University Independent Selection", + grp_habisiniseminar_info_mandiri: "UNTAN Mandiri Selection is a new student admission pathway organized directly by Tanjungpura University. This pathway serves as an alternative for prospective students wishing to study at UNTAN through the selection mechanism established by the university in accordance with applicable regulations.", + grp_habisiniseminar_mandiri_persyaratan_list: "", + grp_habisiniseminar_mandiri_mekanisme_full: "

Admission for Diploma and Bachelor's programs through the UNTAN Mandiri Selection pathway is based on Computer-Based Written Examination (UTBK) results and/or additional tests according to the chosen study program. Below are the details of the examinations and additional tests conducted:

Applicants who are absent during the UTBK and/or Additional Tests are considered to have withdrawn from the UNTAN Mandiri Selection process. Applicants under the affirmative action, achievement, or talent scouting pathways will be verified through an interview.

", + grp_habisiniseminar_tl_mandiri_1: "UNTAN Mandiri Selection Registration", + grp_habisiniseminar_tl_mandiri_2: "PIN Generation", + grp_habisiniseminar_tl_mandiri_3: "UTBK Examination — Mandiri Selection Pathway", + grp_habisiniseminar_tl_mandiri_4: "Additional Tests", + grp_habisiniseminar_tl_mandiri_5: "Interviews for Affirmative Action, Achievement, and Talent Scouting Pathways", + grp_habisiniseminar_tl_mandiri_6: "Mandiri Selection Results Announcement", + grp_habisiniseminar_mandiri_beasiswa_desc: "Mandiri pathway students at UNTAN can access various scholarship programs according to the eligibility criteria and application mechanisms of each provider.", + grp_habisiniseminar_section_persyaratan: "Requirements", + grp_habisiniseminar_section_mekanisme: "Admission Process", + grp_habisiniseminar_section_jadwal: "Registration Schedule", + grp_habisiniseminar_section_biaya: "Registration & Education Fees", + grp_habisiniseminar_section_beasiswa: "Scholarships", + grp_habisiniseminar_chart_title: "Applicants and Capacity per Year", + grp_habisiniseminar_chart_legend_applicants: "Number of Applicants", + grp_habisiniseminar_chart_legend_capacity: "Capacity", + grp_habisiniseminar_acc_biaya_pendaftaran: "Registration Fees", + grp_habisiniseminar_acc_biaya_pendidikan: "Education Fees", + grp_habisiniseminar_acc_biaya_ipi: "Institutional Development Fee (IPI)", + grp_habisiniseminar_fee_selection: "Selection Fee", + grp_habisiniseminar_fee_snbt_utbk: "UTBK Fee", + grp_habisiniseminar_fee_smmptn: "SMMPTN Examination Fee", + grp_habisiniseminar_fee_smmptn_payment_note: "Registration fees are paid during the online registration process through designated partner banks: Bank Mandiri, Bank BNI, Bank BTN, and Bank BSI. Registration fees that have been paid CANNOT be refunded under any circumstances.", + grp_habisiniseminar_fee_mandiri_utbk: "Computer-Based Written Test (UTBK) Fee — Mandiri Pathway", + grp_habisiniseminar_fee_mandiri_keterampilan: "Additional Skill Test Fee (Performing Arts Education, Physical Education, and Sports Coaching Education (PKO) study programs)", + grp_habisiniseminar_fee_mandiri_wawancara: "Additional Interview Fee (Medicine Study Program)", + grp_habisiniseminar_fee_mandiri_psikologi: "Additional Psychological Test Fee (Medicine Study Program)", + grp_habisiniseminar_fee_mandiri_payment_note: "All fees are paid through Bank Kalbar host-to-host transfer or teller services. Please note that fees already paid cannot be modified or refunded for any reason.", + grp_habisiniseminar_fee_col_group: "UKT Category", + grp_habisiniseminar_fee_col_cost: "Cost (IDR)", + grp_habisiniseminar_fee_col_ipi_group: "IPI Group", + grp_habisiniseminar_fee_desc_ukt: "Single Tuition Fee (UKT) is charged to all new Diploma and Bachelor's program students and is paid every semester throughout the study period in accordance with the Tanjungpura University academic calendar.", + grp_habisiniseminar_fee_highlight_ukt: "UKT amounts are divided into several groups determined by the economic capacity of the student, parents, or other parties funding the student's education.", + grp_habisiniseminar_fee_ipi_desc: "The Institutional Development Fee (IPI) applies exclusively to students admitted through the SMMPTN-West and Mandiri pathways. IPI is paid once during the entire study period and may be paid in installments over up to four semesters.", + grp_habisiniseminar_fee_ipi_highlight: "Students admitted through the SNBP and SNBT pathways are exempt from the IPI.", + grp_habisiniseminar_scholar_kip: "Indonesia Smart Card for College", + grp_habisiniseminar_scholar_kipk: "KIP-Kuliah", + grp_habisiniseminar_scholar_bca: "BCA Scholarship", + grp_habisiniseminar_scholar_djarum: "Djarum Plus Scholarship", + grp_habisiniseminar_scholar_genbi: "Bank Indonesia Scholarship", + grp_habisiniseminar_scholar_adik: "ADik Scholarship for Papua & 3T", + grp_habisiniseminar_scholar_kip_desc: "KIP-Kuliah is exclusively aimed at students who are economically disadvantaged.", + grp_habisiniseminar_scholar_bca_desc: "A scholarship in the form of the Informatics Engineering Education Program (PPTI) organized by BCA.", + grp_habisiniseminar_scholar_djarum_desc: "Digital talent development scholarship from Djarum Foundation for 4th-semester students and above.", + grp_habisiniseminar_scholar_genbi_desc: "A scholarship from Bank Indonesia that provides educational support and self-development opportunities for outstanding students.", + grp_habisiniseminar_scholar_adik_desc: "Affirmation scholarship for students from Papua, West Papua, and underdeveloped, frontier, and outermost regions.", + grp_habisiniseminar_scholar_kipk_desc: "A government program for economically disadvantaged yet high-achieving students.", + grp_habisiniseminar_scholar_bca_snbt_desc: "The Informatics Engineering Education Program (PPTI) organized by BCA for selected students.", + grp_habisiniseminar_scholar_unggulan_desc: "A scholarship from the Ministry of Education for students with national or international-level achievements.", + grp_habisiniseminar_scholar_untan_smmptn_desc: "Awarded to new students with the highest selection scores from the SMMPTN-West pathway.", + grp_habisiniseminar_scholar_bca_mandiri_desc: "BCA's Informatics Engineering Education Program (PPTI) for students with outstanding academic achievement.", + grp_habisiniseminar_scholar_untan_mandiri_desc: "A scholarship from UNTAN for new students achieving the highest selection scores via the Mandiri pathway.", + grp_habisiniseminar_btn_detail: "View Details", + grp_habisiniseminar_faq_title: "Frequently Asked Questions — New Student Admission", + grp_habisiniseminar_faq_sub: "Find answers to your questions", + grp_habisiniseminar_faq_q1: "How can I check my selection results?", + grp_habisiniseminar_faq_a1: "Selection results are announced online through the official portal according to the scheduled dates.", + grp_habisiniseminar_faq_q2: "What happens if I miss the re-registration or payment deadline?", + grp_habisiniseminar_faq_a2: "Failure to re-register or pay on time may result in your admission status being revoked or treated as a voluntary withdrawal.", + grp_habisiniseminar_faq_q3: "What should I do after being declared accepted?", + grp_habisiniseminar_faq_a3: "Accepted applicants must complete re-registration and fulfill all administrative requirements. Further information is available on the official Informatics UNTAN website and social media channels.", + grp_habisiniseminar_faq_q4: "How many specializations does this study program offer?", + grp_habisiniseminar_faq_a4: "This program offers four specializations: Artificial Intelligence (AI), Software Engineering, Networks & Security, and Information Systems. Information regarding specialization groups can be accessed here.", + grp_habisiniseminar_faq_q5: "Will students gain practical or project experience during their studies?", + grp_habisiniseminar_faq_a5: "Yes. Students will take part in various practicum sessions, project assignments, and group work designed to build technical skills and real-world problem-solving abilities.", + grp_habisiniseminar_faq_q6: "What is campus life like at Informatics UNTAN?", + grp_habisiniseminar_faq_a6: "The campus environment supports both academic and non-academic activities, with numerous student organizations and technology communities. To learn more about campus life and student activities, please visit the related page here.", + grp_habisiniseminar_faq_q7: "Do Informatics UNTAN graduates have good career prospects?", + grp_habisiniseminar_faq_a7: "Graduates have broad career opportunities across fields such as software development, data analytics, network security, and technology entrepreneurship.", + grp_habisiniseminar_faq_q8: "Can Informatics UNTAN students participate in competitions?", + grp_habisiniseminar_faq_a8: "Yes. Students are actively encouraged to participate in technology competitions at local, national, and international levels. Information on competitions and student achievements is available here.", + grp_habisiniseminar_month_jan: "January", + grp_habisiniseminar_month_feb: "February", + grp_habisiniseminar_month_mar: "March", + grp_habisiniseminar_month_apr: "April", + grp_habisiniseminar_month_mei: "May", + grp_habisiniseminar_month_jun: "June", + grp_habisiniseminar_month_jul: "July", + grp_habisiniseminar_month_des: "December", + grp_habisiniseminar_month_des_jan: "December – January", + grp_habisiniseminar_month_jan_feb: "January – February", + grp_habisiniseminar_month_jan_apr: "January – April", + grp_habisiniseminar_month_feb_apr: "February – April", + grp_habisiniseminar_month_feb_mar: "February – March", + grp_habisiniseminar_month_mar_apr: "March – April", + grp_habisiniseminar_month_mar_mei: "Mar – May", + grp_habisiniseminar_month_apr_mei: "April – May", + grp_habisiniseminar_month_mei_jun: "May – June", + grp_habisiniseminar_month_jun_jul: "June – July", + grp_m4n1fest_hero_label: "INFORMATICS STUDY PROGRAM · UNTAN PONTIANAK", + grp_m4n1fest_hero_title_1: "Informatics", + grp_m4n1fest_hero_title_2: "Alumni", + grp_m4n1fest_hero_subtitle: "UNTAN Pontianak", + grp_m4n1fest_hero_desc: "Informatics UNTAN graduates are now working across Indonesia and the world. Discover their footprints, find inspiration from their journeys, and be part of this network.", + grp_m4n1fest_hero_btn_stats: "View Alumni Statistics", + grp_m4n1fest_hero_btn_join: "Join Alumni Group", + grp_m4n1fest_hero_stat_alumni: "TOTAL ALUMNI", + grp_m4n1fest_hero_stat_alumni_desc: "data obtained from alumni recorded in tracer study", + grp_m4n1fest_hero_stat_batch: "CLASSES", + grp_m4n1fest_hero_stat_company: "COMPANIES", + grp_m4n1fest_eyebrow_achievers: "High Achievers", + grp_m4n1fest_title_achievers: "Informatics Alumni", + grp_m4n1fest_sub_achievers: "They prove that Informatics UNTAN graduates are capable of competing and working at the highest level.", + grp_m4n1fest_ach1_batch: "Class of 2005", + grp_m4n1fest_ach1_pos: "Full-time Lecturer, Univ Muhammadiyah Pontianak & Researcher, Universidad de Granada", + grp_m4n1fest_ach1_ach1: "M.Sc. Computer Engineering, Marmara University", + grp_m4n1fest_ach1_ach2: "Ph.D. in Info & Comm Tech, Universidad de Granada", + grp_m4n1fest_ach1_ach3: "Experienced in cross-country AI research", + grp_m4n1fest_ach2_batch: "Informatics UNTAN", + grp_m4n1fest_ach2_pos: "Software Developer, Traxpay GmbH, Germany", + grp_m4n1fest_ach2_ach1: "M.Sc. Computer Science, Saarland University", + grp_m4n1fest_ach2_ach2: "Working in the European FinTech industry", + grp_m4n1fest_ach2_ach3: "Expert in digital payment systems & ERP integration", + grp_m4n1fest_ach3_batch: "Class of 2009", + grp_m4n1fest_ach3_pos: "Product Manager, CX-Oriented Tech, Ai Tech", + grp_m4n1fest_ach3_ach1: "AI Practitioner focusing on Voice AI", + grp_m4n1fest_ach3_ach2: "Designing technology products based on Customer Experience", + grp_m4n1fest_ach3_ach3: "AI solutions innovator across various platforms", + grp_m4n1fest_eyebrow_career: "Career Path", + grp_m4n1fest_title_career: "Where Do Alumni Work?", + grp_m4n1fest_sub_career: "Distribution of industries where alumni pursue their careers.", + grp_m4n1fest_career_gov: "Government agencies", + grp_m4n1fest_career_private: "Private companies", + grp_m4n1fest_career_entrepreneur: "Entrepreneurs/own companies", + grp_m4n1fest_career_soes: "State-Owned Enterprises (BUMN/BUMD)", + grp_m4n1fest_career_ngo: "Non-profit organizations/NGOs", + grp_m4n1fest_career_note: "* Data obtained from Untan Tracer Study. Last updated May 2026.", + grp_m4n1fest_career_logo_title: "Companies Where Alumni Pursue Careers", + grp_m4n1fest_logo_bca: "BCA", + grp_m4n1fest_logo_bri: "BRI", + grp_m4n1fest_logo_telkom: "Telkom Indonesia", + grp_m4n1fest_logo_pln: "PLN", + grp_m4n1fest_logo_indofood: "Indofood Fortuna", + grp_m4n1fest_logo_astra: "Astra Daihatsu", + grp_m4n1fest_logo_unilever: "Unilever", + grp_m4n1fest_logo_grab: "Grab", + grp_m4n1fest_logo_telkomsel: "Telkomsel", + grp_m4n1fest_logo_garuda: "Garuda Indonesia", + grp_m4n1fest_logo_bulog: "Perum BULOG", + grp_m4n1fest_logo_others: "Others", + grp_m4n1fest_eyebrow_skill: "Skill Distribution", + grp_m4n1fest_title_skill: "Skills Mastered by Alumni", + grp_m4n1fest_sub_skill: "Skill distribution based on alumni contribution form data — a clear picture of real competencies of Informatics UNTAN graduates.", + grp_m4n1fest_skill_not_listed: "Not Listed Here Yet?", + grp_m4n1fest_skill_not_listed_desc: "Help us complete the skill database of Informatics UNTAN alumni. Contribute your expertise data to map the true potential of graduates across various industrial sectors.", + grp_m4n1fest_skill_btn_contrib: "Fill Out Contribution Form", + grp_m4n1fest_eyebrow_stats: "Alumni Statistics", + grp_m4n1fest_title_stats: "Number of Alumni by Graduation Year", + grp_m4n1fest_sub_stats: "Data on graduates by semester period and graduation year.", + grp_m4n1fest_table_head_period: "Period", + grp_m4n1fest_table_head_amount: "Amount", + grp_m4n1fest_table_source: "Source: BAK UNTAN", + grp_m4n1fest_chart_title: "Graduation Chart", + grp_m4n1fest_eyebrow_testi: "Alumni Testimonials", + grp_m4n1fest_title_testi: "What They Say About Informatics UNTAN", + grp_m4n1fest_testi1_text: "\"For prospective Informatics Untan students, my advice is to keep developing yourself and not be easily satisfied. College is the starting point to open up wider opportunities. Focus on your areas of interest, improve specific skills, expand your network, and master foreign languages as an added value. With commitment and strong will, opportunities for career or further study at the global level will be wide open.\"", + grp_m4n1fest_testi1_role: "Product Manager, Ai Practitioner · Class of 2009", + grp_m4n1fest_testi2_text: "\"The foundation you build at Informatics Untan is very valuable. The ability to think algorithmically and solve problems sharpened during college is a provision that will continue to be relevant wherever you pursue a career. Don't be afraid to dream big. If I — who started in Pontianak — could reach Frankfurt and contribute to the European FinTech industry, you can too. The important thing is to keep learning, dare to take opportunities, and never underestimate the quality of education you receive at Untan.\"", + grp_m4n1fest_testi2_role: "Software Developer, Traxpay GmbH", + grp_m4n1fest_testi3_text: "\"The foundation we obtain from the campus and Student Association is the best place to learn resilience. Make that experience a means to stay open to changes. If we are able to adapt well, our limits are no longer determined by geography, but how far we dare to step.\"", + grp_m4n1fest_testi3_role: "Full-time Lecturer, UMP & Researcher, Universidad de Granada", + grp_m4n1fest_eyebrow_events: "Annual Events", + grp_m4n1fest_title_events: "Meet Us Every Year", + grp_m4n1fest_sub_events: "HMIF Informatics UNTAN organizes two major annual events that always invite and gather alumni.", + grp_m4n1fest_event1_title: "SKBI Ramadhan", + grp_m4n1fest_event1_meta: "Informatics Family Gathering", + grp_m4n1fest_event1_desc: "SKBI Ramadhan is a gathering event that brings together the entire big family of Informatics UNTAN — active students, lecturers, and alumni from various batches. This activity serves as a warm gathering moment in the month of Ramadhan, strengthening the bonds of alumni with the academic community through social activities, breaking fast together, and networking.", + grp_m4n1fest_event1_note: "Full info on HMIF Instagram", + grp_m4n1fest_event2_title: "INFORMERS", + grp_m4n1fest_event2_meta: "Informatics Anniversary", + grp_m4n1fest_event2_desc: "INFORMERS is the celebration of the Anniversary of the Informatics Study Program at UNTAN. This big event hosts various student-level academic and non-academic competition activities, IT innovation exhibitions, music entertainment, to a peak night that gathers all academics and alumni in the spirit of dedication and togetherness.", + grp_m4n1fest_event2_note: "Monitor IG for schedule & registration", + grp_m4n1fest_eyebrow_join: "Join IKA", + grp_m4n1fest_title_join: "Join the Alumni Group", + grp_m4n1fest_sub_join: "Register yourself to join the WhatsApp group of the Informatics UNTAN Alumni Association (IKA). Connect with hundreds of alumni and get job vacancy updates, activities, and collaboration opportunities.", + grp_m4n1fest_step1_head: "Fill Out Registration Form", + grp_m4n1fest_step1_desc: "Enter your name, student ID (NIM), phone number, email, work status, and 3 primary skills.", + grp_m4n1fest_step2_head: "IKA Admin Verification", + grp_m4n1fest_step2_desc: "IKA Admin will verify that you are an alumnus of Informatics UNTAN within 1–2 business days.", + grp_m4n1fest_step3_head: "Enter IKA WhatsApp Group", + grp_m4n1fest_step3_desc: "Once verified, you will be invited to the IKA WhatsApp group and instantly connect with hundreds of alumni!", + grp_m4n1fest_wa_badge: "Informatics UNTAN IKA Group", + grp_m4n1fest_wa_title: "Informatics UNTAN IKA", + grp_m4n1fest_wa_sub: "500+ active members", + grp_m4n1fest_form_title: "IKA Registration Form", + grp_m4n1fest_form_sub: "Fill out the data below to join the alumni WhatsApp group.", + grp_m4n1fest_form_nama: "Full Name *", + grp_m4n1fest_form_nim: "Student ID (NIM)", + grp_m4n1fest_form_angkatan: "Class of *", + grp_m4n1fest_form_hp: "Phone / WhatsApp No. *", + grp_m4n1fest_form_email: "Active Email *", + grp_m4n1fest_form_status: "Current Status *", + grp_m4n1fest_form_status_opt1: "— Select Status —", + grp_m4n1fest_form_status_opt2: "Working", + grp_m4n1fest_form_status_opt3: "Entrepreneur / Freelance", + grp_m4n1fest_form_status_opt4: "Continuing Studies", + grp_m4n1fest_form_status_opt5: "Looking for a Job", + grp_m4n1fest_form_instansi: "Workplace / Institution (optional)", + grp_m4n1fest_form_skill_title: "3 Primary Skills Mastered *", + grp_m4n1fest_form_skill_sub: "Select 3 skills you are most proficient in.", + grp_m4n1fest_form_skill_opt1: "— Select —", + grp_m4n1fest_form_persetujuan: "Consent *", + grp_m4n1fest_form_persetujuan_txt: "I agree that the data I entered is true and willing to be verified by the Informatics UNTAN IKA admin.", + grp_m4n1fest_form_btn: "Register Now", + grp_m4n1fest_form_msg: "Data saved instantly · Processing 1-2 business days", + grp_m4n1fest_eyebrow_faq: "FAQ", + grp_m4n1fest_title_faq: "Questions About Alumni", + grp_m4n1fest_faq_q1: "How do I join the IKA alumni group?", + grp_m4n1fest_faq_a1: "Fill out the registration form in the \"Join IKA\" section above with your full name, class year, WhatsApp number, and active email. Once verified by the admin within 1-2 business days, you will be invited to the Informatics UNTAN IKA WhatsApp group.", + grp_m4n1fest_faq_q2: "How long is the IKA registration verification process?", + grp_m4n1fest_faq_a2: "The verification process usually takes 1-2 business days. The IKA admin will verify that you are indeed an alumnus of Informatics UNTAN based on the name and class year filled out.", + grp_m4n1fest_faq_q3: "What are the benefits of joining the IKA alumni group?", + grp_m4n1fest_faq_a3: "In the IKA group, you can access job vacancy updates from fellow alumni, info on annual activities like SKBI Ramadhan and INFORMERS, project collaborations, sharing career experiences, and building professional connections with hundreds of alumni from various classes and industries.", + grp_m4n1fest_faq_q4: "Are there job vacancy or internship updates in IKA?", + grp_m4n1fest_faq_a4: "Yes! One of the main benefits of the IKA group is that alumni who are already working often share vacancy updates from their companies. Many IKA members get their first jobs or change careers through referrals from fellow alumni in this group.", + grp_m4n1fest_faq_q5: "Is there an industry collaboration program from the Informatics Program?", + grp_m4n1fest_faq_a5: "The Informatics UNTAN Study Program has a mandatory Internship (KP) program that connects students with the industry. Latest info on industry collaborations can be followed through Instagram @informatika.untan.", + grp_m4n1fest_faq_q6: "How do I participate in SKBI Ramadhan or INFORMERS activities?", + grp_m4n1fest_faq_a6: "Registration and event info for SKBI Ramadhan can be found on Instagram @hmif_ftuntan. For INFORMERS, monitor Instagram @informershmif. As an IKA group member, you will also receive direct notifications whenever there are activities involving alumni.", + grp_m4n1fest_faq_q7: "Who can register for IKA?", + grp_m4n1fest_faq_a7: "All alumni of the Informatics Study Program of Universitas Tanjungpura from all classes can register for IKA. There are no restrictions on class year, employment status, or domicile. As long as you are an Informatics UNTAN graduate, you are eligible to join!", + grp_m4n1fest_eyebrow_contact: "Contact & Social Media", + grp_m4n1fest_title_contact: "Find Us on Instagram", + grp_m4n1fest_sub_contact: "Follow our official Instagram accounts for the latest information on alumni activities, job vacancy updates, and Informatics UNTAN Study Program updates.", + grp_m4n1fest_ig_main_label: "Prodi Official Instagram", + grp_m4n1fest_ig_main_desc: "Academic updates, activities, and alumni info", + grp_m4n1fest_ig_hmif_desc: "HMIF & SKBI Ramadhan", + grp_m4n1fest_ig_informers_desc: "INFORMERS", + grp_m4n1fest_cta_box_title: "Ready to join?", + grp_m4n1fest_cta_box_desc: "Scroll up and fill out the IKA registration form now. Free, fast, and instantly connect with hundreds of alumni!", + grp_m4n1fest_cta_box_btn: "Register for IKA Now", + + grp_limaem_hero_badge: "COMMUNITY & LEADERSHIP", + grp_limaem_hero_title: "Student Life & Student Association (HMIF)", + grp_limaem_hero_desc: "Building a collaborative ecosystem where technology meets creativity, shaping the industry leaders of tomorrow.", + grp_limaem_hero_btn: "View Organizational Structure", + + grp_limaem_about_title: "A Space to Grow for Informatics Students", + grp_limaem_about_p1: "The Informatics Student Association (HMIF) of Tanjungpura University is more than just an organization — it is a home for technology leaders. We believe that formal education is the foundation, but community is the accelerator.", + grp_limaem_about_p2: "Through various initiatives, we bridge the gap between academic curriculum and industry needs, while maintaining the spirit of kinship and academic integrity.", + grp_limaem_about_stat_students: "Active Students", + grp_limaem_about_stat_partners: "PARTNERS", + grp_limaem_about_badge: "Inclusivity & Collaboration", + + grp_limaem_partners_title: "Partners and Collaborators", + + grp_limaem_divisi_title: "HMIF Organizational Divisions", + grp_limaem_divisi_desc: "An organizational structure designed for efficiency and capacity building across various strategic fields.", + grp_limaem_divisi_b1_label: "Division 1", + grp_limaem_divisi_b1_title: "Student Development and Cadre Regeneration", + grp_limaem_divisi_b1_desc: "This division organizes structured cadre programs, regular mentoring, and leadership and soft-skill training. Its activities include new member onboarding, leadership workshops, collaborative projects, and community service that fosters social responsibility. The focus is on preparing competent cadres and sustaining organizational regeneration.", + grp_limaem_divisi_b2_label: "Division 2", + grp_limaem_divisi_b2_title: "Communication Media and Digital Content", + grp_limaem_divisi_b2_desc: "This division manages HMIF's communication strategy and content production, including editorial planning, social media management, and event documentation. Its work programs include creating promotional materials, publishing activities, and strengthening digital identity to deliver consistent and engaging organizational information.", + grp_limaem_divisi_b3_label: "Division 3", + grp_limaem_divisi_b3_title: "Innovation and Technology Competition", + grp_limaem_divisi_b3_desc: "This division facilitates competition preparation and technical project development through coaching clinics, project-based bootcamps, and study clubs for Competitive Programming, Software Development, and Data/ML. Activities include internal selection, competitive coaching, and digitalization initiatives such as website and dashboard development to support HMIF's innovation ecosystem.", + grp_limaem_divisi_b4_label: "Division 4", + grp_limaem_divisi_b4_title: "Organizational Development", + grp_limaem_divisi_b4_desc: "This division strengthens internal governance and capabilities through board training, simple SOP drafting, and work process evaluation. Its programs include organizational culture development, improved coordination effectiveness, and professionalism initiatives, making HMIF operations more structured and efficient.", + grp_limaem_divisi_b5_label: "Division 5", + grp_limaem_divisi_b5_title: "Community Engagement", + grp_limaem_divisi_b5_desc: "This division designs and implements impactful community service programs and social actions, including community assistance and humanitarian aid. Activities cover field needs assessment, structured implementation, and impact evaluation to ensure sustainability and tangible benefits for the community.", + grp_limaem_divisi_b6_label: "Division 6", + grp_limaem_divisi_b6_title: "Entrepreneurship and Partnerships", + grp_limaem_divisi_b6_desc: "This division develops organizational business units, designs creative funding schemes, and establishes strategic partnerships to support the sustainability of HMIF programs. Its work units include business opportunity research, partnership proposal development, and sponsorship management to enhance financial independence and professional networking access.", + + grp_limaem_kegiatan_title: "Flagship Events", + grp_limaem_kegiatan_subtitle: "Annual events that have become icons of Informatics UNTAN.", + grp_limaem_kegiatan_c1_title: "Informatics Family Gathering", + grp_limaem_kegiatan_c1_desc: "An annual iftar gathering held with the entire Informatics UNTAN family, a tradition maintained every year.", + grp_limaem_kegiatan_c2_title: "GEMASTIK", + grp_limaem_kegiatan_c2_desc: "A prestigious national-level IT competition held annually by the Ministry. A platform for Informatics UNTAN students to showcase their skills in programming, cyber security, data mining, and UI/UX.", + grp_limaem_kegiatan_c3_title: "Independence Day Celebration", + grp_limaem_kegiatan_c3_desc: "A celebration of Indonesian Independence Day filled with exciting competitions. A fun event to strengthen nationalism and the sense of kinship among Informatics students.", + grp_limaem_kegiatan_c4_title: "HMIF Bootcamp", + grp_limaem_kegiatan_c4_desc: "An intensive technical skill development program for Informatics students, guided by professional mentors from the industry.", + grp_limaem_kegiatan_c5_title: "UPC", + grp_limaem_kegiatan_c5_desc: "A Programming Competition to sharpen the coding skills of Informatics students.", + grp_limaem_kegiatan_c6_title: "Study Excursion", + grp_limaem_kegiatan_c6_desc: "Study Excursion is a valuable experience for 5th semester students to study outside the campus, broaden their horizons, get to know new atmospheres, and create memorable memories together during the trip.", + grp_limaem_kegiatan_c7_title: "INFORMERS", + grp_limaem_kegiatan_c7_desc: "The anniversary celebration of Informatics UNTAN, a moment of togetherness and pride for the entire Informatics family.", + + grp_limaem_sc_title: "STUDY CLUBS", + grp_limaem_sc_arise_title: "ARISE", + grp_limaem_sc_arise_desc: "Learn logic and algorithms together, where you can grow and open opportunities to participate in programming competitions at various levels.", + grp_limaem_sc_minerva_title: "MINERVA", + grp_limaem_sc_minerva_desc: "Uncover hidden potential from mountains of data! Here we explore Machine Learning algorithms, dissect classification techniques, and train predictive models to generate meaningful insights.", + grp_limaem_sc_sd_title: "Software Development (Coming Soon)", + grp_limaem_sc_sd_desc: "Turn your ideas into real digital products! We will code together — from designing interactive frontends and building robust backends, to integrating various APIs to create modern web applications.", + + grp_limaem_galeri_title: "Fun Gallery", + + grp_limaem_cta_title: "Have Questions About HMIF?", + grp_limaem_cta_desc: "Join HMIF to expand your network, sharpen your skills, and create real impact.", + grp_limaem_cta_btn: "Contact Us", + grp_limaem_cta_info1_label: "Secretariat", + grp_limaem_cta_info1_val: "Engineering Faculty Building, 2nd Floor, Jl. Prof. Dr. H. Hadari Nawawi", + grp_limaem_cta_info2_label: "Office Hours", + grp_limaem_cta_info2_val: "Monday - Friday, 08:00 - 17:00 WIB", + grp_limaem_cta_info3_label: "Social Media", + grp_limaem_cta_info3_val: "@hmif.untan, YouTube: HMIF UNTAN TV", + // --- Group C4A: Supporting Staff & SOP --- grp_c4a_hero_tag: "ACADEMIC SERVICES \u2022 INFORMATICS UNTAN", grp_c4a_hero_title: "Supporting Staff &
Standard Operating Procedures (SOP)", @@ -1310,4 +2342,4 @@ document.addEventListener('DOMContentLoaded', () => { btn.addEventListener('click', toggleLanguage); }); applyLang(); -}); +}); \ No newline at end of file diff --git a/groups/ACAAB/.gitignore b/groups/ACAAB/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/groups/ACAAB/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/groups/ACAAB/hero.png b/groups/ACAAB/hero.png new file mode 100644 index 0000000..33f70e0 Binary files /dev/null and b/groups/ACAAB/hero.png differ diff --git a/groups/ACAAB/index.html b/groups/ACAAB/index.html index a781215..13e4129 100644 --- a/groups/ACAAB/index.html +++ b/groups/ACAAB/index.html @@ -1,5 +1,6 @@ - + + @@ -7,9 +8,12 @@ - + - + @@ -997,7 +1002,7 @@
- +
-
+
- Tugas Halaman Statis + Tugas + Halaman Statis

Sorotan dan Prestasi Karya Mahasiswa

-

Kelompok 06 — ACAAB  ·  Informatika UNTAN 2026

+

Kelompok 06 — ACAAB  ·  Informatika UNTAN 2026 +

-

Tulis konten halaman kelompok di sini.

-

Subjudul

-

Gunakan paragraf, list, tabel, gambar, dan elemen konten lainnya.

+ + + + + +
+ +
+
+ +
+
+ +
+ MAHASISWA & PRESTASI +

Sorotan dan Prestasi Karya Mahasiswa

+

Temukan pencapaian luar biasa mahasiswa Informatika + UNTAN.

+
+ +
+
+
+
+ Mahasiswa Informatika UNTAN + +
+
+
+
+
+
+
+
+ + + +
+ + +
+
+ +
+ +
+
+ Total Prestasi +
+
+ +
+
+ 0
+
Prestasi
Mahasiswa
+
+ +
+
+ 0
+
Internasional
+
+ +
+
+ 0
+
Nasional
+
+ +
+
+ 0
+
Regional
+
+
+
+ + +
+
+ Total Portofolio +
+
+ +
+
+ 0
+
Proyek
Mahasiswa
+
+
+
+
+ +
+ +

+

+
+ + +
+
+

Prestasi Mahasiswa

+

Daftar pencapaian dan penghargaan yang diraih oleh mahasiswa Informatika UNTAN di berbagai kompetisi.

+
+ + +
+ +
+
+ + Filter Prestasi +
+ +
+ +
+ + +
+ +
+ + + +
+
+ + +

+ + +
+
+ + +
+ + +
+
+ + +
+
+ +
+
+

Top Projects Portofolio

+

Jelajahi inovasi digital dan aplikasi nyata yang dikembangkan oleh mahasiswa Informatika UNTAN.

+
+
+ + +
+
+
+ + Filter Portofolio +
+ +
+
+ + +
+
+ + +
+
+ + +

← Geser untuk melihat lebih banyak →

+ + + + + +
+ + +
+ + +
+ +
+
+
+ + +
+
+ +
+
+ +
+ HUBUNGI KAMI +

Ingin Tahu Lebih Lanjut?

+

Tertarik bergabung dengan Program Studi + Informatika UNTAN, mendaftarkan prestasi mahasiswa, atau menjalin kerja sama? + Hubungi kami sekarang.

+
+ + +
+
+
+
+ + +
+
@@ -1215,8 +1543,8 @@
- Footer Illustration + Footer Illustration
@@ -1312,27 +1640,30 @@ })(); - + - + + + - \ No newline at end of file + + diff --git a/groups/ACAAB/localization.js b/groups/ACAAB/localization.js new file mode 100644 index 0000000..97ee207 --- /dev/null +++ b/groups/ACAAB/localization.js @@ -0,0 +1,123 @@ +/** + * 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); +}()); diff --git a/groups/ACAAB/main.js b/groups/ACAAB/main.js new file mode 100644 index 0000000..2998abc --- /dev/null +++ b/groups/ACAAB/main.js @@ -0,0 +1,837 @@ +/** + * main.js — ACAAB Prestasi & Karya Mahasiswa + * Vanilla JS: fetch data Directus CMS, render sections, handle interactions. + */ + +// ─── Constants ──────────────────────────────────────────────────────────────── + +const API_BASE = 'https://api.ifuntanhub.dev'; +const LANG_KEY = 'if_untan_lang'; + +const BIDANG_MAP = { + 1: 'Kecerdasan Buatan', 2: 'Cyber Security', 3: 'Data', + 4: 'Software Engineering', 5: 'Competitive Programming', 6: 'IoT', + 7: 'Smart City', 8: 'Web3', 9: 'Karya Tulis Ilmiah', 10: 'Robotik', +}; + +// ─── Utilities ──────────────────────────────────────────────────────────────── + +function escHtml(str) { + if (str === null || str === undefined) return ''; + return String(str) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +function getLang() { + try { + return localStorage.getItem(LANG_KEY) || + (navigator.language && navigator.language.startsWith('en') ? 'en' : 'id'); + } catch (_) { return 'id'; } +} + +// Baca dari translations global yang didefinisikan localization.js +function t(key) { + try { + const lang = getLang(); + if (typeof translations !== 'undefined' && translations[lang] && translations[lang][key]) { + return translations[lang][key]; + } + } catch (_) {} + return key; +} + +// ─── Data Transformation ────────────────────────────────────────────────────── + +function extractRank(title) { + const m = (title || '').match(/(Juara\s+\d+|Juara\s+Harapan\s*\d*|Top\s+\d+|Finalis|Peringkat\s+\d+|Medali\s+\w+)/i); + return m ? m[0] : 'Pemenang'; +} + +function extractLevel(title, category) { + const text = ((title || '') + ' ' + (category || '')).toLowerCase(); + if (/internasional|international|global|world|asean|unesco/.test(text)) return 'Internasional'; + if (/regional|kalbar|kalimantan|provinsi|kota|kabupaten|lokal/.test(text)) return 'Regional'; + return 'Nasional'; +} + +function extractCompetition(title) { + return (title || '') + .replace(/^(Juara\s+\d+|Juara\s+Harapan\s*\d*|Top\s+\d+|Finalis|Peringkat\s+\d+|Medali\s+\w+)\s+(di\s+|pada\s+|dalam\s+)?/i, '') + .trim(); +} + +function getImageUrl(item, seed) { + if (item.galeri && Array.isArray(item.galeri) && item.galeri.length > 0) { + const first = item.galeri[0]; + const imgId = typeof first === 'string' ? first : (first.directus_files_id || first.id); + if (imgId) return `${API_BASE}/assets/${imgId}`; + } + if (item.foto) return `${API_BASE}/assets/${item.foto}`; + return `https://picsum.photos/seed/${seed}/800/600`; +} + +function transformAchievement(item) { + const id = String(item.id); + const title = item.nama_prestasi || 'Untitled Achievement'; + + let category = 'Umum'; + if (item.bidang_id) { + category = (typeof item.bidang_id === 'object' && item.bidang_id.nama_bidang) + ? item.bidang_id.nama_bidang + : (BIDANG_MAP[item.bidang_id] || 'Umum'); + } + + const rank = extractRank(title); + const level = extractLevel(title, category); + const competition = extractCompetition(title); + const year = parseInt(item.tahun) || new Date().getFullYear(); + const anggota = Array.isArray(item.anggota) ? item.anggota : []; + const team = item.nama_tim || (anggota.length > 0 ? anggota[0].nama : 'Mahasiswa'); + const avatar = (item.nama_tim || title || 'IF').substring(0, 2).toUpperCase(); + const description = item.deskripsi || + `${team} berhasil meraih prestasi luar biasa sebagai ${rank} dalam ajang ${competition} tahun ${year} pada bidang ${category.toLowerCase()}.`; + + return { + id, title, category, competition, rank, year, level, description, + team, + nim: item.nim || '', + generation: item.angkatan ? `${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(() => + `
` + ).join(''); +} + +function showError(containerId, msg) { + const el = document.getElementById(containerId); + if (el) el.innerHTML = `

${escHtml(msg)}

`; +} + +// ─── Stats ──────────────────────────────────────────────────────────────────── + +function initStats() { + updateStats(); + + const statsEl = document.getElementById('acaab-stats'); + if (!statsEl) return; + + const intl = achievements.filter(a => a.level === 'Internasional').length; + const national = achievements.filter(a => a.level === 'Nasional').length; + const regional = achievements.filter(a => a.level === 'Regional').length; + + const observer = new IntersectionObserver(entries => { + entries.forEach(entry => { + if (!entry.isIntersecting) return; + animateCount('stat-total-ach', achievements.length, 1600); + animateCount('stat-total-proj', projects.length, 1600); + animateCount('stat-intl', intl, 1200); + animateCount('stat-national', national, 1350); + animateCount('stat-regional', regional, 1500); + observer.disconnect(); + }); + }, { threshold: 0.4 }); + + observer.observe(statsEl); +} + +function updateStats() { + const set = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; }; + set('stat-total-ach', achievements.length); + set('stat-total-proj', projects.length); + set('stat-intl', achievements.filter(a => a.level === 'Internasional').length); + set('stat-national', achievements.filter(a => a.level === 'Nasional').length); + set('stat-regional', achievements.filter(a => a.level === 'Regional').length); +} + +function animateCount(elId, target, duration) { + const el = document.getElementById(elId); + if (!el) return; + const start = performance.now(); + (function step(now) { + const p = Math.min((now - start) / duration, 1); + const eased = 1 - Math.pow(1 - p, 4); // easeOutQuart + el.textContent = Math.round(target * eased).toLocaleString(); + if (p < 1) requestAnimationFrame(step); + })(start); +} + +function updateDataNote() { + const el = document.getElementById('acaab-data-note'); + if (!el) return; + const lang = getLang(); + const locale = lang === 'en' ? 'en-US' : 'id-ID'; + const date = new Date().toLocaleDateString(locale, { day: 'numeric', month: 'long', year: 'numeric' }); + el.textContent = `${t('grp_acaab_stats_data_note')} ${date}`; +} + +// ─── Achievements ───────────────────────────────────────────────────────────── + +const ACH_INIT = 3; +const ACH_MORE = 3; + +const LEVEL_CLASS = { + Internasional: 'acaab-badge-intl', + Nasional: 'acaab-badge-nasional', + Regional: 'acaab-badge-regional', +}; + +function initAchievements() { + buildAchFilterOptions(); + renderAchievements(); + bindAchEvents(); +} + +function buildAchFilterOptions() { + const levelSel = document.getElementById('acaab-ach-level'); + const catSel = document.getElementById('acaab-ach-cat'); + const yearSel = document.getElementById('acaab-ach-year'); + if (!levelSel || !catSel || !yearSel) return; + + const levels = ['Internasional', 'Nasional', 'Regional']; + const cats = [...new Set(achievements.map(a => a.category))].sort(); + const years = [...new Set(achievements.map(a => String(a.year)))].filter(Boolean).sort((a, b) => b - a); + + levelSel.innerHTML = `` + + levels.map(l => ``).join(''); + catSel.innerHTML = `` + + cats.map(c => ``).join(''); + yearSel.innerHTML = `` + + years.map(y => ``).join(''); +} + +function filterAchievements() { + const q = achFilter.search.toLowerCase(); + return achievements.filter(a => { + const matchSearch = !q || [a.title, a.team, a.category, a.competition] + .some(s => (s || '').toLowerCase().includes(q)); + const matchLevel = !achFilter.level || a.level === achFilter.level; + const matchCat = !achFilter.cat || a.category === achFilter.cat; + const matchYear = !achFilter.year || String(a.year) === achFilter.year; + return matchSearch && matchLevel && matchCat && matchYear; + }); +} + +function renderAchievements() { + const grid = document.getElementById('acaab-ach-grid'); + const countEl = document.getElementById('acaab-ach-count'); + const moreBtn = document.getElementById('acaab-ach-more'); + const lessBtn = document.getElementById('acaab-ach-less'); + const resetBtn= document.getElementById('acaab-ach-reset'); + if (!grid) return; + + const filtered = filterAchievements(); + const visible = filtered.slice(0, achVisible); + const hasFilter = achFilter.search || achFilter.level || achFilter.cat || achFilter.year; + + if (countEl) countEl.textContent = + `${t('grp_acaab_ach_showing')} ${filtered.length} ${t('grp_acaab_ach_results')}`; + if (resetBtn) resetBtn.style.display = hasFilter ? 'flex' : 'none'; + + if (filtered.length === 0) { + grid.innerHTML = `

${escHtml(t('grp_acaab_ach_empty'))}

`; + if (moreBtn) moreBtn.style.display = 'none'; + if (lessBtn) lessBtn.style.display = 'none'; + return; + } + + grid.innerHTML = visible.map((a, i) => achCardHtml(a, i)).join(''); + + if (moreBtn) moreBtn.style.display = achVisible < filtered.length ? 'inline-block' : 'none'; + if (lessBtn) lessBtn.style.display = achVisible > ACH_INIT ? 'inline-block' : 'none'; + + grid.querySelectorAll('.acaab-ach-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 ` +
+
+ +
+ ${escHtml(a.title)} + +
+ + ${escHtml(a.rank)} +
+
+ +
+ +
+ ${escHtml(a.category)} + + ${escHtml(String(a.year))} + +
+ +

${escHtml(a.title)}

+ +

${escHtml(a.description)}

+ +
+
+ +
+
${escHtml(a.avatar)}
+
+ ${escHtml(a.team)} + ${escHtml(a.generation)} +
+
+ + ${escHtml(a.level)} +
+ + +
+
+
`; +} + +function bindAchEvents() { + const search = document.getElementById('acaab-ach-search'); + const level = document.getElementById('acaab-ach-level'); + const cat = document.getElementById('acaab-ach-cat'); + const year = document.getElementById('acaab-ach-year'); + const reset = document.getElementById('acaab-ach-reset'); + const moreBtn = document.getElementById('acaab-ach-more'); + const lessBtn = document.getElementById('acaab-ach-less'); + + const onChange = () => { + achFilter.search = search ? search.value.trim() : ''; + achFilter.level = level ? level.value : ''; + achFilter.cat = cat ? cat.value : ''; + achFilter.year = year ? year.value : ''; + achVisible = ACH_INIT; + renderAchievements(); + }; + + if (search) search.addEventListener('input', onChange); + if (level) level.addEventListener('change', onChange); + if (cat) cat.addEventListener('change', onChange); + if (year) year.addEventListener('change', onChange); + + if (reset) reset.addEventListener('click', () => { + achFilter = { search: '', level: '', cat: '', year: '' }; + if (search) search.value = ''; + if (level) level.value = ''; + if (cat) cat.value = ''; + if (year) year.value = ''; + achVisible = ACH_INIT; + renderAchievements(); + }); + + if (moreBtn) moreBtn.addEventListener('click', () => { achVisible += ACH_MORE; renderAchievements(); }); + if (lessBtn) lessBtn.addEventListener('click', () => { + achVisible = ACH_INIT; + document.getElementById('acaab-ach-section')?.scrollIntoView({ behavior: 'smooth', block: 'start' }); + renderAchievements(); + }); +} + +// ─── Achievement Modal ──────────────────────────────────────────────────────── + +function infoRow(icon, label, value) { + if (!value) return ''; + return `
+ +
+

${escHtml(label)}

+

${escHtml(value)}

+
+
`; +} + +function memberItem(nama, sub) { + const initial = (nama || '?').charAt(0).toUpperCase(); + return `
  • + ${initial} +
    + ${escHtml(nama)} + ${sub ? `${escHtml(sub)}` : ''} +
    +
  • `; +} + +function openAchModal(a) { + const membersHtml = a.members && a.members.length > 0 ? ` +
    +

    ${t('grp_acaab_modal_members')}

    +
      + ${a.members.map(m => memberItem(m.nama, m.angkatan ? `${t('grp_acaab_modal_batch_prefix')} ${m.angkatan}` : '')).join('')} +
    +
    ` : ''; + + renderModal(` + +
    + ${escHtml(a.title)} +
    + + + +
    + + ${escHtml(a.rank)} + + + ${escHtml(a.level)} + +
    +
    + +
    + ${escHtml(a.category)} +

    ${escHtml(a.title)}

    + +
    + ${infoRow('users', t('grp_acaab_modal_team'), a.team)} + ${a.nim ? infoRow('id-card', 'NIM', a.nim) : ''} + ${infoRow('user-graduate', t('grp_acaab_modal_generation'), a.generation)} + ${infoRow('calendar-alt', 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)} +
    + ${membersHtml} + +
    +

    ${t('grp_acaab_modal_description')}

    +

    ${escHtml(a.description)}

    +
    + + +
    + `, true); +} + +// ─── Projects ───────────────────────────────────────────────────────────────── + +const PROJ_INIT = 6; +const PROJ_MORE = 3; + +function initProjects() { + buildProjFilterOptions(); + renderProjects(); + bindProjEvents(); +} + +function buildProjFilterOptions() { + const catSel = document.getElementById('acaab-proj-cat'); + const yearSel = document.getElementById('acaab-proj-year'); + if (!catSel || !yearSel) return; + + const cats = [...new Set(projects.flatMap(p => p.categories))].sort(); + const years = [...new Set(projects.map(p => String(p.year)))].filter(Boolean).sort((a, b) => b - a); + + catSel.innerHTML = `` + + cats.map(c => ``).join(''); + yearSel.innerHTML = `` + + years.map(y => ``).join(''); +} + +function filterProjects() { + const q = projFilter.search.toLowerCase(); + 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 = `

    ${escHtml(t('grp_acaab_proj_empty'))}

    `; + if (moreBtn) moreBtn.style.display = 'none'; + return; + } + + track.innerHTML = visible.map((p, i) => projCardHtml(p, i)).join(''); + if (moreBtn) moreBtn.style.display = projVisible < filtered.length ? 'inline-block' : 'none'; + + // 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 => ` + + ${escHtml(c)} + `).join(''); + + return ` +
    +
    + +
    + ${escHtml(p.title)} + +
    +

    ${escHtml(p.title)}

    +
    ${catBadges}
    +
    +
    + +
    + +
    + ${p.studentName ? `${escHtml(p.studentName)}` : ''} + ${p.year ? `${escHtml(String(p.year))}` : ''} +
    + +

    ${escHtml(p.description)}

    + +
    + + ${p.link ? ` + + ${escHtml(t('grp_acaab_modal_visit'))} + ` : ''} +
    +
    +
    `; +} + +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 ? ` +
    +

    ${t('grp_acaab_modal_members')}

    +
      + ${p.members.map(m => memberItem(m.nama, m.nim || '')).join('')} +
    +
    ` : ''; + + const catBadgesImg = p.categories.map(c => ` + + ${escHtml(c)} + `).join(''); + + const linkSection = p.link + ? ` + ${escHtml(t('grp_acaab_modal_visit'))} + ` + : `
    + ${escHtml(t('grp_acaab_modal_no_link'))} +
    `; + + renderModal(` + +
    + ${escHtml(p.title)} +
    + + + +
    ${catBadgesImg}
    +
    + +
    +

    ${escHtml(p.title)}

    + +
    + ${p.studentName ? infoRow('users', t('grp_acaab_modal_team'), p.studentName) : ''} + ${p.nim ? infoRow('id-card', 'NIM', p.nim) : ''} + ${infoRow('calendar-alt', t('grp_acaab_modal_year'), String(p.year))} + ${infoRow('tag', t('grp_acaab_modal_field'), p.categories.join(', '))} +
    + ${membersHtml} + +
    +

    ${t('grp_acaab_modal_description')}

    +

    ${escHtml(p.description)}

    + ${p.catatan ? ` +
    +

    ${t('grp_acaab_modal_note')}

    +

    ${escHtml(p.catatan)}

    +
    ` : ''} +
    + + ${linkSection} + +
    + `, true); +} + +// ─── Modal shared ───────────────────────────────────────────────────────────── + +function renderModal(innerHtml, hasCloseBtn = false) { + const container = document.getElementById('acaab-modal'); + if (!container) return; + + container.innerHTML = ` +
    +
    + ${innerHtml} +
    +
    `; + + document.body.classList.add('acaab-modal-open'); + + document.getElementById('acaab-modal-close')?.addEventListener('click', closeModal); + if (hasCloseBtn) { + document.getElementById('acaab-modal-close-btn')?.addEventListener('click', closeModal); + } + document.getElementById('acaab-modal-bd').addEventListener('click', e => { + if (e.target === e.currentTarget) closeModal(); + }); + document.addEventListener('keydown', onEsc); +} + +function closeModal() { + const c = document.getElementById('acaab-modal'); + if (c) c.innerHTML = ''; + document.body.classList.remove('acaab-modal-open'); + document.removeEventListener('keydown', onEsc); +} + +function onEsc(e) { if (e.key === 'Escape') closeModal(); } + +// ─── CTA / WhatsApp ─────────────────────────────────────────────────────────── + +const WA_NUMBER = '6285177655161'; + +function initWaButton() { updateWaButton(); } + +function updateWaButton() { + const btn = document.getElementById('acaab-cta-wa'); + if (!btn) return; + const msg = getLang() === 'en' + ? 'Hello! I would like to know more about Informatika UNTAN student achievements and projects.' + : 'Halo! Saya ingin mengetahui lebih lanjut tentang prestasi dan karya mahasiswa Informatika UNTAN.'; + btn.href = `https://wa.me/${WA_NUMBER}?text=${encodeURIComponent(msg)}`; +} + +// ─── Placeholder sync ───────────────────────────────────────────────────────── + +function updateSearchPlaceholders() { + const s1 = document.getElementById('acaab-ach-search'); + const s2 = document.getElementById('acaab-proj-search'); + if (s1) s1.placeholder = t('grp_acaab_ach_search'); + if (s2) s2.placeholder = t('grp_acaab_proj_search'); +} diff --git a/groups/ACAAB/style.css b/groups/ACAAB/style.css index 2bc5542..aa25e6a 100644 --- a/groups/ACAAB/style.css +++ b/groups/ACAAB/style.css @@ -1,48 +1,222 @@ -:root { - --brand-navy: #003150; - --brand-yellow: #feb401; - --text-main: #1e293b; - --text-soft: #64748b; - --bg-soft: #f8fafc; - --line: #e2e8f0; +/* ===================================================== + 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); } -* { 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; } +/* --- Sembunyikan scrollbar carousel tapi tetap scrollable --- */ +.acaab-hide-scrollbar { + -ms-overflow-style: none; + scrollbar-width: none; +} +.acaab-hide-scrollbar::-webkit-scrollbar { + display: none; +} -.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; } +/* --- 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; } } -.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; } +/* --- Snap scroll carousel --- */ +.acaab-carousel-track { + scroll-snap-type: x mandatory; + -webkit-overflow-scrolling: touch; +} -.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); } } +/* --- 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; +} -.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; } +/* --- 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; } -.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; } +/* --- 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; } +} -.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; } +/* --- 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; +} -.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; } +/* --- 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; + } +} diff --git a/groups/ApeNameTeamE/PROJECT_CONTEXT.md b/groups/ApeNameTeamE/PROJECT_CONTEXT.md new file mode 100644 index 0000000..de03b10 --- /dev/null +++ b/groups/ApeNameTeamE/PROJECT_CONTEXT.md @@ -0,0 +1,273 @@ +# PROJECT CONTEXT +## Website Statis Halaman Beasiswa — Informatika UNTAN + +> File ini adalah konteks utama proyek untuk AI assistant (Antigravity). +> Baca file ini setiap kali kamu membantu pengerjaan proyek ini. + +--- + +## 🎯 Apa yang Dibangun? + +**Single static landing page** berbasis HTML/CSS/JS murni (native web) untuk menampilkan informasi beasiswa bagi mahasiswa aktif, alumni, dan masyarakat umum Program Studi Informatika, Universitas Tanjungpura (UNTAN), Pontianak. + +Website ini bukan aplikasi web dinamis. Tidak ada backend, tidak ada database, tidak ada login. Update konten dilakukan dengan mengedit file JSON dan push ke GitHub → auto-deploy via Puify ke server prodi. + +--- + +## 🏗️ Tech Stack + +| Layer | Teknologi | +|---|---| +| Markup | HTML5 | +| Styling | CSS3 + Tailwind CSS | +| Interaktivitas | Vanilla JavaScript (ES6+) | +| Font | Google Fonts (Inter / Poppins) | +| Ikon | Font Awesome | +| Animasi | AOS.js (Animate on Scroll) | +| Carousel | Swiper.js | +| Hosting | Dedicated Server Prodi UNTAN via Puify | +| Version Control | Git + GitHub | + +**Tidak boleh menggunakan:** Framework berat (React, Vue, Angular), backend (Node.js, PHP, Python), database (MySQL, MongoDB). + +--- + +## 📐 Desain + +- **Figma Project:** https://www.figma.com/design/rG6JYG19CWziaPMhbJ2NPL/PPL-FIGMA?node-id=0-1 +- **Prototype:** https://www.figma.com/proto/rG6JYG19CWziaPMhbJ2NPL/PPL-FIGMA?node-id=120-1247 + +### Warna Utama + +| Variabel | Hex | Penggunaan | +|---|---|---| +| Primary (Biru) | `#2B4590` | Navbar, heading, tombol, border aktif | +| Secondary (Kuning) | `#F5A623` | CTA, aksen, badge | +| Background | `#FFFFFF` | Latar halaman | +| Text | `#1A1A1A` | Body text | +| Accent | `#6B7280` | Caption, label, border | +| Success | `#10B981` | Badge "Buka" | +| Danger | `#EF4444` | Badge "Ditutup" | +| Overlay | `#00000080` | Background popup | + +### Tipografi +- **Font:** Inter (utama) / Poppins (heading) +- H1: 48–64px bold | H2: 32–40px semibold | H3: 24–28px semibold +- Body: 16–18px regular | Caption: 12–14px + +--- + +## 📄 Struktur Halaman (Top → Bottom) + +``` +[NAVBAR - Sticky] + Logo UNTAN | Menu: Beranda, Beasiswa, Testimoni, Kontak | Toggle ID/EN + +[#hero - Hero Section] + Judul: "Beasiswa Studi & Penelitian" + Sub: Teknik Informatika Universitas Tanjungpura + Deskripsi singkat tentang beasiswa + CTA Button (kuning): "Lihat Daftar Beasiswa →" + Foto ilustrasi mahasiswa (kanan) + +[#statistics - Statistik Counter] + 3 angka dengan animasi scroll: + - Total Mahasiswa Penerima Beasiswa Semester Ini + - Total Penerima Beasiswa Pembiayaan Pemerintah + - Total Penerima Beasiswa Pembiayaan Swasta + +[#directory - Direktori Beasiswa] + - Tab filter: Semua | Sedang Buka | Ditutup | Pemerintah | Swasta + - Search bar (real-time) + - Grid kartu beasiswa: + * Desktop: 3 kolom + * Tablet: 2 kolom + * Mobile: 1 kolom + - Tiap kartu: thumbnail, badge status, nama, deskripsi singkat, tanggal, tombol "Selengkapnya →" + - Klik "Selengkapnya →" → buka POPUP MODAL + +[POPUP MODAL - Detail Beasiswa] + - Nama + thumbnail/logo beasiswa + - Badge status (Buka / Ditutup) + - Syarat umum (ringkasan/bullet) + - Kontak yang bisa dihubungi + - Tombol "Info Lebih Lanjut" → buka URL sumber resmi (tab baru) + - Tutup: tombol ×, klik luar area, tekan Escape + +[#testimonial - Testimoni Penerima Beasiswa] + - Carousel / grid kartu + - Tiap kartu: foto, nama, nama beasiswa yang diterima, kutipan singkat + - Navigasi carousel jika > 3 testimoni + +[#cta - Call to Action] + - Judul: "Ingin Mengetahui Info Lebih Lanjut?" + - Dua tombol: + * "Daftar Beasiswa" (scroll ke #directory) + * "Hubungi Kami" (WhatsApp admin) + +[#footer - Footer] + - Logo Informatika UNTAN + - Alamat prodi, email, telepon, WhatsApp + - Ikon media sosial + - Ilustrasi khas Pontianak (dekorasi bawah) +``` + +--- + +## 📁 Struktur File Proyek + +``` +project-root/ +├── index.html +├── assets/ +│ ├── css/ +│ │ └── style.css +│ ├── js/ +│ │ ├── main.js # Init AOS, smooth scroll, sticky navbar +│ │ ├── filter.js # Filter tab + real-time search +│ │ ├── popup.js # Modal detail beasiswa +│ │ ├── counter.js # Animasi counter statistik +│ │ ├── carousel.js # Carousel testimoni (Swiper.js) +│ │ └── i18n.js # Toggle bahasa ID/EN +│ └── images/ +│ ├── hero/ +│ ├── beasiswa/ # Logo/thumbnail beasiswa +│ └── testimoni/ # Foto penerima +├── data/ +│ ├── beasiswa.json +│ └── testimoni.json +└── README.md +``` + +--- + +## 🗃️ Format Data + +### `data/beasiswa.json` +```json +[ + { + "id": "bsw-001", + "nama": "Beasiswa Adaro Foundation 2026", + "kategori": "Swasta", + "status": "Ditutup", + "thumbnail": "assets/images/beasiswa/adaro.jpg", + "deskripsi_singkat": "Beasiswa untuk mahasiswa S1 & D4 dengan IPK minimal 3.00...", + "syarat_umum": [ + "Mahasiswa aktif S1/D4", + "IPK minimal 3.00", + "Berasal dari keluarga kurang mampu" + ], + "kontak": "Admin Prodi / Kemahasiswaan UNTAN", + "tanggal_buka": "2026-01-01", + "tanggal_tutup": "2026-04-09", + "url_sumber": "https://...", + "tags": ["S1", "Swasta"] + } +] +``` + +### `data/testimoni.json` +```json +[ + { + "id": "tsm-001", + "nama": "Aulia Rahman", + "foto": "assets/images/testimoni/aulia.jpg", + "nama_beasiswa": "Beasiswa Prestasi", + "kutipan": "Beasiswa ini benar-benar membuka jalan bagi saya untuk fokus sepenuhnya pada pendidikan..." + } +] +``` + +--- + +## ⚙️ Fitur Interaktif yang Harus Diimplementasikan + +| Fitur | Status | File | +|---|---|---| +| Sticky Navbar (transparent → solid saat scroll) | ⏳ | `main.js` | +| Hamburger Menu | ⏳ | `main.js` | +| Smooth Scroll ke seksi | ⏳ | `main.js` | +| Active State Navbar (IntersectionObserver) | ⏳ | `main.js` | +| Animasi AOS (elemen muncul saat scroll) | ⏳ | `main.js` | +| Counter Animasi Statistik | ⏳ | `counter.js` | +| Render kartu beasiswa dari JSON | ⏳ | `filter.js` | +| Filter tab (Semua/Buka/Ditutup/Pemerintah/Swasta) | ⏳ | `filter.js` | +| Real-time search | ⏳ | `filter.js` | +| Popup Modal detail beasiswa | ⏳ | `popup.js` | +| Tutup popup (×, klik luar, Escape) | ⏳ | `popup.js` | +| Carousel testimoni (Swiper.js) | ⏳ | `carousel.js` | +| Toggle bahasa ID/EN | ⏳ | `i18n.js` | + +--- + +## 📱 Responsive Breakpoints + +```css +/* Mobile first */ +/* Default: mobile < 768px */ +/* Tablet: */ @media (min-width: 768px) { ... } +/* Laptop: */ @media (min-width: 1024px) { ... } +/* Desktop: */ @media (min-width: 1280px) { ... } +``` + +--- + +## 🔒 Aturan Keamanan Kode + +- Semua link eksternal: `target="_blank" rel="noopener noreferrer"` +- Tidak ada hardcoded data sensitif di source code +- Input search di-sanitasi sebelum digunakan untuk filter DOM +- Tidak menggunakan `innerHTML` dengan input pengguna langsung (cegah XSS) + +--- + +## 👥 Tim & Peran + +| Nama | Peran | +|---|---| +| Djapianus Thebrianto | Project Manager | +| Derangga Aray Abimanyu | Backend Developer (data, scraping, JSON) | +| Destra Aulia Faza Ananda Putra | Frontend Developer (HTML, CSS, JS) | +| Dian Satrio Prayogo | UI/UX Designer (Figma) | +| Jonathan Geral Nababan | Quality Assurance (testing) | + +--- + +## 📋 Hal Penting untuk AI Assistant + +1. **Ini bukan React/Vue/Angular.** Semua harus ditulis dalam HTML/CSS/Tailwind/Vanilla JS. +2. **Data beasiswa dibaca dari `data/beasiswa.json`** — bukan hardcoded di HTML. +3. **Warna wajib sesuai palet resmi** — jangan gunakan warna lain sembarangan. +4. **Mobile-first** — tulis CSS dari ukuran kecil dulu, baru ke besar. +5. **Popup harus bisa ditutup 3 cara:** tombol ×, klik overlay, tekan Escape. +6. **Filter dan search bisa aktif bersamaan** (kumulatif, bukan saling menggantikan). +7. **Navbar berubah warna saat scroll** — transparent di atas hero, solid biru setelah scroll turun. +8. **Semua animasi pakai AOS.js** — inisialisasi di `main.js` dengan `AOS.init()`. +9. **Carousel testimoni pakai Swiper.js** — touch-friendly untuk mobile. +10. **Tidak ada `alert()`, `console.log()` di production code.** + +--- + +## 🚀 Cara Kerja Deployment + +``` +Developer push ke GitHub + ↓ + Puify detects push + ↓ + Auto-deploy ke server + ↓ + Website live (Dev) + ↓ + Approved → Production +``` + +--- + +## 📎 Dokumen Terkait + +- `SRS.md` — Spesifikasi kebutuhan lengkap (use case, NFR, batasan) +- `SDD.md` — Desain teknis lengkap (arsitektur, komponen, schema data) +- Figma: https://www.figma.com/design/rG6JYG19CWziaPMhbJ2NPL/PPL-FIGMA?node-id=0-1 diff --git a/groups/ApeNameTeamE/SDD.md b/groups/ApeNameTeamE/SDD.md new file mode 100644 index 0000000..ebbef87 --- /dev/null +++ b/groups/ApeNameTeamE/SDD.md @@ -0,0 +1,329 @@ +# Software Design Document (SDD) +## Website Statis Halaman Beasiswa — One-Page HTML +### Program Studi Informatika — Universitas Tanjungpura + +| Field | Detail | +|---|---| +| Nama Proyek | Pengembangan Website Statis Halaman Beasiswa | +| Kelompok | Ape Nama Team E | +| Versi | 1.0 | +| Fase SDLC | Design (aktif) | +| Dosen | Izuardo Zulkarnain, S.Pd., M.T | +| Semester | Genap 2025/2026 | + +--- + +## 1. Pendahuluan + +### 1.1 Tujuan Dokumen +SDD ini mendeskripsikan perancangan teknis website beasiswa Informatika UNTAN sebagai one-page HTML native. Dokumen ini menjadi panduan tim dalam mengimplementasikan: +- Fitur popup detail beasiswa +- Integrasi data hasil scraping & survei admin +- Otomatisasi deployment via Puify (push-to-deploy) + +### 1.2 Stack Teknologi + +| Layer | Teknologi | Keterangan | +|---|---|---| +| Markup | HTML5 | Struktur konten | +| Styling | CSS3 / Tailwind CSS | Layout responsif | +| Interaktivitas | JavaScript (Vanilla / Alpine.js) | Animasi, scroll, toggle | +| Font & Ikon | Google Fonts / Font Awesome | Tipografi & ikon | +| Hosting | Puify (SaaS) | Auto-deploy dari repo ke server | +| Server | Dedicated Server Prodi | Dev & Production | +| Version Control | Git + GitHub | Kolaborasi & push-to-deploy | + +### 1.3 Posisi dalam SDLC + +| Fase | Status | +|---|---| +| Requirement | ✅ Selesai | +| Design | 🔄 Aktif | +| Implementation | ⏳ Belum | +| Testing | ⏳ Belum | +| Deployment | ⏳ Belum | + +--- + +## 2. Arsitektur Halaman (Site Architecture) + +### 2.1 Konsep One-Page Website +Single-page statis — semua konten dalam satu file HTML utama. Navigasi menggunakan **smooth scroll**. Detail beasiswa (syarat umum) ditampilkan via **popup modal** agar halaman tetap bersih. + +### 2.2 Struktur Seksi (Vertikal, Atas → Bawah) + +| No | ID | Nama Seksi | Konten Utama | +|---|---|---|---| +| 1 | `#hero` | Hero / Landing | Judul "Beasiswa Studi & Penelitian", deskripsi, CTA "Lihat Daftar Beasiswa", foto ilustrasi mahasiswa | +| 2 | `#statistics` | Statistik Counter | 3 angka animasi: Total Penerima Semester Ini, Total Pemerintah, Total Swasta | +| 3 | `#directory` | Direktori Beasiswa | Grid kartu (3 kolom desktop, 2 tablet, 1 mobile), tab filter, real-time search, popup detail | +| 4 | `#testimonial` | Testimoni | Carousel/grid testimoni: foto, nama, beasiswa, kutipan singkat | +| 5 | `#cta` | Call-to-Action | Banner ajakan + tombol WhatsApp admin + Instagram kemahasiswaan | +| 6 | `#footer` | Footer | Logo prodi, alamat, email, telepon, WA, ikon sosmed, dekorasi ilustrasi Pontianak | + +### 2.3 Navigasi +**Sticky navbar** yang selalu terlihat saat scroll. + +| Perangkat | Perilaku Navbar | +|---|---| +| Desktop/Laptop | Horizontal, semua item terlihat | +| Tablet | Horizontal, item muat; sisanya dropdown | +| Mobile | Hamburger menu (≡) — tap untuk expand vertikal | + +--- + +## 3. Desain Visual & Identitas + +### 3.1 Color Palette + +| Peran | Nama | Hex | Digunakan Untuk | +|---|---|---|---| +| Primary | Biru Informatika | `#2B4590` | Navbar, heading, tombol primary, border aktif | +| Secondary | Kuning/Oranye | `#F5A623` | CTA button, aksen, badge | +| Background | White | `#FFFFFF` | Latar halaman utama | +| Text | Dark Gray | `#1A1A1A` | Body text, paragraf | +| Accent | Abu-abu | `#6B7280` | Caption, label, border | +| Success | Hijau | `#10B981` | Badge status "Buka" | +| Danger | Merah | `#EF4444` | Badge status "Ditutup" | +| Overlay | Hitam + Transparansi | `#00000080` | Background popup modal | + +### 3.2 Tipografi + +| Elemen | Font Family | Ukuran | Weight | +|---|---|---|---| +| Heading H1 | Poppins / Inter | 48–64px | 700 (Bold) | +| Heading H2 | Poppins / Inter | 32–40px | 600 (SemiBold) | +| Heading H3 | Poppins / Inter | 24–28px | 600 (SemiBold) | +| Body Text | Inter / Poppins | 16–18px | 400 (Regular) | +| Caption/Label | Inter / Poppins | 12–14px | 400–500 | +| Navbar | Inter | 14–16px | 500 (Medium) | +| Button | Inter | 14–16px | 600 (SemiBold) | + +### 3.3 Komponen UI + +| Komponen | Spesifikasi | +|---|---| +| Navbar | Sticky; transparent di atas hero → solid biru `#2B4590` saat scroll; toggle ID/EN di kanan | +| Hero Section | Full-height, overlay gradient gelap, judul besar, deskripsi, CTA kuning kontras | +| Card Beasiswa | Border subtle, shadow ringan; hover: scale sedikit + shadow lebih dalam; thumbnail 100% lebar | +| Button Primary | Solid biru, teks putih | +| Button Secondary | Outline biru | +| Button Ghost | Tanpa border | +| Badge Status | Rounded pill; Hijau "Buka", Merah "Ditutup" | +| Tab Filter | Underline active state, smooth transition, teks hitam, garis bawah aktif biru | +| Popup Modal | Center screen, dark overlay, border-radius 12px, close button (×) kanan atas | +| Footer | Background `#1A1A1A`, teks putih, 3–4 kolom desktop | + +--- + +## 4. Desain Mockup (UI/UX) + +### 4.1 Alat Desain +**Figma** — kolaborasi realtime, wireframe + hi-fidelity prototype. + +- **Figma Project:** https://www.figma.com/design/rG6JYG19CWziaPMhbJ2NPL/PPL-FIGMA?node-id=0-1 +- **Prototype Interaktif:** https://www.figma.com/proto/rG6JYG19CWziaPMhbJ2NPL/PPL-FIGMA?node-id=120-1247 + +### 4.2 Tahapan Desain +1. **Wireframe (Lo-Fi)** — sketsa hitam-putih, fokus layout & posisi elemen +2. **Mockup (Hi-Fi)** — desain lengkap dengan warna, font, dan aset visual +3. **Prototype** — mockup yang bisa diklik untuk simulasi navigasi antar seksi +4. **User Testing** — validasi desain bersama calon pengguna + +### 4.3 Breakpoint & Resolusi + +| Perangkat | Resolusi Mockup | +|---|---| +| Desktop Monitor | 1920 × 1080 | +| Laptop | 1366 × 768 | +| Tablet Portrait | 768 × 1024 | +| Tablet Landscape | 1024 × 768 | +| Mobile / HP | 390 × 844 | + +--- + +## 5. Desain Responsif + +### 5.1 Strategi: Mobile-First +Desain dimulai dari layar terkecil → diperluas ke layar lebih besar via CSS media query. Memastikan performa optimal di koneksi lambat. + +### 5.2 Breakpoint + +| Perangkat | Breakpoint | Resolusi Umum | +|---|---|---| +| Desktop Monitor | `>= 1280px` | 1920×1080, 2560×1440 | +| Laptop | `>= 1024px` | 1366×768, 1440×900 | +| Tablet | `>= 768px` | 768×1024, 820×1180 | +| Mobile | `< 768px` | 360×800, 390×844, 414×896 | + +### 5.3 Perubahan Layout per Breakpoint + +| Elemen | Mobile (<768px) | Tablet (768–1279px) | Desktop (≥1280px) | +|---|---|---|---| +| Navbar | Hamburger menu | Hamburger / partial menu | Horizontal full menu | +| Hero Section | Teks atas, gambar bawah | Teks kiri, gambar kanan 50/50 | Teks kiri, gambar kanan lebar | +| Card Grid | 1 kolom | 2 kolom | 3–4 kolom | +| Teks Body | 16px | 16–17px | 17–18px | +| Padding Section | 24px 16px | 40px 32px | 80px 64px | +| Footer | Stack vertikal | 2 kolom | 3–4 kolom | + +### 5.4 Hasil Uji Responsivitas + +| No | Perangkat/Browser | Ukuran | Status | Catatan | +|---|---|---|---|---| +| 1 | Chrome Desktop | 1920×1080 | ✅ Lulus | Layout proporsional, spacing rapi | +| 2 | Chrome Mobile Sim. | 390×844 | ✅ Lulus | Card grid 1 kolom, teks 16px nyaman | +| 3 | Firefox Desktop | 1440×900 | ✅ Lulus | Konsisten dengan Chrome Desktop | +| 4 | iPhone (Safari) | 390×844 | ✅ Lulus | Hero stack sempurna, padding aman | +| 5 | Android Chrome | 360×800 | ✅ Lulus | Touch target ideal, gambar proporsional | +| 6 | iPad (Safari) | 768×1024 | ✅ Lulus | Hero 50/50, spasi sentuh lega | + +--- + +## 6. Use Case Diagram + +### 6.1 Aktor Sistem + +| Aktor | Deskripsi | +|---|---| +| Pengunjung Umum | Mahasiswa aktif, alumni, calon mahasiswa, dosen, masyarakat — tanpa autentikasi | +| Admin Prodi | Pengelola konten: memperbarui data beasiswa, testimoni, statistik via push-to-repo | + +### 6.2 Daftar Use Case & Skenario + +Lihat detail lengkap di SRS.md. Ringkasan: + +| Kode | Nama | Aktor | +|---|---|---| +| UC-01 | Melihat Daftar Beasiswa | Pengunjung Umum | +| UC-02 | Memfilter Beasiswa Berdasarkan Kategori | Pengunjung Umum | +| UC-03 | Mencari Beasiswa | Pengunjung Umum | +| UC-04 | Melihat Detail Beasiswa (Popup) | Pengunjung Umum | +| UC-05 | Mengakses Sumber Resmi Beasiswa | Pengunjung Umum | +| UC-06 | Menghubungi Admin via CTA | Pengunjung Umum | +| UC-07 | Melihat Testimoni Penerima Beasiswa | Pengunjung Umum | +| UC-08 | Mengganti Bahasa Tampilan (ID/EN) | Pengunjung Umum | +| UC-09 | Memperbarui Data Beasiswa | Admin Prodi | + +**Relasi antar UC:** UC-04 `<>` UC-05 (popup detail menyertakan akses sumber resmi) + +--- + +## 7. Fitur Interaktif (Client-Side) + +Tidak ada backend API. Semua interaktivitas dijalankan di sisi klien. + +| No | Fitur | Teknologi | Keterangan | +|---|---|---|---| +| 1 | Smooth Scroll Navigasi | CSS `scroll-behavior` / JS | Scroll halus ke seksi saat klik menu | +| 2 | Sticky Navbar | CSS `position:sticky` / JS scroll | Navbar tetap di atas saat scroll | +| 3 | Hamburger Menu | JavaScript + CSS toggle | Menu responsif layar kecil | +| 4 | Active State Navbar | JS `IntersectionObserver` | Highlight menu sesuai seksi terlihat | +| 5 | Animasi Masuk (AOS) | AOS.js / CSS animation | Elemen muncul saat pertama kali di viewport | +| 6 | Counter Animasi | JavaScript | Angka statistik berjalan saat di-scroll | +| 7 | Filter Real-Time | Vanilla JavaScript | Filter kartu saat klik tab, tanpa reload | +| 8 | Pencarian Real-Time | Vanilla JavaScript | Cari beasiswa saat ketik di search bar | +| 9 | Popup Modal Detail | Vanilla JavaScript | Detail beasiswa dalam modal; tutup via ×, klik luar, atau Escape | +| 10 | Toggle Bahasa ID/EN | Vanilla JS + localStorage | Ubah teks tanpa reload, simpan preference di session | +| 11 | Carousel Testimoni | Vanilla JS / Swiper.js | Navigasi testimoni left/right; touch swipe di mobile | +| 12 | Form Kontak (Opsional) | Formspree / EmailJS / mailto | Kirim pesan tanpa backend | +| 13 | Lightbox Galeri (Opsional) | Fancybox / vanilla JS | Gambar fullscreen saat diklik | + +--- + +## 8. Layanan Pihak Ketiga + +| Layanan | Kegunaan | Gratis? | +|---|---|---| +| Google Fonts | Tipografi (Inter/Poppins) | ✅ Ya | +| Font Awesome | Ikon UI | ✅ Ya (tier dasar) | +| Google Maps Embed | Peta lokasi di seksi kontak | ✅ Ya (basic embed) | +| Formspree | Form kontak tanpa backend | ✅ Ya (tier dasar) | +| AOS.js | Animasi scroll (Animate on Scroll) | ✅ Ya (open source) | +| Swiper.js | Carousel testimoni | ✅ Ya (open source) | + +--- + +## 9. Desain Keamanan + +### 9.1 Keamanan Form Kontak +- Validasi client-side: semua field wajib diisi, format email dicek sebelum submit +- Rate limiting: Formspree/EmailJS membatasi pengiriman untuk mencegah spam +- reCAPTCHA (Opsional): Google reCAPTCHA v3 untuk mencegah bot + +### 9.2 Keamanan Konten +- **Tidak ada data sensitif** — website tidak menyimpan data pengguna (no login, no database) +- **HTTPS wajib** — sertifikat SSL/TLS pada server hosting prodi +- **Content Security Policy** — meta CSP header untuk mencegah XSS +- **External link** — semua link eksternal menggunakan `rel="noopener noreferrer"` + +--- + +## 10. Struktur File yang Direkomendasikan + +``` +project-root/ +├── index.html # File HTML utama (one-page) +├── assets/ +│ ├── css/ +│ │ └── style.css # Custom CSS (jika tidak full Tailwind) +│ ├── js/ +│ │ ├── main.js # Logika utama: scroll, navbar, AOS init +│ │ ├── filter.js # Filter & pencarian beasiswa +│ │ ├── popup.js # Modal popup detail beasiswa +│ │ ├── counter.js # Animasi counter statistik +│ │ ├── carousel.js # Carousel testimoni +│ │ └── i18n.js # Toggle bahasa ID/EN +│ └── images/ +│ ├── hero/ # Foto hero section +│ ├── beasiswa/ # Thumbnail logo beasiswa +│ └── testimoni/ # Foto penerima beasiswa +├── data/ +│ ├── beasiswa.json # Data beasiswa (diupdate oleh admin) +│ └── testimoni.json # Data testimoni +└── README.md # Panduan setup & deployment +``` + +--- + +## 11. Data Schema + +### 11.1 Schema Beasiswa (`beasiswa.json`) +```json +{ + "id": "string", + "nama": "string", + "kategori": "Pemerintah | Swasta", + "status": "Buka | Ditutup", + "thumbnail": "path/to/image.jpg", + "deskripsi_singkat": "string (max 150 char)", + "syarat_umum": ["string", "string"], + "kontak": "string", + "tanggal_buka": "YYYY-MM-DD", + "tanggal_tutup": "YYYY-MM-DD", + "url_sumber": "https://...", + "tags": ["S1", "S2", "Magang", "Luar Negeri"] +} +``` + +### 11.2 Schema Testimoni (`testimoni.json`) +```json +{ + "id": "string", + "nama": "string", + "foto": "path/to/foto.jpg", + "nama_beasiswa": "string", + "kutipan": "string (max 200 char)" +} +``` + +--- + +## 12. Referensi + +1. IEEE Standard 1016-2009 — Software Design Descriptions +2. W3C — HTML5 & CSS3 Specification (https://www.w3.org) +3. Google — Web Fundamentals: Responsive Web Design +4. MDN Web Docs — CSS Media Queries +5. Figma — Design Tool Documentation +6. Nurhikmat et al. (2024) — Optimasi Responsivitas Web dengan Mobile-First Design. JINTEKS. diff --git a/groups/ApeNameTeamE/SRS.md b/groups/ApeNameTeamE/SRS.md new file mode 100644 index 0000000..71f675f --- /dev/null +++ b/groups/ApeNameTeamE/SRS.md @@ -0,0 +1,337 @@ +# Software Requirements Specification (SRS) +## Pengembangan Website Statis Halaman Beasiswa +### Program Studi Informatika — Universitas Tanjungpura + +| Field | Detail | +|---|---| +| Nama Proyek | Pengembangan Website Statis Halaman Beasiswa | +| Kelompok | Ape Nama Team E | +| Versi | 1.1 | +| Tanggal | 01/05/2026 | +| Dosen | Izuardo Zulkarnain, S.Pd., M.T | +| Mata Kuliah | Proyek Perangkat Lunak — Semester Genap 2025/2026 | + +**Anggota Tim:** +- Djapianus Thebrianto — Project Manager +- Derangga Aray Abimanyu — Backend Developer +- Destra Aulia Faza Ananda Putra — Frontend Developer +- Dian Satrio Prayogo — UI/UX Designer +- Jonathan Geral Nababan — Quality Assurance + +--- + +## 1. Pendahuluan + +### 1.1 Tujuan Dokumen + +SRS ini memberikan spesifikasi teknis dan fungsional untuk pengembangan Website Statis Beasiswa Informatika Universitas Tanjungpura. Dokumen ini memastikan: + +- Pengembang membangun sistem dengan kode sederhana yang mudah di-maintain melalui mekanisme push-to-repo ke dedicated server prodi. +- Penyajian data beasiswa (nama & syarat umum) dilakukan melalui fitur **Pop-up** dengan akses langsung ke admin via WhatsApp dan Instagram kemahasiswaan UNTAN. +- Proyek mengalihkan alur data dari portal universitas/survei admin ke static page dengan **availability 24 jam**. + +**Ditujukan kepada:** Tim Ape Nama Team E, Product Owner (dosen), dan Administrator Prodi. + +### 1.2 Ruang Lingkup + +Website statis single-page yang menjadi pusat informasi beasiswa, riset, dan pengembangan karir — terintegrasi dengan infrastruktur server Prodi Informatika UNTAN. + +**Fitur yang dicakup:** +- Direktori beasiswa (mahasiswa aktif, alumni, S2 luar negeri, magang) +- Detail beasiswa via popup (nama + syarat umum) +- Data beasiswa dari web scraping sumber resmi +- Seksi testimoni penerima beasiswa (foto, profil, kutipan) +- Toggle bahasa Indonesia / Inggris (bilingual) +- CTA ke WhatsApp admin & Instagram kemahasiswaan UNTAN + +**Yang TIDAK dicakup:** +- Formulir pendaftaran (diarahkan ke URL asli penyedia) +- Halaman admin berbasis web (update via push-to-repo) +- Database — data bersifat statis (JSON/JS di repositori) + +### 1.3 Definisi & Akronim + +| Istilah | Definisi | +|---|---| +| Native Website | Web dengan HTML, CSS, JS murni tanpa framework kompleks | +| Scraping | Pengambilan data otomatis dari website eksternal | +| Dedicated Server | Server milik Prodi Informatika UNTAN (Dev & Production) | +| Pop-up / Modal | Jendela overlay di atas halaman tanpa reload | +| CTA | Call-to-Action — tombol ajakan ke WhatsApp/Instagram | +| Availability | Kemampuan sistem beroperasi 24 jam | +| Puify | Platform SaaS otomatisasi deployment dari repo ke server | + +### 1.4 Referensi +- IEEE Std 830-1998 — Software Requirements Specifications +- UU No. 27 Tahun 2022 tentang Perlindungan Data Pribadi (UU PDP) + +--- + +## 2. Deskripsi Umum Sistem + +### 2.1 Perspektif Produk + +Produk baru berdiri sendiri sebagai **single static landing page** — bukan pengganti sistem lama. Website ini hadir sebagai transformasi digital dari penyebaran info beasiswa yang sebelumnya manual (portal, surat kabar mahasiswa, grup medsos) ke satu platform terpusat. + +**Integrasi eksternal (satu arah):** +- Website resmi penyedia beasiswa (data via scraping) +- API Website Informatika UNTAN (konten artikel/berita) +- WhatsApp & Instagram Kemahasiswaan UNTAN (CTA) + +Tidak ada antarmuka admin berbasis web. Semua update konten via **push ke GitHub → auto-deploy ke server** melalui Puify. + +### 2.2 Fungsi Utama Sistem + +| Kode | Fungsi | Deskripsi | +|---|---|---| +| F01 | Direktori Beasiswa | Grid kartu beasiswa aktif, S2, magang, penelitian dari berbagai sumber resmi | +| F02 | Filter & Pencarian | Saring berdasarkan kategori (Semua/Buka/Ditutup/Pemerintah/Swasta) + real-time search | +| F03 | Detail via Pop-up | Modal berisi nama, syarat umum, kontak, tautan sumber resmi | +| F04 | Testimoni | Foto, nama, nama beasiswa, kutipan dari penerima beasiswa | +| F05 | CTA Hubungi Admin | Tombol cepat ke WhatsApp admin & Instagram kemahasiswaan | +| F06 | Bilingual ID/EN | Toggle bahasa Indonesia ↔ Inggris tanpa reload | +| F07 | Update via Repository | Admin edit file data + push → halaman publik auto-update | + +### 2.3 Kelas Pengguna + +| Kelas | Karakteristik | Hak Akses | +|---|---|---| +| Pengunjung Umum | Mahasiswa aktif, alumni, masyarakat — tanpa login | Lihat konten, filter, search, buka popup, akses link eksternal | +| Admin Prodi | Staf/mahasiswa dengan kemampuan Git dasar | Edit file data di GitHub → push → deploy otomatis | + +### 2.4 Lingkungan Operasional + +| Komponen | Spesifikasi | +|---|---| +| Platform | Browser modern: Chrome ≥v100, Firefox ≥v100, Edge ≥v100, Safari ≥v15 | +| Server/Hosting | Dedicated server Prodi Informatika UNTAN (Dev & Production) via Puify | +| Database | Tidak ada — data di file statis JSON/JS di repositori | +| Bahasa Pemrograman | HTML5, CSS3 + Tailwind CSS, JavaScript ES6+ | +| Version Control | Git / GitHub | +| Koneksi | Min 1 Mbps (dasar), 10 Mbps (optimal) | + +### 2.5 Asumsi & Ketergantungan + +**Asumsi:** +- Pengunjung menggunakan browser modern dengan JS ES6+ dan CSS3 aktif +- Data beasiswa sudah diverifikasi admin sebelum dipublikasikan +- Perangkat pengunjung mendukung akses WhatsApp (app atau web) + +**Ketergantungan:** +- Dedicated server Prodi UNTAN harus aktif untuk availability 24 jam +- Update data bergantung pada keaktifan admin melakukan push secara berkala +- Fitur berita bergantung pada stabilitas API Website Informatika UNTAN +- CTA bergantung pada keaktifan nomor WhatsApp & akun Instagram kemahasiswaan + +--- + +## 3. Kebutuhan Fungsional + +### 3.1 Daftar Use Case + +| UC ID | Nama Use Case | Aktor | Prioritas (MoSCoW) | +|---|---|---|---| +| UC-01 | Melihat Daftar Beasiswa | Pengunjung Umum | Must Have | +| UC-02 | Memfilter Beasiswa Berdasarkan Kategori | Pengunjung Umum | Must Have | +| UC-03 | Mencari Beasiswa | Pengunjung Umum | Must Have | +| UC-04 | Melihat Detail Beasiswa (Popup) | Pengunjung Umum | Must Have | +| UC-05 | Mengakses Sumber Informasi Beasiswa | Pengunjung Umum | Must Have | +| UC-06 | Menghubungi Admin via CTA | Pengunjung Umum | Must Have | +| UC-07 | Melihat Testimoni Penerima Beasiswa | Pengunjung Umum | Should Have | +| UC-08 | Mengganti Bahasa Tampilan (ID/EN) | Pengunjung Umum | Should Have | +| UC-09 | Memperbarui Data Beasiswa | Admin Prodi | Must Have | + +### 3.2 Detail Use Case + +#### UC-01: Melihat Daftar Beasiswa +| Field | Detail | +|---|---| +| Aktor | Pengunjung Umum | +| Precondition | Halaman berhasil dimuat di browser | +| Trigger | Pengunjung membuka URL atau scroll ke seksi Daftar Beasiswa | +| Main Flow | 1. Pengunjung buka URL halaman
    2. Sistem tampilkan hero, statistik, daftar beasiswa
    3. Kartu ditampilkan grid: 3 kolom desktop / 2 tablet / 1 mobile
    4. Tiap kartu: thumbnail, badge status, nama, deskripsi singkat, tanggal, tombol "Selengkapnya"
    5. Paginasi tampil di bawah grid jika beasiswa > 1 halaman | +| Alt Flow | 3a. Koneksi lambat → skeleton loading
    3b. Tidak ada data → tampil "Belum ada beasiswa tersedia saat ini." | +| Postcondition | Pengunjung dapat melihat seluruh daftar beasiswa | + +#### UC-02: Memfilter Beasiswa Berdasarkan Kategori +| Field | Detail | +|---|---| +| Aktor | Pengunjung Umum | +| Precondition | Halaman dimuat, daftar beasiswa tampil | +| Trigger | Pengunjung klik salah satu tab filter | +| Main Flow | 1. Tampil tab: Semua \| Sedang Buka \| Ditutup \| Pemerintah \| Swasta
    2. Pengunjung klik tab
    3. Sistem real-time filter beasiswa sesuai kategori
    4. Tab aktif ditandai bold/underline/warna
    5. Jumlah hasil diperbarui | +| Alt Flow | 3a. Tidak ada hasil → "Tidak ada beasiswa pada kategori ini."
    3b. Klik "Semua" → tampil semua beasiswa | +| Postcondition | Daftar beasiswa sesuai filter yang dipilih | + +#### UC-03: Mencari Beasiswa +| Field | Detail | +|---|---| +| Aktor | Pengunjung Umum | +| Precondition | Halaman dimuat, kolom pencarian tersedia | +| Trigger | Pengunjung mengetik di kolom pencarian | +| Main Flow | 1. Pengunjung klik kolom pencarian
    2. Ketik kata kunci (misal: "Pertamina", "KIP", "luar negeri")
    3. Sistem real-time filter kartu yang nama/deskripsinya cocok
    4. Hapus kata kunci → kembali ke tampilan penuh
    5. Filter dan pencarian dapat aktif bersamaan (kumulatif) | +| Alt Flow | 3a. Tidak ada hasil → "Tidak ditemukan beasiswa yang sesuai pencarian Anda." | +| Postcondition | Daftar beasiswa sesuai kata kunci | + +#### UC-04: Melihat Detail Beasiswa (Popup) +| Field | Detail | +|---|---| +| Aktor | Pengunjung Umum | +| Precondition | Kartu beasiswa tampil, pengunjung menemukan beasiswa yang diinginkan | +| Trigger | Klik tombol "Selengkapnya →" pada kartu | +| Main Flow | 1. Klik "Selengkapnya →"
    2. Sistem tampilkan modal popup berisi: nama + thumbnail, badge status, syarat umum ringkasan, kontak, tombol "Info Lebih Lanjut" (→ URL sumber resmi di tab baru)
    3. Background di-overlay gelap
    4. Tutup via: tombol ×, klik di luar area, atau tekan Escape | +| Alt Flow | 4a. Klik "Info Lebih Lanjut" → jalankan UC-05 | +| Postcondition | Pengunjung mendapat info ringkas dan dapat lanjut ke sumber resmi | + +#### UC-05: Mengakses Sumber Informasi Beasiswa +| Field | Detail | +|---|---| +| Aktor | Pengunjung Umum | +| Precondition | Popup detail terbuka, tombol "Info Lebih Lanjut" tersedia | +| Trigger | Klik tombol "Info Lebih Lanjut" di popup | +| Main Flow | 1. Klik tombol
    2. Sistem buka URL sumber resmi di tab baru
    3. Popup di tab asal tetap terbuka | +| Alt Flow | 3a. URL tidak tersedia → "Tautan tidak tersedia. Silakan hubungi admin prodi." | +| Postcondition | Pengunjung diarahkan ke sumber resmi beasiswa | + +#### UC-06: Menghubungi Admin via CTA +| Field | Detail | +|---|---| +| Aktor | Pengunjung Umum | +| Precondition | Pengunjung ingin info lebih lanjut | +| Trigger | Scroll ke seksi CTA, klik salah satu tombol | +| Main Flow | 1. Scroll ke CTA ("Ingin Tahu Lebih Banyak tentang Beasiswa?")
    2. Tampil dua tombol: "Hubungi Admin" (WhatsApp) + "Instagram Beasiswa" (IG kemahasiswaan)
    3. Klik tombol → buka platform di tab baru | +| Alt Flow | 4a. Tanpa WhatsApp terinstall → buka WhatsApp Web | +| Postcondition | Pengunjung terhubung dengan admin atau follow Instagram | + +#### UC-07: Melihat Testimoni Penerima Beasiswa +| Field | Detail | +|---|---| +| Aktor | Pengunjung Umum | +| Precondition | Halaman dimuat, data testimoni tersedia di source code | +| Trigger | Scroll ke seksi Testimoni | +| Main Flow | 1. Scroll ke seksi Testimoni
    2. Tampil kartu testimoni: foto, nama, nama beasiswa, kutipan singkat
    3. Jika > 3 testimoni → navigasi carousel/tombol selanjutnya | +| Alt Flow | 2a. Belum ada data → seksi tidak ditampilkan atau tampil placeholder | +| Postcondition | Pengunjung mendapat gambaran pengalaman penerima beasiswa | + +#### UC-08: Mengganti Bahasa Tampilan (ID/EN) +| Field | Detail | +|---|---| +| Aktor | Pengunjung Umum | +| Precondition | Toggle ID\|EN tersedia di header navigasi | +| Trigger | Klik toggle bahasa di pojok kanan header | +| Main Flow | 1. Pengunjung lihat toggle ID \| EN di kanan atas
    2. Klik bahasa yang diinginkan
    3. Seluruh teks berubah tanpa reload
    4. Pilihan tersimpan di session (tidak reset saat scroll/filter) | +| Alt Flow | 3a. Konten hanya tersedia 1 bahasa → tetap tampil bahasa aslinya | +| Postcondition | Halaman tampil dalam bahasa yang dipilih | + +#### UC-09: Memperbarui Data Beasiswa +| Field | Detail | +|---|---| +| Aktor | Admin Prodi | +| Precondition | Admin punya akses repo, ada info beasiswa baru/perubahan status | +| Trigger | Admin mendapat informasi update dari survei/portal/sumber lain | +| Main Flow | 1. Admin dapat info baru/perubahan status
    2. Edit file data beasiswa (nama, kategori, status, syarat, kontak, URL)
    3. Simpan dan push ke GitHub
    4. Halaman publik auto-update dalam waktu singkat via Puify | +| Alt Flow | 3a. Error saat deploy → halaman publik tidak berubah, admin perbaiki dan push ulang | +| Postcondition | Info beasiswa terbaru tersedia di halaman publik 24/7 | + +--- + +## 4. Kebutuhan Antarmuka Eksternal + +### 4.1 Antarmuka Pengguna (UI) + +- Responsive design: breakpoint 320px (mobile), 768px (tablet), 1280px (desktop) +- Warna utama: Biru Informatika `#2B4590` dan Kuning `#F5A623` (branding UNTAN) +- Font: Inter atau Poppins, minimal 14px untuk body text +- Waktu respons antarmuka ≤ 3 detik pada koneksi 10 Mbps +- Aksesibilitas WCAG 2.1 level AA (kontras warna, ARIA label, navigasi keyboard) +- Semua elemen interaktif memiliki feedback visual saat hover/klik + +**Halaman / Seksi:** + +| Seksi | ID | Konten Utama | +|---|---|---| +| Header & Navigasi | — | Navbar sticky, logo UNTAN, toggle ID/EN, info ticker | +| Hero | `#hero` | Judul "Beasiswa Studi & Penelitian", deskripsi, CTA kuning, foto mahasiswa | +| Statistik Counter | `#statistics` | 3 angka animasi: Total Penerima, Pemerintah, Swasta | +| Direktori Beasiswa | `#directory` | Grid kartu, tab filter, real-time search, popup detail | +| Testimoni | `#testimonial` | Carousel foto+nama+kutipan penerima beasiswa | +| CTA | `#cta` | Banner ajakan + tombol WhatsApp & Instagram | +| Footer | `#footer` | Logo, alamat, kontak, sosial media, ilustrasi Pontianak | + +### 4.2 Antarmuka Hardware +- Browser modern: Chrome/Firefox/Edge ≥v100, Safari ≥v15 +- Koneksi minimal 1 Mbps; optimal 10 Mbps +- Resolusi minimal 320px lebar +- Tidak butuh hardware khusus (kamera, printer, sensor, dll.) + +### 4.3 Antarmuka Software + +| Layanan | Tujuan | Protokol | +|---|---|---| +| Dedicated Server + Puify | Hosting dev & production, auto-deploy dari GitHub | HTTPS | +| GitHub | Version control, kolaborasi, deliverable akhir | HTTPS/SSH | +| Sumber Resmi Beasiswa | Link eksternal di popup (dibuka tab baru) | HTTPS | +| WhatsApp / Instagram UNTAN | Tombol CTA | HTTPS / wa.me | +| Figma | Desain mockup & wireframe acuan implementasi | HTTPS | +| Web Scraper | Ambil data beasiswa dari sumber resmi | HTTPS | + +### 4.4 Antarmuka Komunikasi +- Semua file dikirim via HTTPS dengan sertifikat SSL/TLS valid +- Fitur interaktif (filter, search, toggle bahasa, popup, counter) berjalan client-side (JS) — tanpa request ke server +- Semua link eksternal: `target="_blank"` + `rel="noopener noreferrer"` +- Tidak menggunakan cookie, session storage, atau autentikasi pengguna (v1.0) + +--- + +## 5. Kebutuhan Non-Fungsional + +### NFR-01: Performa +- **P-01** Loading halaman ≤ 3 detik pada koneksi 10 Mbps +- **P-02** Filter beasiswa merespons < 0,5 detik setelah klik +- **P-03** Tetap responsif dan optimal di desktop, tablet, dan smartphone + +### NFR-02: Keamanan +- **S-01** Semua komunikasi via HTTPS dengan SSL/TLS +- **S-02** Source code bersih dari script berbahaya, bebas celah XSS +- **S-03** Semua link CTA dan pendaftaran mengarah ke sumber resmi tervalidasi + +### NFR-03: Skalabilitas +- **SC-01** Mampu menampilkan minimal 100 data beasiswa tanpa penurunan performa +- **SC-02** Struktur kode memungkinkan penambahan kategori baru tanpa perubahan besar +- **SC-03** Sistem bilingual mendukung penambahan bahasa baru di masa depan + +### NFR-04: Ketersediaan & Reliabilitas +- **R-01** Uptime minimal 99% selama masa operasional +- **R-02** Dapat diakses 24 jam selama server aktif +- **R-03** Backup source code via Git/GitHub untuk pemulihan data + +### NFR-05: Usability +- **U-01** Antarmuka bilingual ID/EN +- **U-02** Pengguna menemukan info beasiswa dalam < 2 menit via navigasi & filter +- **U-03** Tombol CTA di Hero kontras dan posisinya jelas + +### NFR-06: Maintainability +- **M-01** Tech stack: HTML5, CSS3 + Tailwind CSS, JavaScript ES6+ — kode rapi dan mudah dipelihara +- **M-02** Semua perubahan dikelola via Git/GitHub (version control) +- **M-03** Dokumentasi teknis & panduan deployment disertakan saat penyerahan akhir + +--- + +## 6. Batasan & Kendala + +### 6.1 Batasan Implementasi +- Tech stack terkunci: HTML5, CSS3, Tailwind CSS, JavaScript ES6+, Figma, Git/GitHub +- Single static landing page — tidak ada backend, database, atau login +- Harus berjalan optimal di Chrome, Firefox, Edge versi terbaru +- Wajib responsif: desktop, tablet, smartphone + +### 6.2 Batasan Proyek +- Durasi: 10 hari kerja (1 Mei – 11 Mei 2026) +- Tim: 5 orang dengan peran tetap (PM, Backend, Frontend, UI/UX, QA) +- Revisi antarmuka/konten sesuai batas yang disepakati +- Penambahan fitur baru saat development = di luar ruang lingkup utama + +### 6.3 Regulasi & Standar +- UU PDP No. 27/2022 untuk pengelolaan data testimoni mahasiswa +- Visual harus selaras branding resmi Informatika UNTAN +- Memenuhi prinsip aksesibilitas dasar (navigasi jelas, tampilan informatif) diff --git a/groups/ApeNameTeamE/assets/css/style.css b/groups/ApeNameTeamE/assets/css/style.css new file mode 100644 index 0000000..77bb698 --- /dev/null +++ b/groups/ApeNameTeamE/assets/css/style.css @@ -0,0 +1,1596 @@ +/* ===================================================== + INFORMATIKA UNTAN — Website Beasiswa + Custom CSS (Complementary to Tailwind CSS) + ===================================================== */ + +/* ===== CSS VARIABLES ===== */ +:root { + --color-primary: #003150; + --color-secondary: #feb404; + --color-bg: #FFFFFF; + --color-text: #1A1A1A; + --color-accent: #6B7280; + --color-success: #10B981; + --color-danger: #EF4444; + --color-overlay: rgba(0, 0, 0, 0.5); + --font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif; + --font-serif: 'Roboto Condensed', sans-serif; + --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08); + --shadow-card: 0 2px 12px rgba(0, 0, 0, 0.08); + --shadow-card-hover: 0 8px 30px rgba(43, 69, 144, 0.15); + --shadow-popup: 0 20px 60px rgba(0, 0, 0, 0.3); + --radius: 12px; + --navbar-height: 108px; + /* 60px top bar putih + 48px main nav kuning */ +} + +/* ===== BASE RESET ===== */ +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html { + scroll-behavior: smooth; + font-size: 16px; +} + +body { + font-family: var(--font-sans); + color: var(--color-text); + line-height: 1.65; + overflow-x: hidden; + background: var(--color-bg); +} + +/* ===== SCROLLBAR ===== */ +::-webkit-scrollbar { + width: 5px; +} + +::-webkit-scrollbar-track { + background: #f1f5f9; +} + +::-webkit-scrollbar-thumb { + background: var(--color-primary); + border-radius: 3px; +} + +/* ===== STICKY NAVBAR ===== */ +#navbar { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 1000; + transition: var(--transition); +} + +/* ── Top Bar (DARK NAVY sesuai Figma) ── */ +.navbar-top { + background: #003150; + height: 60px; + display: flex; + align-items: center; +} + +/* Logo emblem */ +.navbar-logo-emblem { + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; +} + +.navbar-logo-name { + font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif; + font-size: 1.125rem; + /* text-lg = 18px */ + font-weight: 700; + color: #ffffff; + line-height: 1; + letter-spacing: 0.1em; + /* tracking-widest */ + text-transform: uppercase; + margin-bottom: 0.25rem; +} + +.navbar-logo-sub { + font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif; + font-size: 10px; + /* text-[10px] */ + font-weight: 400; + color: rgba(226, 232, 240, 0.9); + /* text-slate-200 opacity-90 */ + letter-spacing: 0.15em; + /* tracking-[0.15em] */ + margin-top: 0.125rem; + text-transform: uppercase; +} + +@media (min-width: 1024px) { + .navbar-logo-name { + font-size: 1.5rem; + } + + /* lg:text-2xl = 24px */ + .navbar-logo-sub { + font-size: 0.875rem; + letter-spacing: 0.3em; + } + + /* lg:text-sm, lg:tracking-[0.3em] */ +} + +/* "SATU UNTAN" text */ +.navbar-satu { + font-size: 11px; + font-weight: 700; + color: rgba(255, 255, 255, 0.85); + letter-spacing: 0.5px; + text-transform: uppercase; +} + +/* Icon buttons on top bar (dark bg version) */ +.navbar-icon-btn { + width: 32px; + height: 32px; + border-radius: 50%; + background: rgba(255, 255, 255, 0.12); + border: 1px solid rgba(255, 255, 255, 0.2); + display: flex; + align-items: center; + justify-content: center; + color: rgba(255, 255, 255, 0.8); + cursor: pointer; + transition: var(--transition); +} + +.navbar-icon-btn:hover { + background: rgba(255, 255, 255, 0.22); + color: #ffffff; +} + +.navbar-search-btn { + width: 32px; + height: 32px; + border-radius: 50%; + background: none; + border: none; + display: flex; + align-items: center; + justify-content: center; + color: rgba(255, 255, 255, 0.75); + cursor: pointer; + transition: var(--transition); +} + +.navbar-search-btn:hover { + color: #ffffff; +} + +/* Language Toggle — dark top bar */ +#lang-toggle { + display: flex; + align-items: center; + gap: 4px; +} + +.lang-btn { + padding: 0; + font-size: 11px; + font-weight: 500; + color: rgba(255, 255, 255, 0.6); + cursor: pointer; + border: none; + background: none; + transition: var(--transition); + letter-spacing: 0.5px; +} + +.lang-btn:hover { + color: #ffffff; +} + +.lang-btn.active { + font-weight: 800; + color: #ffffff; +} + +.lang-divider { + color: rgba(255, 255, 255, 0.4); + font-size: 10px; + margin: 0 2px; +} + +/* ── Main Nav Bar (KUNING) ── */ +.navbar-main { + background: var(--color-secondary); + height: 48px; + display: none; + align-items: center; +} + +@media (min-width: 1024px) { + .navbar-main { + display: flex; + } +} + +/* Nav links — teks gelap di atas latar kuning */ +.nav-link { + position: relative; + color: #0a1530; + text-decoration: none; + transition: color 0.2s; + white-space: nowrap; +} + +.nav-link:hover { + color: #000000; +} + +.nav-link.active { + color: #000000 !important; +} + +/* ===== INFO TICKER ===== */ +.ticker-wrapper { + background: var(--color-primary); + border-top: 1px solid rgba(255, 255, 255, 0.05); + height: 40px; + display: flex; + align-items: center; + overflow: hidden; + + /* Sticky behavior */ + position: fixed; + top: 108px; + left: 0; + right: 0; + z-index: 40; + transform: translateY(-100%); + opacity: 0; + transition: transform 0.3s ease, opacity 0.3s ease; +} + +.ticker-wrapper.show-ticker { + transform: translateY(0); + opacity: 1; +} + +/* Badge wrapper */ +.ticker-badge-block { + flex-shrink: 0; + background: rgba(0, 0, 0, 0.15); + /* Slightly darker than primary */ + height: 100%; + display: flex; + align-items: center; + padding: 0 20px; + border-right: 1px solid rgba(255, 255, 255, 0.1); + position: relative; + z-index: 2; +} + +/* Badge "INFO TERBARU" */ +.ticker-badge { + background: var(--color-secondary); + color: #1a1a1a; + font-size: 10px; + font-weight: 800; + padding: 4px 8px; + border-radius: 2px; + white-space: nowrap; + letter-spacing: 0.8px; + text-transform: uppercase; +} + +/* Area overflow ticker */ +.ticker-scroll-area { + flex: 1; + overflow: hidden; + height: 100%; + position: relative; + display: flex; + align-items: center; +} + +/* Inner wrapper: dua salinan teks side-by-side, dianimasikan */ +.ticker-inner { + display: flex; + align-items: center; + animation: ticker-scroll 40s linear infinite; + will-change: transform; +} + +.ticker-inner:hover { + animation-play-state: paused; +} + +/* Setiap salinan teks ticker */ +.ticker-track { + display: inline-flex; + align-items: center; + white-space: nowrap; + font-size: 12px; + font-weight: 500; + color: rgba(255, 255, 255, 0.85); + padding-left: 20px; +} + +@keyframes ticker-scroll { + 0% { + transform: translateX(0); + } + + 100% { + transform: translateX(-50%); + } +} + +/* ===== HERO SECTION ===== */ +.hero-section { + min-height: 500px; + position: relative; + display: flex; + align-items: center; + padding: 48px 0 60px; + overflow: hidden; +} + +/* Background — solid dark blue sesuai Figma */ +.hero-bg { + position: absolute; + inset: 0; + background: #003150; + z-index: 0; +} + +/* Subtle dot grid overlay */ +.hero-bg::before { + content: ''; + position: absolute; + inset: 0; + background-image: + radial-gradient(circle at 10% 80%, rgba(245, 166, 35, 0.08) 0%, transparent 40%), + radial-gradient(circle at 90% 20%, rgba(255, 255, 255, 0.03) 0%, transparent 35%); +} + +.hero-bg::after { + content: ''; + position: absolute; + inset: 0; + background-image: radial-gradient(rgba(255, 255, 255, 0.025) 1px, transparent 1px); + background-size: 28px 28px; +} + +.hero-content { + position: relative; + z-index: 10; +} + +/* Badge hero — outline style sesuai Figma (border kuning, bg transparan) */ +.hero-badge { + display: inline-flex; + align-items: center; + gap: 6px; + border: 1.5px solid var(--color-secondary); + color: var(--color-secondary); + background: rgba(245, 166, 35, 0.08); + font-size: 11px; + font-weight: 700; + padding: 6px 16px; + border-radius: 999px; + text-transform: uppercase; + letter-spacing: 1px; + margin-bottom: 20px; +} + +.hero-title { + font-family: var(--font-serif); + font-size: clamp(36px, 5vw, 60px); + font-weight: 800; + color: white; + line-height: 1.15; + margin-bottom: 12px; +} + +.hero-subtitle { + font-size: 15px; + color: rgba(255, 255, 255, 0.75); + font-style: italic; + margin-bottom: 16px; +} + +.hero-desc { + font-size: 14px; + color: rgba(255, 255, 255, 0.65); + line-height: 1.7; + max-width: 440px; + margin-bottom: 32px; +} + +/* CTA button — kuning solid, teks gelap (sesuai Figma) */ +.hero-cta { + display: inline-flex; + align-items: center; + gap: 8px; + background: var(--color-secondary); + color: #1a1a1a; + font-weight: 700; + font-size: 14px; + padding: 13px 26px; + border-radius: 8px; + text-decoration: none; + transition: var(--transition); + box-shadow: 0 4px 16px rgba(245, 166, 35, 0.35); +} + +.hero-cta:hover { + background: #e8960f; + transform: translateY(-2px); + box-shadow: 0 8px 24px rgba(245, 166, 35, 0.45); + color: #1a1a1a; +} + +.hero-cta i { + transition: transform 0.3s; + font-size: 13px; +} + +.hero-cta:hover i { + transform: translateX(4px); +} + +/* Hero image — foto lebih besar dan rounded sesuai Figma */ +.hero-image-wrap { + position: relative; + max-width: 480px; + margin-left: auto; +} + +.hero-image-wrap img { + width: 100%; + height: 400px; + object-fit: cover; + border-radius: 16px; + box-shadow: 0 20px 50px rgba(0, 0, 0, 0.45); + border: 2px solid rgba(255, 255, 255, 0.1); + display: block; +} + +/* Small decorative circle */ +.hero-image-wrap::before { + content: ''; + position: absolute; + bottom: -12px; + right: -12px; + width: 60px; + height: 60px; + border: 3px solid var(--color-secondary); + border-radius: 50%; + opacity: 0.3; + z-index: -1; +} + +/* ===== STATISTICS SECTION ===== */ +.stats-section { + background: var(--color-secondary); + padding: 36px 0; +} + +.stat-item { + text-align: center; + padding: 0 20px; + position: relative; +} + +.stat-item+.stat-item::before { + content: ''; + position: absolute; + left: 0; + top: 15%; + height: 70%; + width: 1px; + background: rgba(255, 255, 255, 0.35); +} + +.stat-number { + font-family: var(--font-serif); + font-size: clamp(36px, 5vw, 52px); + font-weight: 800; + color: #1a1a1a; + line-height: 1; + letter-spacing: -1px; +} + +.stat-label { + font-size: 11px; + color: #2d2d2d; + margin-top: 8px; + line-height: 1.45; + font-weight: 500; +} + +/* ===== SECTION LABEL & BADGE ===== */ +.section-badge { + display: inline-flex; + align-items: center; + border: 1px solid rgba(245, 166, 35, 0.4); + background: rgba(245, 166, 35, 0.12); + color: var(--color-secondary); + font-size: 11px; + font-weight: 700; + padding: 5px 14px; + border-radius: 999px; + text-transform: uppercase; + letter-spacing: 1px; +} + +.section-label { + font-size: 11px; + font-weight: 800; + color: var(--color-secondary); + text-transform: uppercase; + letter-spacing: 2px; + display: block; + margin-bottom: 6px; +} + +.section-title { + font-family: var(--font-serif); + font-size: clamp(32px, 4vw, 42px); + font-weight: 800; + color: #003150; + line-height: 1.2; + margin-bottom: 12px; + letter-spacing: -0.5px; +} + +.section-desc { + font-size: 14px; + color: #64748b; + line-height: 1.65; + max-width: 600px; +} + +/* Button Panduan Beasiswa */ +.btn-panduan { + display: inline-flex; + align-items: center; + gap: 8px; + background: rgba(245, 166, 35, 0.12); + border: 1px solid rgba(245, 166, 35, 0.3); + color: var(--color-secondary); + font-weight: 600; + font-size: 13px; + padding: 10px 20px; + border-radius: 6px; + text-decoration: none; + transition: var(--transition); +} + +.btn-panduan:hover { + background: rgba(245, 166, 35, 0.2); + border-color: rgba(245, 166, 35, 0.5); +} + +/* ===== FILTER TABS ===== */ +.filter-tabs { + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: center; +} + +.filter-tab { + padding: 8px 20px; + font-size: 12px; + font-weight: 600; + border-radius: 999px; + border: 1px solid #e5e7eb; + background: white; + color: #4b5563; + cursor: pointer; + transition: var(--transition); + white-space: nowrap; + letter-spacing: 0.3px; +} + +.filter-tab:hover { + border-color: #d1d5db; + color: #003150; + background: #f9fafb; +} + +.filter-tab.active { + background: #003150; + color: white; + border-color: #003150; + box-shadow: none; +} + +/* ===== SEARCH INPUT ===== */ +.search-wrapper { + position: relative; +} + +.search-input { + width: 100%; + padding: 9px 16px 9px 38px; + border: 1px solid #e5e7eb; + border-radius: 999px; + font-size: 13px; + font-family: var(--font-inter); + color: var(--color-text); + outline: none; + transition: var(--transition); + background: white; +} + +.search-input:focus { + border-color: var(--color-primary); + box-shadow: 0 0 0 3px rgba(43, 69, 144, 0.08); +} + +.search-input::placeholder { + color: #9ca3af; +} + +.search-icon { + position: absolute; + left: 14px; + top: 50%; + transform: translateY(-50%); + color: #9ca3af; + font-size: 13px; + pointer-events: none; +} + +/* ===== SORT BUTTON ===== */ +.btn-sort { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 9px 20px; + background: white; + border: 1px solid #e5e7eb; + border-radius: 999px; + color: #4b5563; + font-size: 12px; + font-weight: 500; + cursor: pointer; + transition: var(--transition); + white-space: nowrap; +} + +.btn-sort:hover { + background: #f9fafb; + border-color: #d1d5db; + color: #003150; +} + +/* ===== SCHOLARSHIP CARDS — Schoters Style ===== */ +.scholarship-card { + background: white; + border-radius: 16px; + box-shadow: 0 2px 16px rgba(0,0,0,0.06); + overflow: hidden; + transition: var(--transition); + border: 1px solid #eef2f8; + display: flex; + flex-direction: column; + position: relative; + cursor: default; +} + +.scholarship-card:hover { + box-shadow: 0 8px 32px rgba(0,49,80,0.12); + transform: translateY(-4px); + border-color: rgba(0,49,80,0.08); +} + + +/* ─── Card Body ─── */ +.card-body { + padding: 20px 20px 18px; + flex: 1; + display: flex; + flex-direction: column; +} + +/* ─── Title ─── */ +.card-title { + font-size: 15px; + font-weight: 800; + color: #0f172a; + line-height: 1.4; + margin: 0 0 6px; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; +} + +/* ─── Subtitle (Funding Type) ─── */ +.card-subtitle { + font-size: 12.5px; + color: #64748b; + margin: 0 0 12px; + font-weight: 400; +} + +/* ─── Pills Row ─── */ +.card-pills { + display: flex; + flex-wrap: wrap; + gap: 7px; + margin-bottom: 14px; +} + +.card-pill { + display: inline-flex; + align-items: center; + padding: 4px 12px; + border-radius: 999px; + font-size: 11.5px; + font-weight: 600; + background: #f1f5f9; + color: #475569; + letter-spacing: 0.2px; +} + +.card-pill-level { + background: #dbeafe; + color: #1e40af; +} + +/* ─── Divider ─── */ +.card-divider { + border: none; + border-top: 1px solid #f1f5f9; + margin: 0 0 14px; +} + +/* ─── Two-column Date Row ─── */ +.card-dates { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; + margin-bottom: 14px; +} + +.card-date-col { + display: flex; + flex-direction: column; + gap: 3px; +} + +.card-date-label { + font-size: 11px; + font-weight: 700; + color: #0f172a; +} + +.card-date-value { + font-size: 12px; + color: #64748b; + font-weight: 400; +} + +/* ─── Status Button ─── */ +.card-status-btn { + width: 100%; + padding: 10px 16px; + border-radius: 8px; + font-size: 13px; + font-weight: 700; + border: none; + cursor: default; + text-align: center; + margin-bottom: 10px; +} + +.status-btn-buka { + background: #dcfce7; + color: #15803d; +} + +.status-btn-ditutup { + background: #fee2e2; + color: #b91c1c; +} + +.status-btn-segera { + background: #fef9c3; + color: #a16207; +} + +/* ─── Outline Selengkapnya Button ─── */ +.btn-selengkapnya { + width: 100%; + padding: 10px 16px; + border-radius: 8px; + border: 1.5px solid #e2e8f0; + background: white; + color: #0f172a; + font-size: 13px; + font-weight: 700; + cursor: pointer; + text-align: center; + transition: var(--transition); + font-family: var(--font-sans); + display: block; +} + +.btn-selengkapnya:hover { + border-color: var(--color-primary); + color: var(--color-primary); + background: #f8faff; +} + +/* Entrance animation */ +@keyframes fadeInCard { + from { opacity: 0; transform: translateY(16px); } + to { opacity: 1; transform: translateY(0); } +} + +.scholarship-card.fade-in-up { + animation: fadeInCard 0.4s ease both; +} + +/* ===== EMPTY STATE ===== */ +.empty-state { + grid-column: 1 / -1; + text-align: center; + padding: 64px 24px; + color: #94a3b8; +} + +.empty-state i { + font-size: 48px; + color: #e2e8f0; + margin-bottom: 16px; + display: block; +} + +/* ===== PAGINATION ===== */ +.pagination-container { + display: flex; + align-items: center; + gap: 6px; + justify-content: center; + margin-top: 40px; +} + +.page-btn { + width: 38px; + height: 38px; + border-radius: 8px; + border: 2px solid #e2e8f0; + background: white; + color: #64748b; + font-size: 13px; + font-weight: 600; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: var(--transition); + font-family: var(--font-inter); +} + +.page-btn:hover:not(:disabled) { + border-color: var(--color-primary); + color: var(--color-primary); + background: #eff3ff; +} + +.page-btn.active { + background: var(--color-primary); + border-color: var(--color-primary); + color: white; + box-shadow: 0 4px 12px rgba(43, 69, 144, 0.3); +} + +.page-btn:disabled { + opacity: 0.38; + cursor: not-allowed; +} + +/* ===== POPUP MODAL ===== */ +#popup-overlay { + position: fixed; + inset: 0; + z-index: 9999; + display: none; + align-items: center; + justify-content: center; + padding: 16px; +} + +#popup-overlay.show { + display: flex; +} + +.popup-backdrop { + position: absolute; + inset: 0; + background: var(--color-overlay); + backdrop-filter: blur(4px); + -webkit-backdrop-filter: blur(4px); +} + +.popup-modal { + position: relative; + background: white; + border-radius: 20px; + box-shadow: var(--shadow-popup); + width: 100%; + max-width: 500px; + max-height: 90vh; + overflow-y: auto; + z-index: 10; + transition: opacity 0.25s ease, transform 0.25s ease; + opacity: 0; + transform: scale(0.92) translateY(8px); +} + +#popup-overlay.show .popup-modal { + opacity: 1; + transform: scale(1) translateY(0); +} + +.popup-close { + position: absolute; + top: 14px; + right: 14px; + z-index: 20; + width: 34px; + height: 34px; + border-radius: 50%; + background: rgba(255, 255, 255, 0.9); + backdrop-filter: blur(4px); + border: none; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + color: #374151; + font-size: 14px; + transition: var(--transition); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12); +} + +.popup-close:hover { + background: #f1f5f9; + transform: rotate(90deg); +} + +.popup-hero { + width: 100%; + height: 200px; + object-fit: cover; + display: block; + border-radius: 20px 20px 0 0; +} + +.popup-hero-logo { + width: 100%; + height: 200px; + background: linear-gradient(135deg, #EEF2FF 0%, #E0E7FF 100%); + display: flex; + align-items: center; + justify-content: center; + border-radius: 20px 20px 0 0; +} + +.popup-hero-logo img { + max-height: 100px; + max-width: 75%; + object-fit: contain; +} + +.popup-body { + padding: 24px; +} + +.popup-kategori-row { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 12px; +} + +.popup-kategori { + font-size: 11px; + color: var(--color-accent); + font-weight: 600; + background: #f1f5f9; + padding: 3px 10px; + border-radius: 999px; +} + +.popup-title { + font-family: var(--font-serif); + font-size: 20px; + font-weight: 700; + color: var(--color-primary); + line-height: 1.3; + margin-bottom: 10px; +} + +.popup-desc { + font-size: 13px; + color: #4b5563; + line-height: 1.65; + margin-bottom: 20px; +} + +.popup-section-title { + font-size: 12px; + font-weight: 700; + color: #374151; + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 12px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.popup-section-title i { + color: var(--color-secondary); +} + +.syarat-list { + list-style: none; + display: flex; + flex-direction: column; + gap: 8px; + margin-bottom: 20px; +} + +.syarat-list li { + display: flex; + align-items: flex-start; + gap: 8px; + font-size: 13px; + color: #4b5563; + line-height: 1.5; +} + +.syarat-list li i { + color: var(--color-success); + font-size: 11px; + margin-top: 3px; + flex-shrink: 0; +} + +.popup-periode { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; + background: #f8fafc; + border-radius: 10px; + padding: 14px 16px; + margin-bottom: 20px; +} + +.popup-periode-item label { + font-size: 10px; + color: #94a3b8; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + display: block; + margin-bottom: 4px; +} + +.popup-periode-item span { + font-size: 13px; + font-weight: 600; + color: #374151; +} + +.popup-kontak { + background: #fffbf0; + border: 1px solid #fde68a; + border-radius: 10px; + padding: 12px 14px; + margin-bottom: 20px; + font-size: 13px; + color: #92400e; + display: flex; + align-items: flex-start; + gap: 8px; +} + +.popup-kontak i { + color: var(--color-secondary); + margin-top: 1px; + flex-shrink: 0; +} + +.popup-cta { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + width: 100%; + background: var(--color-primary); + color: white; + font-weight: 700; + font-size: 14px; + padding: 14px 20px; + border-radius: 12px; + text-decoration: none; + transition: var(--transition); + border: none; + cursor: pointer; +} + +.popup-cta:hover { + background: #1e3272; + transform: translateY(-1px); + box-shadow: 0 6px 20px rgba(43, 69, 144, 0.3); + color: white; +} + +.popup-cta.disabled { + background: #94a3b8; + pointer-events: none; +} + +.popup-cta i { + transition: transform 0.3s; +} + +.popup-cta:hover i { + transform: translateX(4px); +} + +/* ===== TESTIMONIALS ===== */ +.testimonials-section { + background: #f8fafc; + padding: 72px 0; +} + +.testimonial-card { + background: white; + border-radius: 16px; + padding: 28px 22px; + text-align: center; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.06); + transition: var(--transition); + border: 1px solid #f0f4f8; + height: 100%; +} + +.testimonial-card:hover { + box-shadow: 0 10px 36px rgba(43, 69, 144, 0.12); + transform: translateY(-5px); +} + +.testimonial-avatar { + width: 82px; + height: 82px; + border-radius: 50%; + object-fit: cover; + margin: 0 auto 16px; + border: 3px solid var(--color-secondary); + display: block; +} + +.testimonial-name { + font-family: var(--font-serif); + font-size: 15px; + font-weight: 700; + color: var(--color-primary); + margin-bottom: 4px; +} + +.testimonial-beasiswa { + font-size: 11px; + color: var(--color-secondary); + font-weight: 600; + margin-bottom: 14px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.testimonial-quote { + font-size: 13px; + color: #4b5563; + line-height: 1.65; + font-style: italic; + position: relative; +} + +/* Swiper custom styles */ +.swiper-pagination-bullet { + background: #cbd5e1 !important; + opacity: 1 !important; + width: 8px !important; + height: 8px !important; + transition: var(--transition); +} + +.swiper-pagination-bullet-active { + background: var(--color-secondary) !important; + width: 24px !important; + border-radius: 4px !important; +} + +/* ===== CTA SECTION ===== */ +.cta-section { + padding: 72px 0; + background: #f8fafc; +} + +.cta-card { + background: #003150; + border-radius: 24px; + padding: 64px 40px; + text-align: center; + position: relative; + overflow: hidden; +} + +.cta-card::before, +.cta-card::after { + content: ''; + position: absolute; + border-radius: 50%; + background: rgba(255, 255, 255, 0.04); +} + +.cta-card::before { + width: 300px; + height: 300px; + top: -100px; + right: -80px; +} + +.cta-card::after { + width: 200px; + height: 200px; + bottom: -60px; + left: -60px; +} + +.cta-title { + font-family: var(--font-serif); + font-size: clamp(22px, 3vw, 30px); + font-weight: 700; + color: white; + margin-bottom: 12px; + position: relative; + z-index: 1; +} + +.cta-desc { + font-size: 14px; + color: rgba(255, 255, 255, 0.68); + max-width: 420px; + margin: 0 auto 32px; + line-height: 1.65; + position: relative; + z-index: 1; +} + +.cta-buttons { + display: flex; + gap: 14px; + justify-content: center; + flex-wrap: wrap; + position: relative; + z-index: 1; +} + +.cta-btn-primary { + display: inline-flex; + align-items: center; + gap: 8px; + background: var(--color-secondary); + color: #0a1530; + font-weight: 700; + font-size: 13px; + padding: 13px 26px; + border-radius: 8px; + text-decoration: none; + transition: var(--transition); + box-shadow: 0 4px 16px rgba(245, 166, 35, 0.4); +} + +.cta-btn-primary:hover { + background: #e8960f; + transform: translateY(-2px); + box-shadow: 0 8px 24px rgba(245, 166, 35, 0.5); + color: #0a1530; +} + +.cta-btn-secondary { + display: inline-flex; + align-items: center; + gap: 8px; + background: transparent; + border: 2px solid rgba(255, 255, 255, 0.5); + color: white; + font-weight: 700; + font-size: 13px; + padding: 11px 26px; + border-radius: 10px; + text-decoration: none; + transition: var(--transition); + cursor: pointer; +} + + +.cta-btn-secondary:hover { + background: rgba(255, 255, 255, 0.12); + border-color: white; + color: white; + transform: translateY(-2px); +} + +/* ===== FOOTER ===== */ +footer { + font-family: 'Roboto', sans-serif; +} + +.site-footer { + background: var(--color-primary); + color: white; + position: relative; + overflow: hidden; +} + +.footer-top { + padding: 52px 0 32px; +} + +.footer-follow-title { + text-align: center; + color: white; + font-size: 11px; + font-weight: 700; + letter-spacing: 2px; + margin-bottom: 12px; +} + +.social-icons { + display: flex; + justify-content: center; + gap: 16px; + margin-bottom: 40px; +} + +.social-icon { + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 16px; + transition: var(--transition); + text-decoration: none; +} + +.social-icon:hover { + color: var(--color-secondary); + transform: translateY(-2px); +} + +.footer-logo { + display: flex; + align-items: center; + justify-content: center; + gap: 12px; + margin-bottom: 20px; +} + +.footer-logo img { + height: 44px; + width: auto; + object-fit: contain; +} + +.footer-logo-text { + text-align: left; + margin-left: 8px; +} + +.footer-logo-text .name { + font-family: var(--font-serif); + font-size: 20px; + font-weight: 700; + color: white; + line-height: 1.2; + letter-spacing: 2px; +} + +.footer-logo-text .sub { + font-size: 10px; + color: rgba(255, 255, 255, 0.7); + margin-top: 2px; + letter-spacing: 1px; +} + +.footer-address { + text-align: center; + color: rgba(255, 255, 255, 0.6); + font-size: 12px; + line-height: 1.8; + margin-top: 32px; +} + +.footer-address i { + color: var(--color-secondary); +} + +.address-main { + margin-bottom: 16px; +} + +.address-contact { + font-size: 11px; + color: rgba(255, 255, 255, 0.5); + margin-bottom: 40px; +} + +.footer-divider { + border: none; + border-top: 1px solid rgba(255, 255, 255, 0.08); + margin: 28px 0 20px; +} + +.footer-copy { + text-align: center; + font-size: 11px; + color: rgba(255, 255, 255, 0.28); + padding-bottom: 28px; +} + +.footer-illustration { + width: 100%; + display: block; + max-height: 180px; + object-fit: cover; + object-position: top; +} + +/* ===== BREADCRUMB ===== */ +.breadcrumb { + display: flex; + align-items: center; + gap: 8px; + font-size: 11px; + color: rgba(255, 255, 255, 0.5); + margin-bottom: 24px; +} + +.breadcrumb a { + color: rgba(255, 255, 255, 0.5); + text-decoration: none; + transition: color 0.2s; +} + +.breadcrumb a:hover { + color: rgba(255, 255, 255, 0.85); +} + +.breadcrumb .sep { + font-size: 9px; +} + +.breadcrumb .current { + color: rgba(255, 255, 255, 0.85); +} + +/* ===== ANIMATIONS ===== */ +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(18px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} + +.fade-in-up { + animation: fadeInUp 0.45s ease forwards; +} + +/* Skeleton loading */ +.skeleton { + background: linear-gradient(90deg, #f1f5f9 25%, #e2e8f0 50%, #f1f5f9 75%); + background-size: 200% 100%; + animation: shimmer 1.6s infinite; + border-radius: 8px; +} + +@keyframes shimmer { + 0% { + background-position: 200% 0; + } + + 100% { + background-position: -200% 0; + } +} + +/* ===== RESPONSIVE ===== */ +@media (max-width: 767px) { + :root { + --navbar-height: 104px; + } + + .hero-section { + padding: 32px 0 48px; + min-height: auto; + } + + .hero-title { + font-size: 32px; + } + + .hero-image-wrap { + display: none; + } + + .stats-section { + padding: 24px 0; + } + + .stat-item+.stat-item::before { + display: none; + } + + .cta-card { + padding: 40px 20px; + } + + .filter-tabs { + gap: 6px; + } + + .filter-tab { + padding: 7px 12px; + font-size: 10px; + } + + .popup-modal { + border-radius: 14px; + } + + .popup-periode { + grid-template-columns: 1fr; + } + + .testimonial-card { + padding: 22px 16px; + } +} + +@media (min-width: 768px) and (max-width: 1023px) { + .hero-image-wrap img { + height: 300px; + } +} + +@media (min-width: 1024px) { + .hero-section { + min-height: 540px; + } +} \ No newline at end of file diff --git a/groups/ApeNameTeamE/assets/images/hero/mahasiswa.png b/groups/ApeNameTeamE/assets/images/hero/mahasiswa.png new file mode 100644 index 0000000..b8244c2 Binary files /dev/null and b/groups/ApeNameTeamE/assets/images/hero/mahasiswa.png differ diff --git a/groups/ApeNameTeamE/assets/images/hero/pontianak-illustration.png b/groups/ApeNameTeamE/assets/images/hero/pontianak-illustration.png new file mode 100644 index 0000000..922a0e0 Binary files /dev/null and b/groups/ApeNameTeamE/assets/images/hero/pontianak-illustration.png differ diff --git a/groups/ApeNameTeamE/assets/images/logo/logo-untan.png b/groups/ApeNameTeamE/assets/images/logo/logo-untan.png new file mode 100644 index 0000000..3d45d4e Binary files /dev/null and b/groups/ApeNameTeamE/assets/images/logo/logo-untan.png differ diff --git a/groups/ApeNameTeamE/assets/js/API.js b/groups/ApeNameTeamE/assets/js/API.js new file mode 100644 index 0000000..628da88 --- /dev/null +++ b/groups/ApeNameTeamE/assets/js/API.js @@ -0,0 +1,81 @@ +// ===================================================== +// API.js — Mekanisme Fetch Data Beasiswa (terpusat) +// ----------------------------------------------------- +// Alur: coba API remote (Directus publik) → jika gagal, +// fallback ke berkas lokal `data/beasiswa.json`. +// +// Data remote dinormalisasi ke skema UI yang sama dengan +// data lokal agar filter.js dan popup.js tidak perlu berubah. +// ===================================================== + +(function (global) { + 'use strict'; + + const CONFIG = { + baseUrl: 'https://api.ifuntanhub.dev', + collection: '/items/beasiswa', + localSource: 'data/beasiswa.json', + }; + + // Normalise status remote → nilai yang dipakai UI + function normalizeStatus(s) { + if (!s) return 'Ditutup'; + const lower = s.toLowerCase(); + if (lower === 'dibuka' || lower === 'buka') return 'Buka'; + if (lower === 'ditutup') return 'Ditutup'; + return s; // "Segera Dibuka" atau nilai lain: tampilkan apa adanya + } + + // Petakan satu item Directus ke skema yang diharapkan filter.js / popup.js + function normalizeItem(item) { + const parts = [item.jenjang, item.negara, item.tipe_pendanaan].filter(Boolean); + return { + id: String(item.id), + nama: item.nama || '', + status: normalizeStatus(item.status), + kategori: item.tipe_pendanaan || '', + thumbnail: '', + deskripsi_singkat: parts.join(' · ') || 'Info beasiswa tersedia di sumber resmi.', + tanggal_buka: item.tanggal_buka || '', + tanggal_tutup: item.tenggat_waktu || '', + url_sumber: item.url || '', + syarat_umum: [], + kontak: '', + tags: parts, + }; + } + + async function fetchRemote() { + const res = await fetch(CONFIG.baseUrl + CONFIG.collection); + if (!res.ok) throw new Error(`API merespons HTTP ${res.status}`); + const json = await res.json(); + return (json.data || []).map(normalizeItem); + } + + async function fetchLocal() { + const res = await fetch(CONFIG.localSource); + if (!res.ok) throw new Error(`Gagal memuat data lokal (HTTP ${res.status})`); + const json = await res.json(); + return Array.isArray(json) ? json : json.data || []; + } + + async function getBeasiswa() { + try { + // Prioritas: data lokal (lengkap: url_sumber, syarat_umum, kontak tersedia) + // Remote API digunakan hanya sebagai fallback jika data lokal tidak ditemukan. + const data = await fetchLocal(); + return { data, source: 'local' }; + } catch (err) { + console.warn('[BeasiswaAPI] Data lokal gagal, fallback ke API remote:', err.message); + try { + const data = await fetchRemote(); + return { data, source: 'remote' }; + } catch (remoteErr) { + console.error('[BeasiswaAPI] Semua sumber gagal:', remoteErr.message); + throw remoteErr; + } + } + } + + global.BeasiswaAPI = { config: CONFIG, getBeasiswa, fetchRemote, fetchLocal }; +})(window); diff --git a/groups/ApeNameTeamE/assets/js/carousel.js b/groups/ApeNameTeamE/assets/js/carousel.js new file mode 100644 index 0000000..3dc3286 --- /dev/null +++ b/groups/ApeNameTeamE/assets/js/carousel.js @@ -0,0 +1,140 @@ +// ===================================================== +// carousel.js — Swiper.js Carousel Testimoni +// Touch-friendly, auto-play, responsive breakpoints +// + DUKUNGAN MULTI-BAHASA (ID/EN) Terintegrasi +// ===================================================== + +let testimoniSwiper = null; +let allTestimoniData = []; // Simpan data di memori agar tidak perlu loading ulang saat ganti bahasa + +// 1. Variabel penampung bahasa yang terhubung otomatis dengan tombol ID/EN +let activeCarouselLang = localStorage.getItem('if_untan_lang') || document.documentElement.lang || 'id'; + +// 2. Tangkap event perubahan bahasa secara langsung dari localization.js +document.addEventListener('languageChanged', (e) => { + // Update bahasa + if (e.detail && e.detail.lang) { + activeCarouselLang = e.detail.lang; + } else if (typeof window.getLang === 'function') { + activeCarouselLang = window.getLang(); + } else { + activeCarouselLang = document.documentElement.lang || 'id'; + } + + // Jika data sudah ada, langsung cetak ulang testimoninya + if (allTestimoniData && allTestimoniData.length > 0) { + renderTestimoniSlides(allTestimoniData); + initSwiper(allTestimoniData.length); // Swiper harus di-restart agar animasinya tidak rusak + } +}); + +/** Render slide testimoni ke dalam swiper-wrapper */ +function renderTestimoniSlides(data) { + const wrapper = document.getElementById('testimonial-wrapper'); + if (!wrapper) return; + + const lang = activeCarouselLang; // Gunakan bahasa terbaru + + wrapper.innerHTML = data.map(t => { + // Logika Pintar: Cek mode bahasa. Jika 'en' dan teks terjemahannya ada, pakai bahasa Inggris. + // Jika tidak, tetap tampilkan teks bahasa Indonesia (sebagai cadangan). + const beasiswaName = (lang === 'en' && t.nama_beasiswa_en) ? t.nama_beasiswa_en : t.nama_beasiswa; + const quote = (lang === 'en' && t.kutipan_en) ? t.kutipan_en : t.kutipan; + + return ` +
    +
    + Foto ${t.nama} +
    + ${t.nama.charAt(0).toUpperCase()} +
    + +
    ${t.nama}
    +
    ${beasiswaName}
    +

    "${quote}"

    +
    +
    + `; + }).join(''); +} + +/** Inisialisasi Swiper setelah slide di-render */ +function initSwiper(count) { + // Bersihkan swiper lama jika ada (penting agar tidak menumpuk saat ganti bahasa) + if (testimoniSwiper) { + testimoniSwiper.destroy(true, true); + testimoniSwiper = null; + } + + testimoniSwiper = new Swiper('.testimonial-swiper', { + slidesPerView: 1, + spaceBetween: 24, + loop: count > 3, + centerInsufficientSlides: true, + autoplay: { + delay: 4500, + disableOnInteraction: false, + pauseOnMouseEnter: true, + }, + pagination: { + el: '.testimonial-pagination', + clickable: true, + }, + grabCursor: true, + breakpoints: { + 640: { slidesPerView: 2 }, + 1024: { slidesPerView: 3 }, + }, + // Dukungan Aksesibilitas Dinamis (VoiceOver / Screen Reader) + a11y: { + prevSlideMessage: activeCarouselLang === 'en' ? 'Previous slide' : 'Slide sebelumnya', + nextSlideMessage: activeCarouselLang === 'en' ? 'Next slide' : 'Slide berikutnya', + }, + }); +} + +/** Fetch data testimoni dan render */ +async function initCarousel() { + const section = document.getElementById('testimonial'); + + try { + const res = await fetch('data/testimoni.json'); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + + const data = await res.json(); + allTestimoniData = data; // Simpan ke variabel global + + if (!data || data.length === 0) { + section?.classList.add('hidden'); + return; + } + + renderTestimoniSlides(data); + requestAnimationFrame(() => initSwiper(data.length)); + + } catch { + section?.classList.add('hidden'); + } +} + +document.addEventListener('DOMContentLoaded', initCarousel); \ No newline at end of file diff --git a/groups/ApeNameTeamE/assets/js/counter.js b/groups/ApeNameTeamE/assets/js/counter.js new file mode 100644 index 0000000..b68851a --- /dev/null +++ b/groups/ApeNameTeamE/assets/js/counter.js @@ -0,0 +1,53 @@ +// ===================================================== +// counter.js — Animasi Counter Statistik +// Menggunakan IntersectionObserver + requestAnimationFrame +// ===================================================== + +/** + * Animasi angka dari 0 ke nilai target + * @param {HTMLElement} el - Elemen yang menampilkan angka + * @param {number} target - Angka target + * @param {number} duration - Durasi animasi dalam ms + */ +function animateCounter(el, target, duration = 1800) { + const startTime = performance.now(); + + function update(currentTime) { + const elapsed = currentTime - startTime; + const progress = Math.min(elapsed / duration, 1); + + // Ease out cubic — percepat di awal, lambat di akhir + const eased = 1 - Math.pow(1 - progress, 3); + const current = Math.round(eased * target); + + el.textContent = current; + + if (progress < 1) { + requestAnimationFrame(update); + } + } + + requestAnimationFrame(update); +} + +function initCounters() { + const counters = document.querySelectorAll('[data-counter]'); + if (!counters.length) return; + + const observer = new IntersectionObserver( + (entries) => { + entries.forEach(entry => { + if (entry.isIntersecting && !entry.target.dataset.animated) { + entry.target.dataset.animated = 'true'; + const target = parseInt(entry.target.dataset.counter, 10) || 0; + animateCounter(entry.target, target); + } + }); + }, + { threshold: 0.6 } + ); + + counters.forEach(el => observer.observe(el)); +} + +document.addEventListener('DOMContentLoaded', initCounters); diff --git a/groups/ApeNameTeamE/assets/js/filter.js b/groups/ApeNameTeamE/assets/js/filter.js new file mode 100644 index 0000000..c1894c5 --- /dev/null +++ b/groups/ApeNameTeamE/assets/js/filter.js @@ -0,0 +1,288 @@ +// ===================================================== +// filter.js — Fetch JSON, Render Kartu, Filter Tab, Pencarian, Paginasi +// Filter dan search berjalan KUMULATIF +// + DUKUNGAN MULTI-BAHASA (ID/EN) Terintegrasi +// ===================================================== + +const ITEMS_PER_PAGE = 6; + +let allData = []; +let activeFilter = 'semua'; +let searchKeyword = ''; +let currentPage = 1; + +// 1. Variabel penampung bahasa yang terhubung otomatis dengan tombol ID/EN +let activeLang = localStorage.getItem('if_untan_lang') || document.documentElement.lang || 'id'; + +// 2. Tangkap event perubahan bahasa secara langsung dari localization.js +document.addEventListener('languageChanged', (e) => { + if (e.detail && e.detail.lang) { + activeLang = e.detail.lang; // Perbarui bahasa! + } else { + activeLang = document.documentElement.lang || 'id'; + } + + // Jika data sudah ada, langsung cetak ulang kartunya + if (allData.length > 0) { + renderCards(); + } +}); + +// 3. Format tanggal cerdas berdasarkan bahasa +function formatDate(str) { + if (!str) return '—'; + const d = new Date(str); + if (isNaN(d.getTime())) return str; + + return d.toLocaleDateString(activeLang === 'en' ? 'en-US' : 'id-ID', { + day: 'numeric', month: 'long', year: 'numeric' + }); +} + +// 4. Kamus mini untuk teks dinamis dari JSON (Negara & Kategori) +function translateDynamic(text, lang) { + if (!text || lang === 'id') return text; + + const dict = { + 'Amerika Serikat': 'United States', + 'Swiss': 'Switzerland', + 'Pemerintah': 'Government', + 'Swasta': 'Private', + }; + + return dict[text] || text; +} + +function sanitize(str) { + return str.replace(/[<>&"']/g, c => + ({ '<': '<', '>': '>', '&': '&', '"': '"', "'": ''' }[c]) + ); +} + +function createCard(bsw) { + const lang = activeLang; // Selalu gunakan bahasa aktif terbaru + + // Terjemahan Tombol Status + let statusClass = 'status-btn-ditutup'; + let statusLabel = lang === 'en' ? 'Closed' : 'Ditutup'; + + const statusLower = (bsw.status || '').toLowerCase(); + if (statusLower === 'buka' || statusLower === 'dibuka') { + statusClass = 'status-btn-buka'; + statusLabel = lang === 'en' ? 'Registration Open' : 'Pendaftaran Dibuka'; + } else if (statusLower.includes('segera')) { + statusClass = 'status-btn-segera'; + statusLabel = lang === 'en' ? 'Opening Soon' : 'Segera Dibuka'; + } + + // --- KAMUS MINI UNTUK KARTU --- + const textMulai = lang === 'en' ? 'Start' : 'Mulai'; + const textDeadline = lang === 'en' ? 'Deadline' : 'Tenggat'; + const textSelengkapnya = lang === 'en' ? 'Read More' : 'Selengkapnya'; + + // Ambil pills (tags) dan terjemahkan negaranya + const tags = Array.isArray(bsw.tags) ? bsw.tags : []; + const jenjang = tags[0] || ''; + const skipPatterns = /^(S1|S2|S3|D3|D4|Swasta|Pemerintah|Fully Funded|Partially Funded|Beasiswa Penuh|Beasiswa Sebagian|Prestasi)$/i; + const rawNegara = tags.slice(1).find(t => !skipPatterns.test(t)) || ''; + const negara = translateDynamic(rawNegara, lang); + + // Terjemahan Subtitle (Kategori Beasiswa) + const tipe = bsw.kategori || ''; + const rawTipe = tipe.replace(/^beasiswa\s+/i, ''); + let subtitle = ''; + if (rawTipe) { + if (lang === 'en') { + const tipeEn = translateDynamic(rawTipe, lang); + subtitle = `${tipeEn} Scholarship`; + } else { + subtitle = `Beasiswa ${rawTipe}`; + } + } + + // Ambil nama dari versi Inggris jika ada + const namaBsw = (lang === 'en' && bsw.nama_en) ? bsw.nama_en : bsw.nama; + + const pillsHtml = [jenjang, negara] + .filter(Boolean) + .map((t, i) => `${t}`) + .join(''); + + const article = document.createElement('article'); + article.className = 'scholarship-card fade-in-up'; + article.setAttribute('role', 'listitem'); + article.setAttribute('data-id', bsw.id); + + article.innerHTML = ` +
    +

    ${namaBsw}

    + ${subtitle ? `

    ${subtitle}

    ` : ''} + ${pillsHtml ? `
    ${pillsHtml}
    ` : ''} +
    +
    +
    + ${textMulai} + ${formatDate(bsw.tanggal_buka)} +
    +
    + ${textDeadline} + ${formatDate(bsw.tanggal_tutup)} +
    +
    + + +
    + `; + + return article; +} + +function getFiltered() { + const kw = searchKeyword.toLowerCase(); + return allData.filter(bsw => { + + // Logika Filter Tab S1 & Studi Lanjut + let matchFilter = false; + const tags = bsw.tags || []; + + if (activeFilter === 'semua') { + matchFilter = true; + } else if (activeFilter === 's1') { + matchFilter = tags.some(t => t.includes('S1')); + } else if (activeFilter === 'studi_lanjut') { + matchFilter = tags.some(t => t.includes('S2') || t.includes('S3')); + } + + // Logika Pencarian Search Bar + const matchSearch = !kw || + bsw.nama.toLowerCase().includes(kw) || + bsw.deskripsi_singkat.toLowerCase().includes(kw) || + tags.some(t => t.toLowerCase().includes(kw)); + + return matchFilter && matchSearch; + }); +} + +function renderCards() { + const grid = document.getElementById('beasiswa-grid'); + const pagCont = document.getElementById('pagination'); + const filtered = getFiltered(); + const lang = activeLang; // Gunakan bahasa terbaru + + grid.innerHTML = ''; + + if (filtered.length === 0) { + const emptyTitle = lang === 'en' ? 'No scholarships found' : 'Tidak ada beasiswa ditemukan'; + const emptyDesc = lang === 'en' ? 'Try adjusting your filters or search keywords' : 'Coba ubah filter atau kata kunci pencarian Anda'; + + grid.innerHTML = ` +
    + +

    ${emptyTitle}

    +

    ${emptyDesc}

    +
    `; + pagCont.innerHTML = ''; + return; + } + + const totalPages = Math.ceil(filtered.length / ITEMS_PER_PAGE); + if (currentPage > totalPages) currentPage = totalPages; + const start = (currentPage - 1) * ITEMS_PER_PAGE; + const items = filtered.slice(start, start + ITEMS_PER_PAGE); + + items.forEach((bsw, i) => { + const card = createCard(bsw); + card.style.animationDelay = `${i * 55}ms`; + grid.appendChild(card); + }); + + renderPagination(totalPages, pagCont); +} + +function renderPagination(total, container) { + container.innerHTML = ''; + if (total <= 1) return; + + const make = (content, page, disabled = false, isActive = false) => { + const btn = document.createElement('button'); + btn.className = `page-btn${isActive ? ' active' : ''}`; + btn.innerHTML = content; + btn.disabled = disabled; + if (!disabled) { + btn.addEventListener('click', () => { + currentPage = page; + renderCards(); + const dir = document.getElementById('directory'); + if (dir) { + const top = dir.getBoundingClientRect().top + window.scrollY - 120; + window.scrollTo({ top, behavior: 'smooth' }); + } + }); + } + return btn; + }; + + container.appendChild(make('', currentPage - 1, currentPage === 1)); + for (let i = 1; i <= total; i++) { + container.appendChild(make(i, i, false, i === currentPage)); + } + container.appendChild(make('', currentPage + 1, currentPage === total)); +} + +function initFilterTabs() { + document.querySelectorAll('.filter-tab').forEach(btn => { + btn.addEventListener('click', () => { + document.querySelectorAll('.filter-tab').forEach(b => { + b.classList.remove('active'); + b.setAttribute('aria-selected', 'false'); + }); + btn.classList.add('active'); + btn.setAttribute('aria-selected', 'true'); + activeFilter = btn.dataset.filter; + currentPage = 1; + renderCards(); + }); + }); +} + +function initSearch() { + const input = document.getElementById('search-input'); + if (!input) return; + input.addEventListener('input', () => { + searchKeyword = sanitize(input.value).trim(); + currentPage = 1; + renderCards(); + }); +} + +async function initDirectory() { + const grid = document.getElementById('beasiswa-grid'); + try { + if (!window.BeasiswaAPI) throw new Error('Modul API.js (BeasiswaAPI) belum dimuat'); + const { data, source } = await window.BeasiswaAPI.getBeasiswa(); + allData = data; + window.beasiswaData = allData; + renderCards(); + } catch (err) { + const lang = activeLang; + const errTitle = lang === 'en' ? 'Failed to load scholarship data' : 'Gagal memuat data beasiswa'; + const errDesc = lang === 'en' ? 'Could not connect to the server. Please refresh or contact admin.' : 'Tidak dapat terhubung ke server. Silakan refresh atau hubungi admin.'; + + grid.innerHTML = ` +
    + +

    ${errTitle}

    +

    ${errDesc}

    +
    `; + } +} + +document.addEventListener('DOMContentLoaded', () => { + initFilterTabs(); + initSearch(); + initDirectory(); +}); \ No newline at end of file diff --git a/groups/ApeNameTeamE/assets/js/main.js b/groups/ApeNameTeamE/assets/js/main.js new file mode 100644 index 0000000..b5681da --- /dev/null +++ b/groups/ApeNameTeamE/assets/js/main.js @@ -0,0 +1,173 @@ +// ===================================================== +// main.js — Inisialisasi Utama +// Sticky Navbar, Hamburger Menu, Smooth Scroll, AOS, Active Nav +// ===================================================== + +document.addEventListener('DOMContentLoaded', () => { + + // ─── AOS (Animate on Scroll) ──────────────────────── + AOS.init({ + duration: 700, + easing: 'ease-out-cubic', + once: true, + offset: 50, + }); + + + // ─── Sticky Navbar ────────────────────────────────── + // Navbar sudah fixed di CSS; tidak perlu mengubah warna karena + // warna primary sudah menjadi identitas halaman ini. + // Namun kita tambahkan shadow saat scroll untuk depth effect. + + // PERBAIKAN: Menyesuaikan ID dengan yang ada di index.html (nav-wrapper) + const navbar = document.getElementById('nav-wrapper') || document.getElementById('navbar'); + + const handleScroll = () => { + if (!navbar) return; // PERBAIKAN: Mencegah error jika elemen tidak ada di halaman + + if (window.scrollY > 10) { + navbar.style.boxShadow = '0 4px 20px rgba(0,0,0,0.25)'; + } else { + navbar.style.boxShadow = 'none'; + } + }; + + window.addEventListener('scroll', handleScroll, { passive: true }); + handleScroll(); // initial call + + + // ─── Ticker Visibility (Muncul setelah Hero & Stats) ─ + const ticker = document.querySelector('.ticker-wrapper'); + const statsSection = document.getElementById('statistics'); + + const handleTickerVisibility = () => { + if (!ticker || !statsSection) return; + + // Ticker muncul setelah melewati batas bawah elemen statistik + const statsBottom = statsSection.offsetTop + statsSection.offsetHeight; + + if (window.scrollY > statsBottom) { + ticker.classList.add('show-ticker'); + } else { + ticker.classList.remove('show-ticker'); + } + }; + + window.addEventListener('scroll', handleTickerVisibility, { passive: true }); + // Call once on load with a slight delay to ensure layout is ready + setTimeout(handleTickerVisibility, 100); + + + // ─── Hamburger Menu (Mobile) ───────────────────────── + const mobileBtn = document.getElementById('mobile-menu-button'); + const mobileMenu = document.getElementById('mobile-menu'); + const closeMobileBtn = document.getElementById('close-mobile-menu'); + + const closeMobileMenu = () => { + mobileMenu?.classList.add('-translate-x-full'); + mobileBtn?.setAttribute('aria-expanded', 'false'); + }; + + // openMobileMenu handled by inline script in HTML; this covers close-on-scroll + closeMobileBtn?.addEventListener('click', closeMobileMenu); + + + // ─── Smooth Scroll untuk anchor links ────────────── + // Sesuai SDD: CSS scroll-behavior + JS fallback dengan offset + document.querySelectorAll('a[href^="#"]').forEach(anchor => { + anchor.addEventListener('click', e => { + const href = anchor.getAttribute('href'); + const target = document.querySelector(href); + if (!target) return; + + e.preventDefault(); + + // PERBAIKAN: Mencegah error saat membaca offsetHeight jika navbar null + const navHeight = navbar ? navbar.offsetHeight : 110; + const top = target.getBoundingClientRect().top + window.scrollY - navHeight - 4; + window.scrollTo({ top, behavior: 'smooth' }); + + // Tutup mobile menu jika sedang terbuka + closeMobileMenu(); + }); + }); + + + // ─── Active Nav Link via IntersectionObserver ────── + // Sesuai SDD fitur #4: highlight menu sesuai seksi yang terlihat + const sections = document.querySelectorAll('section[id]'); + const navLinks = document.querySelectorAll('a.nav-link'); + + const sectionObserver = new IntersectionObserver( + entries => { + entries.forEach(entry => { + if (entry.isIntersecting) { + navLinks.forEach(link => { + link.classList.remove('active'); + if (link.getAttribute('href') === `#${entry.target.id}`) { + link.classList.add('active'); + } + }); + } + }); + }, + { rootMargin: '-30% 0px -65% 0px' } + ); + + sections.forEach(s => sectionObserver.observe(s)); + + + // ─── Back to Top visibility ──────────────────────── + const backToTop = document.getElementById('back-to-top'); + if (backToTop) { + window.addEventListener('scroll', () => { + if (window.scrollY > 400) { + backToTop.classList.remove('opacity-0', 'pointer-events-none'); + backToTop.classList.add('opacity-100'); + } else { + backToTop.classList.add('opacity-0', 'pointer-events-none'); + backToTop.classList.remove('opacity-100'); + } + }, { passive: true }); + + backToTop.addEventListener('click', () => { + window.scrollTo({ top: 0, behavior: 'smooth' }); + }); + } + + // ─── Contact Us Modal (Hubungi Kami) ──────────────── + const btnOpenContact = document.getElementById('btn-open-contact'); + const contactOverlay = document.getElementById('contact-overlay'); + const contactModal = document.getElementById('contact-modal'); + const contactClose = document.getElementById('contact-close'); + const contactBackdrop = document.getElementById('contact-backdrop'); + + const openContactModal = () => { + if (!contactOverlay) return; + contactOverlay.classList.remove('hidden'); + contactOverlay.classList.add('flex'); + // Animasi muncul + setTimeout(() => { + contactOverlay.classList.remove('opacity-0'); + contactModal.classList.remove('scale-95'); + contactModal.classList.add('scale-100'); + }, 10); + }; + + const closeContactModal = () => { + if (!contactOverlay) return; + contactOverlay.classList.add('opacity-0'); + contactModal.classList.remove('scale-100'); + contactModal.classList.add('scale-95'); + // Tunggu animasi selesai baru di-hide + setTimeout(() => { + contactOverlay.classList.remove('flex'); + contactOverlay.classList.add('hidden'); + }, 300); + }; + + if (btnOpenContact) btnOpenContact.addEventListener('click', openContactModal); + if (contactClose) contactClose.addEventListener('click', closeContactModal); + if (contactBackdrop) contactBackdrop.addEventListener('click', closeContactModal); + +}); // end DOMContentLoaded \ No newline at end of file diff --git a/groups/ApeNameTeamE/assets/js/popup.js b/groups/ApeNameTeamE/assets/js/popup.js new file mode 100644 index 0000000..4af4072 --- /dev/null +++ b/groups/ApeNameTeamE/assets/js/popup.js @@ -0,0 +1,202 @@ +// ===================================================== +// popup.js — Modal Detail Beasiswa +// Tutup via: tombol ×, klik overlay, tekan Escape +// + DUKUNGAN MULTI-BAHASA (ID/EN) +// ===================================================== + +const overlay = document.getElementById('popup-overlay'); +const modal = document.getElementById('popup-modal'); +const closeBtn = document.getElementById('popup-close'); +const popupBody = document.getElementById('popup-content'); + +let currentOpenId = null; // Menyimpan status beasiswa mana yang sedang dibuka + +// Bertanya langsung ke mesin utama (localization.js) +function getPopupLang() { + if (typeof window.getLang === 'function') { + return window.getLang(); + } + return localStorage.getItem('if_untan_lang') || 'id'; +} + +// Format tanggal dinamis (ID / EN) +function fmtDate(str, lang) { + if (!str) return '—'; + const d = new Date(str); + if (isNaN(d.getTime())) return str; + return d.toLocaleDateString(lang === 'en' ? 'en-US' : 'id-ID', { + day: 'numeric', + month: 'long', + year: 'numeric' + }); +} + +// Kamus Teks Statis Modal +const popupDict = { + id: { + req_empty: 'Syarat belum tersedia. Kunjungi sumber resmi.', + req_title: 'Syarat Umum', + open_date: 'Tanggal Buka', + close_date: 'Tanggal Tutup', + contact: 'Kontak', + more_info: 'Info Lebih Lanjut', + no_link: 'Tautan tidak tersedia. Silakan hubungi admin prodi.', + status_open: 'Buka', + status_closed: 'Ditutup' + }, + en: { + req_empty: 'Requirements not available. Visit official source.', + req_title: 'General Requirements', + open_date: 'Open Date', + close_date: 'Closing Date', + contact: 'Contact', + more_info: 'More Information', + no_link: 'Link unavailable. Please contact department admin.', + status_open: 'Open', + status_closed: 'Closed' + } +}; + +/** + * Buka popup modal untuk beasiswa dengan ID tertentu + */ +function openPopup(id) { + currentOpenId = id; + const data = window.beasiswaData || []; + const bsw = data.find(b => b.id === id); + if (!bsw) return; + + const lang = getPopupLang(); + const d = popupDict[lang]; + + const isBuka = bsw.status === 'Buka' || bsw.status === 'Dibuka'; + const badgeClass = isBuka ? 'badge-buka' : 'badge-ditutup'; + + // Terjemahkan Status & Kategori + const statusText = isBuka ? d.status_open : (lang === 'en' && (bsw.status === 'Ditutup' || bsw.status === 'Tutup') ? d.status_closed : bsw.status); + + // Menerjemahkan Kategori (Misal: "Beasiswa Swasta" -> "Private Scholarship") + let kategoriText = bsw.kategori; + if (lang === 'en') { + if (bsw.kategori_en) { + kategoriText = bsw.kategori_en; + } else if (bsw.kategori.toLowerCase().includes('swasta')) { + kategoriText = 'Private Scholarship'; + } else if (bsw.kategori.toLowerCase().includes('pemerintah')) { + kategoriText = 'Government Scholarship'; + } + } + + // Teks Dinamis (Ambil _en dari JSON jika ada, jika tidak pakai ID) + const namaText = (lang === 'en' && bsw.nama_en) ? bsw.nama_en : bsw.nama; + const deskripsiText = (lang === 'en' && bsw.deskripsi_singkat_en) ? bsw.deskripsi_singkat_en : bsw.deskripsi_singkat; + const syaratData = (lang === 'en' && bsw.syarat_umum_en) ? bsw.syarat_umum_en : (bsw.syarat_umum || []); + + // Bangun list syarat umum + const syaratItems = syaratData + .map(s => ` +
  • + + ${s} +
  • `) + .join(''); + + const syaratHTML = syaratItems + ? `
      ${syaratItems}
    ` + : `

    ${d.req_empty}

    `; + + const hasUrl = bsw.url_sumber && bsw.url_sumber !== '#'; + + // Render konten popup dengan template Anda yang dipertahankan + popupBody.innerHTML = ` + + `; + + // Tampilkan overlay + overlay.classList.add('show'); + document.body.style.overflow = 'hidden'; + + // Set focus ke modal untuk aksesibilitas + setTimeout(() => modal.focus(), 50); +} + +/** Tutup popup modal */ +function closePopup() { + currentOpenId = null; + overlay.classList.remove('show'); + document.body.style.overflow = ''; +} + +// === Event Listeners untuk menutup popup === +closeBtn?.addEventListener('click', closePopup); +document.getElementById('popup-backdrop')?.addEventListener('click', closePopup); +document.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && overlay.classList.contains('show')) { + closePopup(); + } +}); + +// Jika bahasa diganti SAAT popup sedang terbuka, cetak ulang isinya! +document.addEventListener('languageChanged', () => { + if (currentOpenId && overlay.classList.contains('show')) { + openPopup(currentOpenId); + } +}); + +window.openPopup = openPopup; +window.closePopup = closePopup; \ No newline at end of file diff --git a/groups/ApeNameTeamE/data/beasiswa.json b/groups/ApeNameTeamE/data/beasiswa.json new file mode 100644 index 0000000..d15ff66 --- /dev/null +++ b/groups/ApeNameTeamE/data/beasiswa.json @@ -0,0 +1,285 @@ +[ + { + "id": "bsw-001", + "nama": "Boustany Foundation Scholarship, MBA - Harvard University", + "nama_en": "Boustany Foundation Scholarship, MBA - Harvard University", + "kategori": "Swasta", + "kategori_en": "Private", + "status": "Buka", + "thumbnail": "https://images.unsplash.com/photo-1541339907198-e08756dedf3f?w=400&q=80&auto=format&fit=crop", + "deskripsi_singkat": "Beasiswa bergengsi dari Boustany Foundation untuk program MBA di Harvard University, Amerika Serikat, bagi calon pemimpin bisnis dunia.", + "deskripsi_singkat_en": "A prestigious scholarship from the Boustany Foundation for the MBA program at Harvard University, United States, for future global business leaders.", + "syarat_umum": [ + "Diterima atau mendaftar di program MBA Harvard Business School", + "Memiliki rekam jejak prestasi akademik dan profesional yang luar biasa", + "Menunjukkan potensi kepemimpinan dan kontribusi internasional", + "Melengkapi aplikasi beasiswa secara online melalui situs resmi Boustany Foundation" + ], + "syarat_umum_en": [ + "Accepted or applying to the MBA program at Harvard Business School", + "Have an exceptional track record of academic and professional achievements", + "Demonstrate leadership potential and international contribution", + "Complete the scholarship application online through the official Boustany Foundation website" + ], + "kontak": "Boustany Foundation — boustany-foundation.com", + "tanggal_buka": "2026-05-21", + "tanggal_tutup": "2027-05-31", + "url_sumber": "https://www.schoters.com/id/beasiswa/boustany-foundation-scholarship-mba-harvard-university-1", + "tags": ["S2", "Swasta", "Amerika Serikat", "Partially Funded", "MBA"] + }, + { + "id": "bsw-002", + "nama": "La Trobe University - La Trobe High Achiever Scholarship (S1)", + "kategori": "Swasta", + "kategori_en": "Private", + "status": "Buka", + "thumbnail": "https://images.unsplash.com/photo-1523240795612-9a054b0db644?w=400&q=80&auto=format&fit=crop", + "deskripsi_singkat": "Beasiswa dari La Trobe University Australia untuk mahasiswa internasional jenjang S1 berprestasi tinggi yang ingin melanjutkan studi di Melbourne.", + "deskripsi_singkat_en": "Scholarship from La Trobe University Australia for high-achieving international undergraduate students who wish to pursue their studies in Melbourne.", + "syarat_umum": [ + "Mendaftar sebagai mahasiswa internasional jenjang S1 (Undergraduate) di La Trobe University", + "Memiliki nilai akademik yang sangat baik (High Achiever)", + "Tidak sedang menerima beasiswa penuh lainnya dari La Trobe University", + "Memenuhi persyaratan masuk dan kemampuan bahasa Inggris La Trobe University" + ], + "syarat_umum_en": [ + "Apply as an international undergraduate student at La Trobe University", + "Have an excellent academic record (High Achiever)", + "Not currently receiving any other full scholarship from La Trobe University", + "Meet La Trobe University's entry and English language requirements" + ], + "kontak": "La Trobe University Admissions — latrobe.edu.au", + "tanggal_buka": "2023-09-29", + "tanggal_tutup": "2026-12-31", + "url_sumber": "https://www.schoters.com/id/beasiswa/la-trobe-university-la-trobe-high-achiever-scholarship-2", + "tags": ["S1", "Swasta", "Australia", "Partially Funded"] + }, + { + "id": "bsw-003", + "nama": "Charles Sturt University - International Student Support Scholarship", + "kategori": "Swasta", + "kategori_en": "Private", + "status": "Buka", + "thumbnail": "https://images.unsplash.com/photo-1560472354-b33ff0c44a43?w=400&q=80&auto=format&fit=crop", + "deskripsi_singkat": "Beasiswa dukungan dari Charles Sturt University bagi mahasiswa internasional jenjang S1 yang mendaftar di kampus yang berlokasi di New South Wales, Australia.", + "deskripsi_singkat_en": "Support scholarship from Charles Sturt University for international undergraduate students enrolling at a campus located in New South Wales, Australia.", + "syarat_umum": [ + "Mendaftar sebagai mahasiswa internasional penuh waktu (full-time) jenjang S1", + "Memenuhi persyaratan penerimaan akademik Charles Sturt University", + "Memiliki kemampuan Bahasa Inggris yang memadai (IELTS/TOEFL)", + "Mendaftar melalui jalur penerimaan internasional resmi" + ], + "syarat_umum_en": [ + "Apply as a full-time international undergraduate student", + "Meet Charles Sturt University's academic admission requirements", + "Have adequate English language proficiency (IELTS/TOEFL)", + "Apply through the official international admission pathway" + ], + "kontak": "Charles Sturt University International — csu.edu.au", + "tanggal_buka": "2026-05-21", + "tanggal_tutup": "2026-12-31", + "url_sumber": "https://www.schoters.com/id/beasiswa/charles-sturt-university-international-student-support-scholarship-3", + "tags": ["S1", "Swasta", "Australia", "Partially Funded"] + }, + { + "id": "bsw-004", + "nama": "La Trobe University - La Trobe High Achiever Scholarship (S2)", + "kategori": "Swasta", + "kategori_en": "Private", + "status": "Buka", + "thumbnail": "https://images.unsplash.com/photo-1529156069898-49953e39b3ac?w=400&q=80&auto=format&fit=crop", + "deskripsi_singkat": "Beasiswa dari La Trobe University Australia untuk mahasiswa internasional jenjang S2 (Postgraduate) dengan pencapaian akademik tinggi.", + "deskripsi_singkat_en": "Scholarship from La Trobe University Australia for international postgraduate students with high academic achievements.", + "syarat_umum": [ + "Mendaftar sebagai mahasiswa internasional jenjang S2 di La Trobe University", + "Memiliki nilai akademik yang sangat baik di jenjang sarjana sebelumnya", + "Memenuhi persyaratan bahasa Inggris yang ditetapkan", + "Tidak sedang menerima beasiswa lain dari universitas yang sama" + ], + "syarat_umum_en": [ + "Apply as an international postgraduate student at La Trobe University", + "Have excellent academic grades in the previous undergraduate degree", + "Meet the specified English language requirements", + "Not currently receiving another scholarship from the same university" + ], + "kontak": "La Trobe University Admissions — latrobe.edu.au", + "tanggal_buka": "2023-09-29", + "tanggal_tutup": "2026-12-31", + "url_sumber": "https://www.schoters.com/id/beasiswa/la-trobe-university-la-trobe-high-achiever-scholarship-4", + "tags": ["S2", "Swasta", "Australia", "Partially Funded"] + }, + { + "id": "bsw-005", + "nama": "ETH Zurich - Excellence Scholarship & Opportunity Programme (ESOP)", + "kategori": "Pemerintah", + "kategori_en": "Government", + "status": "Buka", + "thumbnail": "https://images.unsplash.com/photo-1497366754035-f200968a6e72?w=400&q=80&auto=format&fit=crop", + "deskripsi_singkat": "Beasiswa bergengsi dari ETH Zurich (salah satu universitas teknik terbaik dunia) untuk mahasiswa S2 internasional yang unggul secara akademik.", + "deskripsi_singkat_en": "Prestigious scholarship from ETH Zurich (one of the world's best engineering universities) for academically outstanding international master's students.", + "syarat_umum": [ + "Mendaftar program Master (S2) di ETH Zurich", + "Lulus ujian penerimaan ETH Zurich atau memiliki setara gelar sarjana yang diakui", + "Menunjukkan prestasi akademik luar biasa (biasanya masuk 10% terbaik angkatan)", + "Melengkapi formulir aplikasi beasiswa ESOP yang terpisah dari aplikasi studi", + "Mendapatkan surat rekomendasi dari akademisi berwenang" + ], + "syarat_umum_en": [ + "Apply for a Master's program at ETH Zurich", + "Pass the ETH Zurich entrance exam or hold a recognized equivalent bachelor's degree", + "Demonstrate outstanding academic performance (usually top 10% of the cohort)", + "Complete the ESOP scholarship application form separately from the study application", + "Obtain a recommendation letter from authorized academics" + ], + "kontak": "ETH Zurich Scholarship Office — ethz.ch", + "tanggal_buka": "2026-11-01", + "tanggal_tutup": "2026-11-30", + "url_sumber": "https://www.schoters.com/id/beasiswa/eth-zurich-excellence-scholarship-opportunity-programme-esop-5", + "tags": ["S2", "Pemerintah", "Swiss", "Fully Funded", "Prestasi"] + }, + { + "id": "bsw-006", + "nama": "STUST - Scholarship for International Students (S3, Fully Funded)", + "kategori": "Swasta", + "kategori_en": "Private", + "status": "Buka", + "thumbnail": "https://images.unsplash.com/photo-1580582932707-520aed937b7b?w=400&q=80&auto=format&fit=crop", + "deskripsi_singkat": "Beasiswa penuh (fully funded) dari Southern Taiwan University of Science and Technology untuk mahasiswa internasional jenjang S3 (Doktoral).", + "deskripsi_singkat_en": "Fully funded scholarship from Southern Taiwan University of Science and Technology for international doctoral students.", + "syarat_umum": [ + "Mendaftar program Doktoral (S3) di STUST, Taiwan", + "Memiliki gelar magister (S2) yang diakui", + "Memenuhi persyaratan kemampuan bahasa Inggris atau Mandarin", + "Menyerahkan proposal penelitian yang relevan dengan bidang studi yang dipilih", + "Mendapatkan persetujuan dari calon pembimbing (supervisor) di STUST" + ], + "syarat_umum_en": [ + "Apply for a Doctoral program at STUST, Taiwan", + "Hold a recognized Master's degree", + "Meet the English or Mandarin language proficiency requirements", + "Submit a research proposal relevant to the chosen field of study", + "Obtain approval from a prospective supervisor at STUST" + ], + "kontak": "STUST International Affairs Office — stust.edu.tw", + "tanggal_buka": "2026-09-15", + "tanggal_tutup": "2026-11-30", + "url_sumber": "https://www.schoters.com/id/beasiswa/southern-taiwan-university-of-science-and-technology-the-stust-scholarship-for-international-students-6", + "tags": ["S3", "Swasta", "Taiwan", "Fully Funded"] + }, + { + "id": "bsw-007", + "nama": "STUST - Scholarship for International Students (S2, Partially Funded)", + "kategori": "Swasta", + "kategori_en": "Private", + "status": "Buka", + "thumbnail": "https://images.unsplash.com/photo-1606761568499-6d2451b23c66?w=400&q=80&auto=format&fit=crop", + "deskripsi_singkat": "Beasiswa parsial (partially funded) dari Southern Taiwan University of Science and Technology untuk mahasiswa internasional jenjang S2 (Magister).", + "deskripsi_singkat_en": "Partially funded scholarship from Southern Taiwan University of Science and Technology for international master's students.", + "syarat_umum": [ + "Mendaftar program Magister (S2) di STUST, Taiwan", + "Memiliki gelar sarjana (S1) yang diakui", + "Memenuhi persyaratan kemampuan bahasa Inggris atau Mandarin", + "Melengkapi berkas aplikasi secara online melalui portal resmi STUST", + "Menyertakan transkrip nilai dan surat motivasi" + ], + "syarat_umum_en": [ + "Apply for a Master's program at STUST, Taiwan", + "Hold a recognized Bachelor's degree", + "Meet the English or Mandarin language proficiency requirements", + "Complete the application file online through the official STUST portal", + "Include academic transcripts and a motivation letter" + ], + "kontak": "STUST International Affairs Office — stust.edu.tw", + "tanggal_buka": "2026-09-15", + "tanggal_tutup": "2026-11-30", + "url_sumber": "https://www.schoters.com/id/beasiswa/southern-taiwan-university-of-science-and-technology-the-stust-scholarship-for-international-students-7", + "tags": ["S2", "Swasta", "Taiwan", "Partially Funded"] + }, + { + "id": "bsw-008", + "nama": "University of Melbourne - Melbourne International Undergraduate Scholarship", + "kategori": "Swasta", + "kategori_en": "Private", + "status": "Buka", + "thumbnail": "https://images.unsplash.com/photo-1504711434969-e33886168f5c?w=400&q=80&auto=format&fit=crop", + "deskripsi_singkat": "Beasiswa untuk mahasiswa internasional berprestasi yang mendaftar program S1 di University of Melbourne, salah satu universitas terbaik di Australia.", + "deskripsi_singkat_en": "Scholarship for outstanding international students applying for an undergraduate program at the University of Melbourne, one of Australia's best universities.", + "syarat_umum": [ + "Merupakan mahasiswa internasional (bukan warga negara Australia atau New Zealand)", + "Mendaftar program sarjana (S1 / Undergraduate) di University of Melbourne", + "Memiliki hasil akademik yang sangat luar biasa di tingkat sekolah menengah atas", + "Memenuhi persyaratan kemampuan bahasa Inggris (IELTS minimal 6.5)", + "Beasiswa diberikan otomatis berdasarkan nilai akademik, tidak perlu aplikasi terpisah" + ], + "syarat_umum_en": [ + "Be an international student (not a citizen of Australia or New Zealand)", + "Apply for an undergraduate program at the University of Melbourne", + "Have an exceptional academic result at the high school level", + "Meet the English language proficiency requirements (minimum IELTS 6.5)", + "The scholarship is awarded automatically based on academic merit, no separate application required" + ], + "kontak": "University of Melbourne Future Students — unimelb.edu.au", + "tanggal_buka": "2026-05-21", + "tanggal_tutup": "2026-10-31", + "url_sumber": "https://www.schoters.com/id/beasiswa/university-of-melbourne-melbourne-international-undergraduate-scholarship-8", + "tags": ["S1", "Swasta", "Australia", "Partially Funded", "Prestasi"] + }, + { + "id": "bsw-009", + "nama": "Charles Darwin University - CDU Global Merit Scholarship", + "kategori": "Swasta", + "kategori_en": "Private", + "status": "Buka", + "thumbnail": "https://images.unsplash.com/photo-1434030216411-0b793f4b4173?w=400&q=80&auto=format&fit=crop", + "deskripsi_singkat": "Beasiswa merit global dari Charles Darwin University (CDU) di Darwin, Australia, untuk mahasiswa internasional S1 yang memiliki rekam jejak akademik yang kuat.", + "deskripsi_singkat_en": "Global merit scholarship from Charles Darwin University (CDU) in Darwin, Australia, for international undergraduate students with a strong academic track record.", + "syarat_umum": [ + "Mendaftar sebagai mahasiswa internasional baru (new international student) di CDU", + "Mendaftar untuk program sarjana (Undergraduate/S1) penuh waktu", + "Memiliki nilai ujian akhir sekolah yang baik (dikonversi ke skala CDU)", + "Memenuhi persyaratan bahasa Inggris (IELTS 6.0 atau setara)", + "Tidak sedang menerima potongan biaya kuliah lainnya dari CDU" + ], + "syarat_umum_en": [ + "Apply as a new international student at CDU", + "Apply for a full-time undergraduate program", + "Have a good high school final exam score (converted to CDU scale)", + "Meet the English language requirements (IELTS 6.0 or equivalent)", + "Not currently receiving any other tuition fee discount from CDU" + ], + "kontak": "CDU International — cdu.edu.au", + "tanggal_buka": "2026-05-21", + "tanggal_tutup": "2026-10-09", + "url_sumber": "https://www.schoters.com/id/beasiswa/charles-darwin-university-cdu-global-merit-scholarship-9", + "tags": ["S1", "Swasta", "Australia", "Partially Funded"] + }, + { + "id": "bsw-010", + "nama": "Deakin University - Vice-Chancellor's International Scholarship", + "kategori": "Swasta", + "kategori_en": "Private", + "status": "Buka", + "thumbnail": "https://images.unsplash.com/photo-1541339907198-e08756dedf3f?w=400&q=80&auto=format&fit=crop", + "deskripsi_singkat": "Beasiswa bergengsi dari Wakil Rektor (Vice-Chancellor) Deakin University untuk mahasiswa internasional terbaik yang akan menempuh studi S1 atau S2 di Australia.", + "deskripsi_singkat_en": "Prestigious Vice-Chancellor's scholarship from Deakin University for the best international students pursuing an undergraduate or postgraduate degree in Australia.", + "syarat_umum": [ + "Merupakan mahasiswa internasional (bukan warga negara Australia/New Zealand)", + "Mendaftar untuk program S1 (Undergraduate) atau S2 (Postgraduate Coursework) di Deakin", + "Memiliki rekam jejak akademik yang sangat baik dan konsisten", + "Memenuhi persyaratan masuk dan kemampuan bahasa Inggris Deakin University", + "Mengajukan aplikasi beasiswa sebelum tanggal penutupan yang ditetapkan" + ], + "syarat_umum_en": [ + "Be an international student (not an Australia/New Zealand citizen)", + "Apply for an undergraduate or postgraduate coursework program at Deakin", + "Have a consistent and excellent academic track record", + "Meet Deakin University's entry and English language requirements", + "Submit the scholarship application before the specified closing date" + ], + "kontak": "Deakin University International — deakin.edu.au", + "tanggal_buka": "2026-05-21", + "tanggal_tutup": "2026-09-25", + "url_sumber": "https://www.schoters.com/id/beasiswa/deakin-university-vice-chancellor-s-international-scholarship-10", + "tags": ["S1 & S2", "S2", "Swasta", "Australia", "Partially Funded", "Prestasi"] + } +] \ No newline at end of file diff --git a/groups/ApeNameTeamE/data/testimoni.json b/groups/ApeNameTeamE/data/testimoni.json new file mode 100644 index 0000000..1f71990 --- /dev/null +++ b/groups/ApeNameTeamE/data/testimoni.json @@ -0,0 +1,11 @@ +[ + { + "id": "tsm-001", + "nama": "Violaine Gunawan", + "foto": "https://ui-avatars.com/api/?name=Violaine+Gunawan&background=111a2f&color=fff&size=200&rounded=true", + "nama_beasiswa": "BEASISWA BAKTI BCA 2026", + "nama_beasiswa_en": "BAKTI BCA SCHOLARSHIP 2026", + "kutipan": "Beasiswa ini sangat berdampak besar. Tantangannya menyeimbangkan kuliah, asisten praktikum, organisasi, dan projek besar beasiswa. Namun, relasi di kampus dan masyarakat jadi bertambah luas. Tips: Jangan takut mencoba, pahami fokus beasiswa, sesuaikan visi misi, dan aktiflah berorganisasi!", + "kutipan_en": "This scholarship has a massive impact. The challenge is balancing college, practicum assistant duties, organizations, and major scholarship projects. However, connections on campus and in society have expanded. Tip: Don't be afraid to try, understand the scholarship's focus, align your vision and mission, and be active in organizations!" + } +] \ No newline at end of file diff --git a/groups/ApeNameTeamE/index.html b/groups/ApeNameTeamE/index.html index d7d58d3..b29acfb 100644 --- a/groups/ApeNameTeamE/index.html +++ b/groups/ApeNameTeamE/index.html @@ -1,15 +1,27 @@ - + + - Group 10 – Ape Name Tim E | Informatika UNTAN + Group 10 – ApeNameTeamE | Informatika UNTAN + - + - + @@ -1139,18 +1152,262 @@ -
    -
    -
    - Tugas Halaman Statis -

    Beasiswa

    -

    Kelompok 10 — Ape Name Tim E  ·  Informatika UNTAN 2026

    +
    +
    + +
    +
    + +
    +
    + Selamat datang di antarmuka situs web Informatika terbaru! +  |  Pendaftaran + kolaborasi riset semester genap telah dibuka.  |  Pantau publikasi dosen dan + mahasiswa secara + real-time.  |  Beasiswa KIP Kuliah 2026 dibuka — Daftar sekarang! +  |  Beasiswa LPDP S2 + tersedia hingga September 2026.       + +
    +
    +
    + + +
    +
    + +
    + + + +
    + + +
    + +
    + PROGRAM BEASISWA +
    + + +

    + Beasiswa Studi &
    Penelitian +

    + + +

    + Teknik Informatika Universitas Tanjungpura +

    + + +

    + Temukan berbagai peluang beasiswa untuk mendukung perjalanan akademik dan penelitian Anda. +

    + + + + Lihat Daftar Beasiswa + + +
    + + + + +
    +
    +
    + + +
    +
    +
    + +
    +
    0
    +
    + Total Mahasiswa Penerima
    Beasiswa Semester Ini +
    +
    + +
    +
    0
    +
    + Total Penerima Beasiswa
    Khusus S1 +
    +
    + +
    +
    0
    +
    + Total Penerima Beasiswa
    Studi Lanjut +
    +
    + +
    +
    +
    + + +
    +
    + + +
    +
    + +
    + DAFTAR LENGKAP +
    +

    Daftar Beasiswa

    +

    + Temukan beragam beasiswa dari berbagai sumber resmi yang tersedia untuk mahasiswa aktif. +

    +
    +
    + + +
    + +
    + + + +
    + + +
    +
    + + +
    +
    +
    + + +
    + +
    + +

    Memuat data beasiswa...

    +
    +
    + + + + +
    +
    + + + + + +
    +
    + + +
    +

    Apa Kata Mereka?

    +

    + Cerita nyata dari mahasiswa yang telah meraih beasiswa +

    +
    + + +
    +
    + +
    +
    +
    + +
    +
    + + +
    +
    +
    +

    + Ingin Mengetahui Info Lebih Lanjut? +

    +

    + Bersama kami membangun sumber daya manusia unggul melalui program beasiswa dan + kolaborasi riset +

    + +
    +
    +
    +
    -

    Tulis konten halaman kelompok di sini.

    -

    Subjudul

    -

    Gunakan paragraf, list, tabel, gambar, dan elemen konten lainnya.

    + +
    @@ -1215,11 +1472,23 @@
    - Footer Illustration + Footer Illustration
    + + + + + + + + + + + + - + - + \ No newline at end of file diff --git a/groups/ApeNameTeamE/page1.json b/groups/ApeNameTeamE/page1.json new file mode 100644 index 0000000..3970938 --- /dev/null +++ b/groups/ApeNameTeamE/page1.json @@ -0,0 +1,203 @@ +{ + "source": "https://www.schoters.com/id/beasiswa", + "title": "Kalender Beasiswa - Schoters", + "description": "Kalender dan informasi beasiswa terlengkap dari semua jenjang S1, S2, & S3 di setiap negara dan kampus impian beserta persyaratan dan proses pendaftarannya", + "filters": { + "jenjang": [ + { "level": "D2", "count": 2 }, + { "level": "D3", "count": 6 }, + { "level": "Non-Degree", "count": 20 }, + { "level": "S1", "count": 648 }, + { "level": "S2", "count": 683 }, + { "level": "S3", "count": 362 } + ], + "tipe_pendanaan": [ + { "type": "Fully Funded", "count": 809 }, + { "type": "Partially Funded", "count": 590 } + ], + "tipe_beasiswa": [ + { "type": "Company/Institution Scholarship", "count": 237 }, + { "type": "Government Scholarship", "count": 459 }, + { "type": "University Scholarship", "count": 830 } + ], + "negara": [ + { "country": "Australia", "count": 161 }, + { "country": "Austria", "count": 19 }, + { "country": "Azerbaijan", "count": 6 }, + { "country": "Belgium", "count": 38 }, + { "country": "Brazil", "count": 2 }, + { "country": "Brunei", "count": 5 }, + { "country": "Canada", "count": 46 }, + { "country": "China", "count": 114 }, + { "country": "Czech Republic", "count": 2 }, + { "country": "Denmark", "count": 13 }, + { "country": "Egypt", "count": 3 }, + { "country": "Estonia", "count": 2 }, + { "country": "Finland", "count": 11 }, + { "country": "France", "count": 20 }, + { "country": "Germany", "count": 46 }, + { "country": "Hong Kong", "count": 24 }, + { "country": "Hungary", "count": 5 }, + { "country": "India", "count": 2 }, + { "country": "Indonesia", "count": 35 }, + { "country": "Ireland", "count": 8 }, + { "country": "Italy", "count": 19 }, + { "country": "Japan", "count": 254 }, + { "country": "Kazakhstan", "count": 3 }, + { "country": "Latvia", "count": 3 }, + { "country": "Libya", "count": 2 }, + { "country": "Luxembourg", "count": 1 }, + { "country": "Macau", "count": 6 }, + { "country": "Malaysia", "count": 12 }, + { "country": "Morocco", "count": 2 }, + { "country": "Netherlands", "count": 21 }, + { "country": "New Zealand", "count": 22 }, + { "country": "Pakistan", "count": 1 }, + { "country": "Philippines", "count": 2 }, + { "country": "Poland", "count": 9 }, + { "country": "Portugal", "count": 4 }, + { "country": "Qatar", "count": 6 }, + { "country": "Romania", "count": 9 }, + { "country": "Russia", "count": 6 }, + { "country": "Saudi Arabia", "count": 12 }, + { "country": "Scotland", "count": 2 }, + { "country": "Serbia", "count": 3 }, + { "country": "Singapore", "count": 49 }, + { "country": "Slovakia", "count": 1 }, + { "country": "South Korea", "count": 109 }, + { "country": "Spain", "count": 12 }, + { "country": "Sweden", "count": 11 }, + { "country": "Switzerland", "count": 19 }, + { "country": "Taiwan", "count": 112 }, + { "country": "Thailand", "count": 38 }, + { "country": "Turkey", "count": 11 }, + { "country": "United Arab Emirates", "count": 67 }, + { "country": "United Kingdom", "count": 76 }, + { "country": "United States", "count": 55 }, + { "country": "Universal", "count": 2 }, + { "country": "Vietnam", "count": 2 } + ] + }, + "scholarships_page_1": [ + { + "name": "Boustany Foundation Scholarship, MBA - Harvard University", + "funding_type": "Partially Funded", + "level": ["S2"], + "country": "United States", + "open_date": "2026-05-21", + "deadline": "2027-05-31", + "status": "Pendaftaran dibuka", + "url": "https://www.schoters.com/id/beasiswa/boustany-foundation-scholarship-mba-harvard-university-1" + }, + { + "name": "La Trobe University - La Trobe High Achiever Scholarship", + "funding_type": "Partially Funded", + "level": ["S1"], + "country": "Australia", + "open_date": "2023-09-29", + "deadline": "2026-12-31", + "status": "Pendaftaran dibuka", + "url": "https://www.schoters.com/id/beasiswa/la-trobe-university-la-trobe-high-achiever-scholarship-2" + }, + { + "name": "Charles Sturt University - International Student Support Scholarship", + "funding_type": "Partially Funded", + "level": ["S1"], + "country": "Australia", + "open_date": "2026-05-21", + "deadline": "2026-12-31", + "status": "Pendaftaran dibuka", + "url": "https://www.schoters.com/id/beasiswa/charles-sturt-university-international-student-support-scholarship-3" + }, + { + "name": "La Trobe University - La Trobe High Achiever Scholarship", + "funding_type": "Partially Funded", + "level": ["S2"], + "country": "Australia", + "open_date": "2023-09-29", + "deadline": "2026-12-31", + "status": "Pendaftaran dibuka", + "url": "https://www.schoters.com/id/beasiswa/la-trobe-university-la-trobe-high-achiever-scholarship-4" + }, + { + "name": "ETH Zurich - Excellence Scholarship & Opportunity Programme (ESOP)", + "funding_type": "Fully Funded", + "level": ["S2"], + "country": "Switzerland", + "open_date": "2026-11-01", + "deadline": "2026-11-30", + "status": "Pendaftaran dibuka", + "url": "https://www.schoters.com/id/beasiswa/eth-zurich-excellence-scholarship-opportunity-programme-esop-5" + }, + { + "name": "Southern Taiwan University of Science and Technology - The STUST Scholarship for International Students", + "funding_type": "Fully Funded", + "level": ["S3"], + "country": "Taiwan", + "open_date": "2026-09-15", + "deadline": "2026-11-30", + "status": "Pendaftaran dibuka", + "url": "https://www.schoters.com/id/beasiswa/southern-taiwan-university-of-science-and-technology-the-stust-scholarship-for-international-students-6" + }, + { + "name": "Southern Taiwan University of Science and Technology - The STUST Scholarship for International Students", + "funding_type": "Partially Funded", + "level": ["S2"], + "country": "Taiwan", + "open_date": "2026-09-15", + "deadline": "2026-11-30", + "status": "Pendaftaran dibuka", + "url": "https://www.schoters.com/id/beasiswa/southern-taiwan-university-of-science-and-technology-the-stust-scholarship-for-international-students-7" + }, + { + "name": "University of Melbourne - Melbourne International Undergraduate Scholarship", + "funding_type": "Partially Funded", + "level": ["S1"], + "country": "Australia", + "open_date": "2026-05-21", + "deadline": "2026-10-31", + "status": "Pendaftaran dibuka", + "url": "https://www.schoters.com/id/beasiswa/university-of-melbourne-melbourne-international-undergraduate-scholarship-8" + }, + { + "name": "Charles Darwin University - CDU Global Merit Scholarship", + "funding_type": "Partially Funded", + "level": ["S1"], + "country": "Australia", + "open_date": "2026-05-21", + "deadline": "2026-10-09", + "status": "Pendaftaran dibuka", + "url": "https://www.schoters.com/id/beasiswa/charles-darwin-university-cdu-global-merit-scholarship-9" + }, + { + "name": "Deakin University - Vice-Chancellor's International Scholarship", + "funding_type": "Partially Funded", + "level": ["S1", "S2"], + "country": "Australia", + "open_date": "2026-05-21", + "deadline": "2026-09-25", + "status": "Pendaftaran dibuka", + "url": "https://www.schoters.com/id/beasiswa/deakin-university-vice-chancellor-s-international-scholarship-10" + }, + { + + } + ], + "pagination": { + "current_page": 1, + "total_pages": 153 + }, + "contact": { + "email": "ask@schoters.com", + "phone": "0811 9424 966", + "address": "Gedung Sinarmas MSIG Tower Lt. 33, Jl. Jenderal Sudirman Kav. 21, Karet Kuningan, Setiabudi, Jakarta Selatan, Jakarta 12920" + }, + "social_media": { + "instagram": "https://www.instagram.com/schoters/", + "facebook": "https://www.facebook.com/schoters", + "youtube": "https://www.youtube.com/@Schoters", + "tiktok": "https://www.tiktok.com/@schoters.id", + "twitter_x": "https://x.com/schoters_com", + "linkedin": "https://id.linkedin.com/company/schoters" + } +} \ No newline at end of file diff --git a/groups/C4A/data/data-faq.json b/groups/C4A/data/data-faq.json index 61f70e1..8ffae0a 100644 --- a/groups/C4A/data/data-faq.json +++ b/groups/C4A/data/data-faq.json @@ -1,52 +1,112 @@ [ { "id": 1, - "question": "Apa saja syarat administratif agar saya bisa mengikuti perkuliahan?", - "answer": "Syarat untuk mengikuti perkuliahan adalah mahasiswa telah melakukan pembayaran UKT, mengisi KRS, dan terdaftar di kelas Edlink." + "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 pembayaran UKT, mengisi KRS, dan terdaftar di kelas Edlink.", + "en": "The requirements to attend lectures are that the student has completed UKT payment, filled out the KRS, and is registered in Edlink classes." + } }, { "id": 2, - "question": "Berapa persentase kehadiran minimal agar bisa mengikuti Ujian Akhir Semester (UAS)?", - "answer": "Mahasiswa wajib memenuhi minimal 75% kehadiran dari total pertemuan perkuliahan. Mahasiswa yang tidak memenuhi syarat kehadiran ini tidak diperkenankan mengikuti UAS." + "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 75% kehadiran dari total pertemuan perkuliahan. Mahasiswa yang tidak memenuhi syarat kehadiran ini tidak diperkenankan mengikuti UAS.", + "en": "Students must meet a minimum of 75% attendance out of the total lecture sessions. Students who do not meet this attendance requirement are not permitted to take the UAS." + } }, { "id": 3, - "question": "Apakah pengisian evaluasi perkuliahan itu wajib?", - "answer": "Ya, karena pengisian evaluasi perkuliahan dapat menjadi salah satu syarat untuk mengakses hasil studi sesuai dengan kebijakan yang berlaku. Hasil ini sangat penting untuk perbaikan dan pengembangan kualitas pembelajaran prodi ke depannya." + "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 mengakses hasil studi 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 access study results 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": "Berapa minimal SKS dan IPK untuk bisa mendaftar Kerja Praktik (KP)?", - "answer": "Untuk mendaftar KP, mahasiswa harus sudah lulus minimal 100 SKS dan memiliki Indeks Prestasi Kumulatif (IPK) ≥ 2,00." + "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 100 SKS dan memiliki Indeks Prestasi Kumulatif (IPK) ≥ 2,00.", + "en": "To register for KP, students must have passed at least 100 credits (SKS) and have a Cumulative Grade Point Average (GPA) of ≥ 2.00." + } }, { "id": 5, - "question": "Apakah ada syarat kelulusan mata kuliah tertentu sebelum saya bisa mengajukan Kerja Praktik (KP)?", - "answer": "Ya, selain memenuhi syarat minimal SKS dan IPK, mahasiswa juga diwajibkan telah lulus mata kuliah Penulisan Proposal Tugas Akhir dengan nilai minimal C. Selain itu, mahasiswa juga harus menyerahkan berkas pendukung pengajuan KP seperti KTM, riwayat studi, transkrip nilai, dan surat permohonan." + "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 Penulisan Proposal Tugas Akhir dengan nilai minimal C. 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 Final Project Proposal Writing course with a minimum grade of C. 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": "Apa kualifikasi yang dibutuhkan untuk Pembimbing Lapangan di instansi tempat KP?", - "answer": "Pembimbing Lapangan dari tempat pelaksanaan KP diwajibkan memiliki kualifikasi pendidikan minimal S1 atau D4." + "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 S1 atau D4.", + "en": "The Field Supervisor from the KP institution is required to have a minimum educational qualification of Bachelor's (S1) or Applied Bachelor's (D4)." + } }, { "id": 7, - "question": "Bolehkah saya mengambil mata kuliah Kerja Praktik (KP) bersamaan dengan Tugas Akhir?", - "answer": "Boleh. Mata Kuliah Kerja Praktik (KP) dapat diambil secara bersamaan dengan Tugas Akhir 1 (TA1) dan Tugas Akhir 2 (TA2) dalam satu semester yang sama." + "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 Tugas Akhir 1 (TA1) dan Tugas Akhir 2 (TA2) dalam satu semester yang sama.", + "en": "Yes. The Internship (KP) course can be taken concurrently with Final Project 1 (TA1) and Final Project 2 (TA2) within the same semester." + } }, { "id": 8, - "question": "Apa saja syarat akademik utama sebelum mengajukan judul Tugas Akhir (Skripsi)?", - "answer": "Syarat utamanya adalah mahasiswa harus telah lulus minimal 120 SKS, memiliki IPK minimal 2,00, telah lulus mata kuliah Penulisan Proposal Tugas Akhir dengan nilai minimal C, serta telah mendaftarkan mata kuliah skripsi pada KRS." + "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 120 SKS, memiliki IPK minimal 2,00, telah lulus mata kuliah Penulisan Proposal Tugas Akhir dengan nilai minimal C, serta telah mendaftarkan mata kuliah skripsi pada KRS.", + "en": "The main requirements are that the student must have passed at least 120 credits (SKS), have a minimum GPA of 2.00, have passed the Final Project Proposal Writing course with a minimum grade of C, and have registered the thesis course in the KRS." + } }, { "id": 9, - "question": "Siapa yang berhak mendaftar menjadi Asisten Praktikum (Aslab)?", - "answer": "Rekrutmen terbuka bagi mahasiswa aktif Program Studi Informatika UNTAN yang telah lulus mata kuliah terkait (dengan nilai minimal sesuai ketentuan Dosen Pengampu) dan berkomitmen mengikuti seluruh jadwal praktikum selama semester berjalan." + "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 mahasiswa aktif Program Studi Informatika UNTAN 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 active students of the Informatics Study Program at UNTAN 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": "Bagaimana prosedur meminjam Laboratorium Komputer untuk keperluan kegiatan kampus?", - "answer": "Peminjam (dosen atau mahasiswa) harus mengajukan surat permohonan peminjaman kepada staf laboratorium pengelola dan menyampaikan jadwal penggunaannya. Penggunaan lab juga harus didampingi oleh dosen atau asisten praktikum yang bertanggung jawab." + "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 surat permohonan peminjaman kepada staf laboratorium pengelola dan menyampaikan jadwal penggunaannya. Penggunaan lab juga harus didampingi oleh dosen atau asisten praktikum yang bertanggung jawab.", + "en": "The borrower (lecturer or student) must submit a borrowing request letter to the laboratory management staff and provide the usage schedule. Lab usage must also be supervised by a responsible lecturer or practicum assistant." + } } ] \ No newline at end of file diff --git a/groups/C4A/data/data-sop.json b/groups/C4A/data/data-sop.json index 5c912b4..2fdecbc 100644 --- a/groups/C4A/data/data-sop.json +++ b/groups/C4A/data/data-sop.json @@ -4,194 +4,290 @@ "tab_number": 1, "category": "akademik", "icon": "fa-chalkboard-teacher", - "title": "SOP Pelaksanaan Kuliah", - "description": "Mengatur tata cara dan tahapan pelaksanaan perkuliahan mulai dari persiapan hingga evaluasi pembelajaran.", - "purpose": "SOP ini bertujuan untuk menjelaskan tata cara pelaksanaan perkuliahan bagi mahasiswa Program Studi Informatika Universitas Tanjungpura.", + "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": [ - "Mahasiswa telah melakukan pembayaran UKT", - "Mahasiswa telah mengisi KRS", - "Mahasiswa terdaftar di kelas Edlink", - "Mahasiswa wajib mengikuti perkuliahan dan menyelesaikan tugas tepat waktu", - "Mahasiswa wajib mengisi daftar hadir dan memberikan alasan resmi jika tidak hadir", - "Mahasiswa memenuhi minimal 70% kehadiran untuk mengikuti UAS" + { "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": [ - "Dosen memasuki ruang perkuliahan sesuai jadwal yang telah ditetapkan", - "Dosen mengabsensi mahasiswa dengan mencantumkan keterangan hadir, izin, atau tidak hadir", - "Pada pertemuan pertama, dosen membuat kontrak perkuliahan secara tertulis bersama mahasiswa", - "Dosen menjelaskan materi perkuliahan sesuai RPS yang telah diserahkan ke program studi", - "Mahasiswa mendiskusikan, mencatat, dan mengkritisi materi perkuliahan", - "Dosen menyimpulkan materi perkuliahan dan memberikan penugasan" + { "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": "Mahasiswa yang tidak memenuhi persyaratan kehadiran minimal tidak diperkenankan mengikuti Ujian Akhir Semester (UAS) sesuai ketentuan akademik yang berlaku." + "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": "SOP Pelaksanaan Ujian (UTS & UAS)", - "description": "Mengatur pedoman pelaksanaan Ujian Tengah Semester (UTS) dan Ujian Akhir Semester (UAS) mulai dari penentuan kriteria evaluasi, pelaksanaan, hingga perekapan nilai.", - "purpose": "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.", + "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": [ - "Mahasiswa terdaftar aktif pada mata kuliah yang sedang berjalan pada semester tersebut", - "Persentase kehadiran perkuliahan mahasiswa tidak kurang dari 75%" + { "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": [ - "Dosen pengampu menentukan kriteria UTS dan UAS", - "Mahasiswa mengikuti UTS atau UAS sesuai instruksi dosen pengampu, dan wajib melakukan presensi", - "Mahasiswa mengumpulkan hasil UTS atau UAS sesuai rentang waktu yang diberikan", - "Dosen menilai dan merekap pengerjaan UTS atau UAS mahasiswa" + { "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": "Segala bentuk kecurangan saat ujian, maupun tindakan plagiarisme dalam pengerjaan UTS/UAS, dapat mengakibatkan pembatalan nilai atau pemberian sanksi." + "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": "SOP Pelaksanaan Evaluasi Pengembangan Perkuliahan", - "description": "Mengatur tata cara pelaksanaan evaluasi perkuliahan oleh mahasiswa sebagai sarana umpan balik untuk meningkatkan kualitas proses belajar mengajar.", - "purpose": "SOP ini bertujuan memberikan pedoman pelaksanaan evaluasi perkuliahan guna mendukung peningkatan mutu pembelajaran di Program Studi Informatika Universitas Tanjungpura.", + "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": [ - "Mahasiswa terdaftar aktif pada semester berjalan", - "Telah mengikuti kegiatan perkuliahan pada mata kuliah yang dievaluasi", - "Tersedia akses ke sistem evaluasi perkuliahan" + { "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": [ - "Program Studi membuka periode evaluasi perkuliahan", - "Mahasiswa mengakses satu untan dan mengisi kuesioner evaluasi perkuliahan", - "Sistem merekap hasil evaluasi yang telah diisi mahasiswa", - "Program Studi meninjau hasil evaluasi sebagai bahan pengembangan perkuliahan" + { "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": "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." + "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": "SOP Kerja Praktik", - "description": "Panduan resmi pelaksanaan Kerja Praktek (KP) mulai dari tata cara pendaftaran, pelaksanaan, hingga seminar Kerja Praktek (KP).", - "purpose": "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.", + "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": [ - "Telah lulus minimal 100 SKS", - "Memiliki IPK minimal 2,00", - "Telah lulus mata kuliah Penulisan Proposal Tugas Akhir dengan nilai minimal C", - "Menyerahkan berkas pendukung pengajuan KP (KTM, riwayat studi, transkrip, dan surat permohonan)", - "Pembimbing Lapangan dari tempat KP memiliki kualifikasi pendidikan minimal S1/D4" + { "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": [ - "Mahasiswa mengusulkan topik, draf proposal, dan menyerahkan berkas KP", - "Staf Administrasi memverifikasi kelengkapan berkas", - "Ketua Prodi menerbitkan surat pengantar KP", - "Mahasiswa melaksanakan KP dan mengikuti bimbingan", - "Pembimbing Lapangan memberikan penilaian KP", - "Mahasiswa mengajukan seminar dan Prodi menerbitkan undangan", - "Mahasiswa melaksanakan seminar KP", - "Mahasiswa melakukan revisi laporan akhir KP" + { "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" + "👤 Staf Administrasi Informatika / Informatics Administrative Staff" ], - "additional_info": "Mata Kuliah Kerja Praktik (KP) dapat diambil bersamaan dengan Tugas Akhir 1 (TA1) dan Tugas Akhir 2 (TA2) dalam semester yang sama." + "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": "SOP Tugas Akhir/Skripsi", - "description": "Mengatur tata cara pelaksanaan skripsi mulai dari pengajuan judul hingga ujian dan penyelesaian skripsi mahasiswa.", - "purpose": "SOP ini bertujuan untuk memberikan pedoman kepada mahasiswa dan dosen dalam pelaksanaan skripsi agar proses penyusunan skripsi berjalan tertib, terarah, dan sesuai ketentuan akademik.", + "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": [ - "Telah lulus minimal 120 SKS", - "Memiliki IPK minimal 2,00", - "Telah lulus mata kuliah Penulisan Proposal Tugas Akhir dengan nilai minimal C", - "Telah mendaftarkan mata kuliah skripsi pada KRS", - "Telah menyiapkan judul dan proposal skripsi", - "Memiliki dosen pembimbing skripsi", - "Melengkapi berkas persyaratan" + { "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": [ - "Mahasiswa mengajukan judul dan proposal skripsi", - "Program studi menetapkan dosen pembimbing skripsi", - "Mahasiswa melakukan bimbingan dan penyusunan skripsi", - "Mahasiswa mengajukan seminar atau sidang skripsi", - "Tim penguji dan pembimbing melaksanakan seminar skripsi", - "Mahasiswa melakukan revisi skripsi", - "Mahasiswa menyerahkan skripsi final" + { "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" + "👤 Staf Administrasi Informatika / Informatics Administrative Staff" ], - "additional_info": "Mahasiswa diberi waktu maksimal 2 semester untuk menyelesaikan Tugas Akhir. Perpanjangan dapat dilakukan dengan persetujuan tertulis dari Kaprodi dan Pembimbing." + "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": "SOP Praktikum Mata Kuliah", - "description": "Mengatur tata cara pelaksanaan praktikum mata kuliah agar kegiatan praktikum berjalan tertib, aman, dan sesuai ketentuan pembelajaran.", - "purpose": "SOP ini bertujuan untuk menjelaskan tata cara pelaksanaan perkuliahan yang memiliki komponen praktikum pada semester berjalan.", + "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": [ - "Terdaftar sebagai peserta mata kuliah yang memiliki komponen praktikum pada semester berjalan", - "Mahasiswa membawa perlengkapan praktikum yang diperlukan" + { "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": [ - "Dosen mata kuliah menuntaskan pembahasan materi yang menjadi dasar praktikum", - "Mahasiswa mengisi daftar hadir dan menerima penjelasan prosedur praktikum", - "Mahasiswa melaksanakan praktikum sesuai modul di bawah pengawasan dosen atau asisten praktikum", - "Mahasiswa menyusun dan mengumpulkan laporan praktikum sesuai tenggat waktu", - "Dosen atau asisten melakukan penilaian laporan dan hasil praktikum sebagai komponen nilai akhir" + { "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": "Mahasiswa wajib mengikuti seluruh ketentuan praktikum dan menjaga ketertiban serta keamanan selama kegiatan praktikum berlangsung." + "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": "SOP Perekrutan Asisten Praktikum", - "description": "Prosedur perekrutan Asisten Praktikum (Aslab) mulai dari pendataan kebutuhan mata kuliah, proses seleksi, hingga pengumuman kelulusan.", - "purpose": "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.", + "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": [ - "Mahasiswa aktif Program Studi Informatika UNTAN", - "Lulus mata kuliah terkait dengan nilai minimal ketentuan Dosen Pengampu", - "Berkomitmen mengikuti seluruh jadwal dan kontrak praktikum selama semester berjalan" + { "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": [ - "Dosen mendata kebutuhan kuota dan kualifikasi Asisten Praktikum", - "Dosen mengumumkan pembukaan rekrutmen asisten praktikum", - "Mahasiswa mendaftarkan diri sesuai persyaratan yang diberikan", - "Dosen melakukan seleksi wawancara kepada calon asisten praktikum", - "Dosen mengumumkan calon yang diterima berdasarkan hasil seleksi" + { "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": "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." + "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": "SOP Peminjaman Laboratorium Komputer", - "description": "Mengatur tata cara peminjaman dan penggunaan laboratorium komputer untuk mendukung kegiatan praktikum dan pembelajaran.", - "purpose": "SOP ini bertujuan menjelaskan tata cara peminjaman lab komputer dan memastikan peminjaman mendukung kelancaran proses belajar mengajar di Program Studi Informatika Universitas Tanjungpura.", + "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": [ - "Melampirkan surat permohonan peminjaman laboratorium", - "Terdapat dosen atau asisten praktikum yang bertanggung jawab", - "Pengguna menaati tata tertib dan ketentuan penggunaan perangkat laboratorium" + { "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": [ - "Dosen atau mahasiswa mengajukan surat permohonan peminjaman laboratorium kepada pengelola", - "Pengguna menyampaikan jadwal penggunaan laboratorium", - "Pengelola menyusun jadwal penggunaan laboratorium", - "Laboratorium digunakan sesuai jadwal dan ketentuan yang berlaku" + { "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" + "👤 Staf Laboratorium Informatika / Informatics Laboratory Staff" ], - "additional_info": "Pengguna laboratorium wajib menjaga kebersihan, keamanan, dan kondisi perangkat laboratorium selama kegiatan berlangsung." + "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." + } } ] \ No newline at end of file diff --git a/groups/C4A/data/data-staff.json b/groups/C4A/data/data-staff.json index be7f7fa..de29fac 100644 --- a/groups/C4A/data/data-staff.json +++ b/groups/C4A/data/data-staff.json @@ -2,42 +2,81 @@ { "id": 1, "name": "Tuning Rahayu, S.Hut.", - "role": "Staf Administrasi Informatika", + "role": { + "id": "Staf Administrasi Informatika", + "en": "Informatics Administrative Staff" + }, "image": "assets/images/staff-female.png", - "location": "Ruang Administrasi Prodi Informatika", + "location": { + "id": "Ruang Administrasi Prodi Informatika", + "en": "Informatics Study Program Administration Room" + }, "contact_consent": true, "phone": "6282350250892", "schedule": [ { - "day": "Senin – Jumat", + "day": { + "id": "Senin – Jumat", + "en": "Monday – Friday" + }, "time": "07.30–16.00 WIB", - "break": "istirahat 12.00–13.00 WIB" + "break": { + "id": "istirahat 12.00–13.00 WIB", + "en": "break 12.00–13.00 WIB" + } } ], "tasks": [ - "Administrasi mahasiswa", - "Informasi berkas akademik" + { + "id": "Administrasi mahasiswa", + "en": "Student administration" + }, + { + "id": "Informasi berkas akademik", + "en": "Academic document information" + } ] }, { "id": 2, "name": "Mahyus Safutra, A.Md.", - "role": "Staf Laboratorium Informatika", + "role": { + "id": "Staf Laboratorium Informatika", + "en": "Informatics Laboratory Staff" + }, "image": "assets/images/staff-male.png", - "location": "Laboratorium Informatika", + "location": { + "id": "Laboratorium Informatika", + "en": "Informatics Laboratory" + }, "contact_consent": true, "phone": "895338672533", "schedule": [ { - "day": "Senin – Jumat", + "day": { + "id": "Senin – Jumat", + "en": "Monday – Friday" + }, "time": "07.30–16.00 WIB", - "break": "istirahat 12.00–13.00 WIB" + "break": { + "id": "istirahat 12.00–13.00 WIB", + "en": "break 12.00–13.00 WIB" + } } ], "tasks": [ - "Pengelolaan laboratorium", - "Dukungan praktikum", - "Peminjaman ruangan dan alat" + { + "id": "Pengelolaan laboratorium", + "en": "Laboratory management" + }, + { + "id": "Dukungan praktikum", + "en": "Practicum support" + }, + { + "id": "Peminjaman ruangan dan alat", + "en": "Room and equipment borrowing" + } ] } ] \ No newline at end of file diff --git a/groups/C4A/index.html b/groups/C4A/index.html index 6652060..ac38de3 100644 --- a/groups/C4A/index.html +++ b/groups/C4A/index.html @@ -1226,12 +1226,15 @@
    - +
    @@ -1269,7 +1272,8 @@
    - +

    Pertanyaan yang Sering Diajukan

    Temukan jawaban cepat atas pertanyaan umum mahasiswa seputar layanan akademik prodi.

    diff --git a/groups/C4A/js/main.js b/groups/C4A/js/main.js index 56a0743..1bb53ca 100644 --- a/groups/C4A/js/main.js +++ b/groups/C4A/js/main.js @@ -48,7 +48,7 @@ async function initApp() { initAnimations(); }, 100); } catch (error) { - console.error('Terjadi kesalahan saat menginisialisasi aplikasi C4A:', error); + console.error('Terjadi kesalahan saat menginisialisasi halaman:', error); } } diff --git a/groups/C4A/js/ui.js b/groups/C4A/js/ui.js index 10cdf4b..8f9f26e 100644 --- a/groups/C4A/js/ui.js +++ b/groups/C4A/js/ui.js @@ -13,14 +13,23 @@ function t(key, fallback) { } /** - * Helper untuk melokalisasi teks dari objek bilingual {id, en} atau string biasa. - * @param {string|Object} field - Data teks (bisa string biasa atau objek {id, en}) - * @returns {string} Teks terlokalisasi + * 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') { - const lang = (window.getLang && typeof window.getLang === 'function') ? window.getLang() : 'id'; + if (typeof field === 'object' && field !== null) { + const lang = getLang(); return field[lang] || field['id'] || ''; } return String(field); @@ -112,10 +121,13 @@ export function renderStaffCards(staffs) { tasks } = staff; + const roleText = loc(role); + const locationText = loc(location); + const scheduleHTML = schedule.map(sched => `
    ` + - `${loc(sched.day)}` + - `${loc(sched.time)} | ${loc(sched.break)}` + + `${loc(sched.day)}` + + `${loc(sched.time)} | ${loc(sched.break)}` + `
    ` ).join(''); @@ -138,12 +150,12 @@ export function renderStaffCards(staffs) {
    ${loc(name)}
    -
    ${loc(role)}
    +
    ${roleText}
    -
    📍
    ${t('grp_c4a_label_location', 'Lokasi')}${loc(location)}
    +
    📍
    ${t('grp_c4a_label_location', 'Lokasi')}${locationText}
    🕒
    @@ -214,11 +226,12 @@ function buildSopCard(sop) { procedures } = sop; - const catLabel = getCatLabel(category); - const catClass = CATEGORY_COLORS[category] || ''; const titleText = loc(title); const descText = loc(description); + const catLabel = getCatLabel(category); + const catClass = CATEGORY_COLORS[category] || ''; + return `
    @@ -263,14 +276,13 @@ function buildSopModalContent(sop) { additional_info: additionalInfo } = sop; - const catLabel = getCatLabel(category); - const catClass = CATEGORY_COLORS[category] || ''; - 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 => `
  • ${loc(req)}
  • `).join(''); const procsHTML = procedures.map(proc => `
  • ${loc(proc)}
  • `).join(''); diff --git a/groups/HabisIniSeminar/README.md b/groups/HabisIniSeminar/README.md new file mode 100644 index 0000000..7969bc8 --- /dev/null +++ b/groups/HabisIniSeminar/README.md @@ -0,0 +1,162 @@ +# 🎓 Portal Informasi Penerimaan Mahasiswa Baru (PMB) +**Program Studi Informatika - Universitas Tanjungpura (UNTAN)** + +![Screenshot Website](Screenshot.png) +*Pratinjau Antarmuka Halaman PMB Informatika UNTAN oleh Kelompok 02* + +--- + +## 💡 Pengantar (Project Overview) + +### 📌 Gambaran Umum +Proyek ini merupakan halaman web statis yang dikembangkan untuk menyediakan informasi penerimaan mahasiswa baru secara terpusat, terstruktur, dan mudah diakses. Halaman ini bertujuan membantu calon mahasiswa dalam memahami berbagai jalur seleksi serta informasi terkait secara menyeluruh. + +### 🎯 Fitur Utama + +Penyajian informasi terpusat untuk seluruh jalur penerimaan: +1. SNBP +2. SNBT +3. Mandiri-UNTAN +4. SMMPTN-Barat + +Informasi detail setiap jalur: +1. Persyaratan +2. Mekanisme seleksi +3. Jadwal +4. Biaya pendidikan +5. Informasi beasiswa +6. FAQ (Frequently Asked Questions) +7. Tautan ke situs resmi dan sumber informasi terkait +8. Tampilan responsif untuk desktop dan perangkat mobile + +--- + +## ❓ Tanya Jawab Seputar Proyek (Project Q&A) + +**Q: Apa fokus utama dari rancangan website ini?** +A: Website ini dirancang sebagai pusat informasi terpadu yang merangkum empat jalur seleksi masuk UNTAN (SNBP, SNBT, SMMPTN-Barat, dan Mandiri). Pengunjung dapat melihat persyaratan, mekanisme, jadwal, rincian biaya, hingga peluang beasiswa dalam satu tampilan interaktif tanpa harus berpindah halaman. + +**Q: Mengapa dibangun sepenuhnya sebagai halaman statis?** +A: Pendekatan statis (HTML, CSS, Vanilla JS) menawarkan tiga keuntungan utama: **Kecepatan** muat yang luar biasa, **Keamanan** tingkat tinggi (karena tidak ada akses *database* langsung dari luar), dan kemudahan **Deployment** di hampir semua layanan *hosting*. + +**Q: Bagaimana sistem dua bahasa (Bilingual) bekerja di website ini?** +A: Kami menggunakan *script* kustom (`localization.js`) yang mendeteksi bahasa bawaan dari peramban pengguna (`navigator.language`). Pengguna juga dapat mengubah bahasa secara manual (ID/EN), dan pilihan tersebut akan disimpan di dalam `localStorage` sehingga preferensi tidak hilang saat halaman di-muat ulang. + +**Q: Apakah visualisasi data di website ini bersifat statis (gambar)?** +A: Tidak. Kami mengimplementasikan **Chart.js** untuk merender grafik perbandingan antara "Jumlah Peminat" dan "Daya Tampung" secara dinamis dan interaktif. + +**Q: Bagaimana tingkat responsivitas website ini di perangkat seluler?** +A: Sangat optimal. Kami menggunakan pendekatan tata letak *Grid* dan *Flexbox* mutakhir. Untuk navigasi kartu jalur masuk, kami bahkan mengintegrasikan *Horizontal Drag Slider* yang ramah sentuhan (touch-friendly) khusus untuk perangkat *mobile* dan tablet. + +--- + +## 🌐 Navigasi & Antarmuka + +1. Menggunakan pendekatan single-page dengan smooth scrolling +2. Navigasi berbasis tab untuk pemilihan jalur seleksi +3. Komponen accordion untuk FAQ dan persyaratan +4. Layout berbasis card untuk penyajian informasi ringkas + +--- + +## 📎 Sumber Eksternal +Sistem menyediakan tautan menuju berbagai sumber resmi, seperti: + +1. Situs resmi SNPMB +2. Situs resmi Mandiri-UNTAN +3. Situs resmi SMMPTN-Barat +4. Portal informasi beasiswa +5. Media sosial resmi terkait + +--- + +## 🛠️ Tumpukan Teknologi (Tech Stack) + +| Kategori | Teknologi | Kegunaan dalam Proyek | +| :--- | :--- | :--- | +| **Inti (Core)** | HTML5, CSS3, Vanilla JS | Fundamental pembentuk struktur, gaya, dan logika halaman. | +| **Visualisasi** | Chart.js 4.4 | Merender grafik batang peminat & daya tampung. | +| **Animasi** | AOS (Animate On Scroll) | Memberikan efek transisi halus saat elemen masuk ke *viewport*. | +| **Ikonografi** | FontAwesome 6.5 | Menyediakan aset ikon modern dan ringan berskala vektor. | + +--- + +## 🚀 Panduan Deployment (Hosting Guide) +Sifat proyek yang 100% statis membuatnya sangat mudah untuk di-deploy (di-online-kan). Pilih salah satu platform favorit Anda di bawah ini: + +### Opsi 1: Vercel (Paling Direkomendasikan & Cepat) + +1. Push proyek ke repositori GitHub, GitLab, atau Bitbucket. + +2. Buat akun dan masuk ke Vercel. + +3. Klik tombol Add New Project dan import repositori Anda. + +4. Pada bagian Framework Preset, pilih Other. + +5. Kosongkan bagian Build Command dan Output Directory. + +6. Klik Deploy. Website Anda akan aktif dalam kurang dari 30 detik. + +### Opsi 2: Coolify (Self-Hosted) + +1. Masuk ke Dashboard Coolify server Anda. + +2. Buka menu proyek lalu tambahkan resource baru (+ Add). + +3. Pilih opsi Application dan hubungkan repositori Git Anda. + +4. Coolify secara otomatis akan menggunakan Nixpacks untuk mendeteksi Static/HTML Site. + +5. Kosongkan Build Command dan Install Command. + +6. Klik Deploy. + +### Opsi 3: GitHub Pages (Paling Praktis) + +1. Pastikan kode sudah di-push ke repositori GitHub. + +2. Masuk ke halaman repositori Anda, klik tab Settings. + +3. Di panel sebelah kiri, pilih menu Pages. + +4. Pada bagian Source, pilih Deploy from a branch. + +5. Pilih branch utama dan pastikan foldernya diatur ke / (root). + +6. Klik Save. + +### Opsi 4: Netlify (Sistem Drag & Drop) + +1. Masuk ke Netlify. + +2. Masuk ke menu Sites -> Klik Add new site. + +3. Kita bisa memilih Import an existing project (lewat Git) atau Deploy manually (cukup seret dan lepas folder proyek ke browser). + +4. Jika lewat Git, biarkan Build command dan Publish directory kosong. + +5. Klik Deploy site. + +--- + +## 👥 Tim Pengembang (Credits) +Disusun dan dikembangkan oleh Kelompok 02 untuk memenuhi standar tugas perancangan antarmuka Halaman Statis. + +Nama Kelompok : Habis Ini Seminar + +--- + +## 📂 Struktur Direktori (Folder Structure) + +Pastikan struktur direktori Anda sesuai sebelum menjalankan atau men-deploy proyek ini: + +```text +📦 project-root/ + ┣ 📜 localization.js # Script logika terjemahan & State Management + ┃ 🖼️ logo.png # Logo Universitas/Prodi + ┃ 🖼️ footer.png # Aset ornamen footer + ┣ 📜 index.html # Halaman utama aplikasi (Entry point) + ┣ 📜 style.css # Berkas tata letak dan desain (Styling) + ┣ 🖼️ Screenshot.png # Gambar pratinjau halaman + ┗ 📜 README.md # Dokumentasi proyek \ No newline at end of file diff --git a/groups/HabisIniSeminar/Screenshot.png b/groups/HabisIniSeminar/Screenshot.png new file mode 100644 index 0000000..45c7996 Binary files /dev/null and b/groups/HabisIniSeminar/Screenshot.png differ diff --git a/groups/HabisIniSeminar/index.html b/groups/HabisIniSeminar/index.html index d958cb9..512bf29 100644 --- a/groups/HabisIniSeminar/index.html +++ b/groups/HabisIniSeminar/index.html @@ -1,14 +1,18 @@ - + - Group 02 – HabisIniSeminar | Informatika UNTAN + Group 02 – HabisIniSeminar | Informatika UNTAN + + + + + @@ -1139,20 +1145,1003 @@
    -
    -
    -
    - Tugas Halaman Statis -

    Kehidupan Mahasiswa & Himpunan (HMIF)

    -

    Kelompok 07 — LIMA EM  ·  Informatika UNTAN 2026

    -
    -
    - -

    Tulis konten halaman kelompok di sini.

    -

    Subjudul

    -

    Gunakan paragraf, list, tabel, gambar, dan elemen konten lainnya.

    - -
    +
    + +
    + + +
    + + Foto Kegiatan Mahasiswa + +
    +
    + +
    + +
    + +
    + +
    + COMMUNITY & LEADERSHIP +
    + +

    + Kehidupan Mahasiswa & Himpunan (HMIF) +

    + +

    + Membangun ekosistem kolaboratif di mana teknologi bertemu dengan + kreativitas, membentuk calon pemimpin industri masa depan. +

    + + + +
    + + + +
    +
    +
    + + +
    +
    +
    + + +
    + + +

    + Ruang Tumbuh Bagi
    Mahasiswa Informatika +

    + + +
    +

    + Himpunan Mahasiswa Informatika (HMIF) Universitas Tanjungpura bukan sekadar + organisasi, melainkan rumah bagi para pemimpin teknologi. Kami percaya bahwa + pendidikan formal adalah fondasi, namun komunitas adalah akselerator. +

    +

    + Melalui berbagai inisiatif, kami menjembatani kesenjangan antara kurikulum + akademik dan kebutuhan industri, sambil tetap menjaga semangat kekeluargaan + dan integritas ilmiah. +

    +
    + + +
    +
    +

    + 518

    +

    + Mahasiswa Aktif +

    +
    +
    +

    + 8

    +

    + MITRA +

    +
    +
    +
    + + +
    + + +
    + + +
    + Ruang Tumbuh HMIF Universitas Tanjungpura + +
    +
    +
    + + +
    + +

    + Inklusivitas &
    Kolaborasi +

    +
    +
    + +
    +
    +
    + + +
    +
    +

    + Partner dan Kolaborator +

    + +
    + + + Namaku + + + + + Sobat Konveksi + + + +
    + Ayani Megamall +
    + + +
    + Indofood +
    + + +
    + Playhera +
    + + +
    + PLN +
    + + +
    + RTech +
    + + +
    + Rumangsa +
    +
    +
    +
    + + + +
    +
    + +
    +

    + Divisi Kepengurusan HMIF +

    +

    + Struktur organisasi yang dirancang untuk efisiensi dan pengembangan kapasitas anggota di + berbagai + bidang strategis. +

    +
    + + +
    + +
    + + Bidang 1 + +

    + Pengembangan Sumber Daya Mahasiswa dan Kaderisasi +

    +

    + Divisi ini menyelenggarakan kaderisasi terstruktur, mentoring berkala, dan pelatihan + kepemimpinan serta soft-skill. Program kerja mencakup onboarding anggota baru, + workshop kepemimpinan, proyek kolaboratif, dan kegiatan pengabdian yang melatih + tanggung jawab sosial. Fokusnya adalah menyiapkan kader yang kompeten dan + menjaga regenerasi organisasi. +

    +
    + + +
    + + Bidang 2 + +

    + Media Komunikasi dan Konten Digital +

    +

    + Divisi ini mengelola strategi komunikasi dan produksi konten HMIF, termasuk + perencanaan editorial, manajemen media sosial, serta dokumentasi acara. Program + kerjanya meliputi pembuatan materi promosi, publikasi kegiatan, dan penguatan + identitas digital agar informasi organisasi tersampaikan konsisten dan menarik. +

    +
    + + +
    + + Bidang 3 + +

    + Inovasi dan Kompetisi Teknologi +

    +

    + Divisi ini memfasilitasi persiapan lomba dan pengembangan proyek teknis melalui + coaching clinic, bootcamp berbasis proyek, serta study club untuk Competitive + Programming, Software Development, dan Data/ML. Kegiatan termasuk seleksi internal, + bimbingan kompetitif, dan inisiatif digitalisasi seperti pengembangan website + dan dashboard untuk mendukung ekosistem inovasi HMIF. +

    +
    + + +
    + + Bidang 4 + +

    + Pengembangan Organisasi +

    +

    + Divisi ini memperkuat tata kelola dan kapabilitas internal melalui pelatihan + pengurus, penyusunan SOP sederhana, dan evaluasi proses kerja. Program kerjanya + mencakup pengembangan budaya organisasi, peningkatan efektivitas koordinasi, dan + kegiatan peningkatan profesionalisme sehingga operasi HMIF menjadi lebih terstruktur + dan efisien. +

    +
    + + +
    + + Bidang 5 + +

    + Sosial Masyarakat +

    +

    + Divisi ini merancang dan melaksanakan program pengabdian serta aksi sosial yang + berdampak, termasuk pendampingan komunitas dan bantuan kemanusiaan. Kegiatan + meliputi identifikasi kebutuhan lapangan, pelaksanaan terstruktur, dan evaluasi + dampak untuk memastikan keberlanjutan dan manfaat nyata bagi masyarakat. +

    +
    + + +
    + + Bidang 6 + +

    + Kewirausahaan dan Kemitraan +

    +

    + Divisi ini mengembangkan unit usaha organisasi, merancang skema pendanaan kreatif, + dan menjalin kemitraan strategis untuk mendukung keberlanjutan program HMIF. Unit + kerjanya meliputi riset peluang usaha, penyusunan proposal kemitraan, dan + pengelolaan sponsor untuk meningkatkan kemandirian finansial dan akses jaringan + profesional. +

    +
    +
    +
    +
    + + +
    +
    + +
    +

    + Kegiatan Unggulan +

    +

    + Kegiatan tahunan yang menjadi ikon Informatika UNTAN. +

    +
    + + +
    + + + + +
    + +
    + + +
    +
    + Silaturahmi Keluarga Besar +
    +
    +
    + 27 + Feb +
    +
    +

    + Silaturahmi Keluarga Besar Informatika

    +

    Kegiatan buka + bersama + seluruh keluarga Informatika Untan yang selalu diadakan setiap tahunnya. +

    +
    +
    +
    + + +
    +
    + Bootcamp HMIF +
    +
    +
    + 1 + Jul +
    +
    +

    + GEMASTIK

    +

    Ajang + kompetisi IT bergengsi tingkat nasional tahunan dari kementerian. + Wadah bagi mahasiswa Informatika UNTAN untuk unjuk + gigi di bidang programming, cyber security, data + mining, hingga UI/UX. +

    +
    +
    +
    + + +
    +
    + Perayaan 17 Agustus +
    +
    +
    + 17 + Agu +
    +
    +

    + Perayaan 17 Agustus

    +

    Momen perayaan + Hari Kemerdekaan Indonesia yang diisi dengan berbagai perlombaan + menarik. + Ajang seru-seruan bersama untuk mempererat rasa nasionalisme dan + kekeluargaan mahasiswa Informatika.

    +
    +
    +
    + + +
    +
    + Bootcamp HMIF +
    +
    +
    + 28 + Agu +
    +
    +

    + Bootcamp HMIF

    +

    Program + intensif + pengembangan skill teknis mahasiswa Informatika bersama mentor + profesional dari + industri.

    +
    +
    +
    + + +
    +
    + UPC 2025 +
    +
    +
    + 20 + Okt +
    +
    +

    + UPC

    +

    Kompetisi + Pemograman untuk + mengasah skill coding mahasiswa Informatika.

    +
    +
    +
    + + +
    +
    + Study Excursion +
    +
    +
    + 20 + Okt +
    +
    +

    + Study Excursion

    +

    Study Excursion + menjadi pengalaman + berharga bagi mahasiswa semester 5 untuk belajar di luar kampus, + memperluas wawasan, + mengenal suasana baru, serta menciptakan kenangan berkesan bersama + selama perjalanan.

    +
    +
    +
    + + +
    +
    + Ultah Informatika +
    +
    +
    + 25 + Okt +
    +
    +

    + INFORMERS

    +

    Perayaan ulang + tahun + Informatika UNTAN, momen kebersamaan dan kebanggaan seluruh keluarga + besar + Informatika.

    +
    +
    +
    + +
    +
    + + + +
    + + +
    + + + + + + +
    +
    +
    + + +
    +
    + +
    +

    + STUDY CLUB +

    +
    +
    + + +
    + +
    +
    + + + + +
    +
    +

    + ARISE +

    +

    + Belajar logika dan algoritma bersama, di mana kalian bisa berkembang dan membuka + peluang + mengikuti kompetisi pemrograman di berbagai level. +

    +
    +
    + + +
    +
    + + + + +
    +
    +

    + MINERVA +

    +

    + Gali potensi tersembunyi dari tumpukan data! Di sini kita akan bereksplorasi dengan + algoritma Machine Learning, membedah teknik klasifikasi, hingga melatih + model + prediktif untuk menghasilkan insight yang bermakna. +

    +
    +
    + +
    +
    + + + + +
    +
    +

    + Software Development (Soon) +

    +

    + Wujudkan idemu jadi produk digital yang nyata! Kita akan ngoding bareng dari + merancang + frontend yang interaktif, membangun backend yang tangguh, serta + mengintegrasikan berbagai API untuk menciptakan aplikasi web masa kini. +

    +
    +
    +
    +
    +
    + + +
    +
    + + +
    +

    + Galeri Keseruan +

    +
    + + +
    + + + + + +
    + + +
    + + +
    +
    + + + UPC V + + + + + + + +
    +
    + + + + + + + +
    +
    + + + + +
    + + +
    + + + +
    + +
    +
    + + +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +

    + Ada yang Ingin Ditanyakan Seputar HMIF? +

    + +

    + Bergabunglah dengan HMIF untuk memperluas jaringan, mengasah skill, dan + menciptakan dampak nyata. +

    + + +
    + +
    +
    +
    +

    + Sekretariat +

    +

    + Gedung Fakultas Teknik Lt. 2
    + Jl. Prof. Dr. H. Hadari Nawawi +

    +
    + +
    +

    + Jam Operasional +

    +

    + Senin - Jumat
    + 08:00 - 17:00 WIB +

    +
    + +
    +

    + Media Sosial +

    +

    + @hmif_ftuntan
    +

    +
    +
    +
    +
    +
    +
    +
    +
    @@ -1215,11 +2204,249 @@
    - Footer Illustration + Footer Illustration
    + + - + - + + \ No newline at end of file diff --git a/groups/LimaEm/style.css b/groups/LimaEm/style.css index 2bc5542..6a19969 100644 --- a/groups/LimaEm/style.css +++ b/groups/LimaEm/style.css @@ -1,4 +1,4 @@ -:root { +:root { --brand-navy: #003150; --brand-yellow: #feb401; --text-main: #1e293b; @@ -22,7 +22,27 @@ body { font-family: "Roboto", Arial, sans-serif; color: var(--text-main); backgr .hero h1 { margin: 0; font-size: clamp(1.6rem, 3.2vw, 2.8rem); line-height: 1.2; } .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); } } +@media (min-width: 1024px) { + .layout { grid-template-columns: minmax(0, 3fr) minmax(260px, 1fr); } + /* Fix tinggi grid Galeri Keseruan (Desktop) */ + .galeri-grid { + height: 500px !important; + } +} + +/* ===== Custom Layout Fixes (Menggantikan arbitrary Tailwind) ===== */ +@media (min-width: 768px) { + + /* Fix ukuran 2 card per baris untuk Kegiatan Unggulan */ + .md-card-half { + width: calc(50% - 16px) !important; + } + + /* Fix tinggi grid Galeri Keseruan (Tablet) */ + .galeri-grid { + height: 420px !important; + } +} .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; } @@ -46,3 +66,29 @@ body { font-family: "Roboto", Arial, sans-serif; color: var(--text-main); backgr .footer-logo { height: 28px; width: auto; } .footer-ornament { width: 100%; display: block; opacity: .9; } +/* ===== Slider Dot Indicators ===== */ +.slider-dot, +.galeri-dot { + display: inline-block; + height: 10px; + width: 10px; + min-width: 10px; + border-radius: 9999px; + background-color: #d1d5db; /* gray-300 fallback */ + cursor: pointer; + border: none; + padding: 0; + transition: width 0.3s ease, background-color 0.3s ease; + flex-shrink: 0; +} + +.slider-dot.bg-hmif-primary, +.galeri-dot.bg-hmif-primary { + background-color: #0b2f4c; /* --color-hmif-primary dari output.css */ + width: 24px; +} + +/* Fix: title divisi berubah kuning saat hover */ +#divisi .group:hover h3 { + color: #f5c82f !important; +} diff --git a/groups/M4N1FEST/assets/BCA.png b/groups/M4N1FEST/assets/BCA.png new file mode 100644 index 0000000..8700dd5 Binary files /dev/null and b/groups/M4N1FEST/assets/BCA.png differ diff --git a/groups/M4N1FEST/assets/Bulog-Logo.png b/groups/M4N1FEST/assets/Bulog-Logo.png new file mode 100644 index 0000000..86a97a2 Binary files /dev/null and b/groups/M4N1FEST/assets/Bulog-Logo.png differ diff --git a/groups/M4N1FEST/assets/Indofood.png b/groups/M4N1FEST/assets/Indofood.png new file mode 100644 index 0000000..a784a44 Binary files /dev/null and b/groups/M4N1FEST/assets/Indofood.png differ diff --git a/groups/M4N1FEST/assets/Telkom.jpg b/groups/M4N1FEST/assets/Telkom.jpg new file mode 100644 index 0000000..0b21c7d Binary files /dev/null and b/groups/M4N1FEST/assets/Telkom.jpg differ diff --git a/groups/M4N1FEST/assets/Unilever.png b/groups/M4N1FEST/assets/Unilever.png new file mode 100644 index 0000000..3bcfaba Binary files /dev/null and b/groups/M4N1FEST/assets/Unilever.png differ diff --git a/groups/M4N1FEST/assets/WhatsApp Image 2026-05-21 at 5.33.12 AM (1).jpeg b/groups/M4N1FEST/assets/WhatsApp Image 2026-05-21 at 5.33.12 AM (1).jpeg new file mode 100644 index 0000000..aefed7c Binary files /dev/null and b/groups/M4N1FEST/assets/WhatsApp Image 2026-05-21 at 5.33.12 AM (1).jpeg differ diff --git a/groups/M4N1FEST/assets/WhatsApp Image 2026-05-21 at 5.33.12 AM.jpeg b/groups/M4N1FEST/assets/WhatsApp Image 2026-05-21 at 5.33.12 AM.jpeg new file mode 100644 index 0000000..3ea070a Binary files /dev/null and b/groups/M4N1FEST/assets/WhatsApp Image 2026-05-21 at 5.33.12 AM.jpeg differ diff --git a/groups/M4N1FEST/assets/WhatsApp Image 2026-05-21 at 5.34.09 AM.jpeg b/groups/M4N1FEST/assets/WhatsApp Image 2026-05-21 at 5.34.09 AM.jpeg new file mode 100644 index 0000000..f0bbc0e Binary files /dev/null and b/groups/M4N1FEST/assets/WhatsApp Image 2026-05-21 at 5.34.09 AM.jpeg differ diff --git a/groups/M4N1FEST/assets/astra.webp b/groups/M4N1FEST/assets/astra.webp new file mode 100644 index 0000000..fc54c6a Binary files /dev/null and b/groups/M4N1FEST/assets/astra.webp differ diff --git a/groups/M4N1FEST/assets/bri.png b/groups/M4N1FEST/assets/bri.png new file mode 100644 index 0000000..9771408 Binary files /dev/null and b/groups/M4N1FEST/assets/bri.png differ diff --git a/groups/M4N1FEST/assets/bulog.jpeg b/groups/M4N1FEST/assets/bulog.jpeg new file mode 100644 index 0000000..fdeb10f Binary files /dev/null and b/groups/M4N1FEST/assets/bulog.jpeg differ diff --git a/groups/M4N1FEST/assets/charles.jpg b/groups/M4N1FEST/assets/charles.jpg new file mode 100644 index 0000000..8fe078b Binary files /dev/null and b/groups/M4N1FEST/assets/charles.jpg differ diff --git a/groups/M4N1FEST/assets/dona.jpg b/groups/M4N1FEST/assets/dona.jpg new file mode 100644 index 0000000..5a52140 Binary files /dev/null and b/groups/M4N1FEST/assets/dona.jpg differ diff --git a/groups/M4N1FEST/assets/garuda_indonesia.jpg b/groups/M4N1FEST/assets/garuda_indonesia.jpg new file mode 100644 index 0000000..4346f9e Binary files /dev/null and b/groups/M4N1FEST/assets/garuda_indonesia.jpg differ diff --git a/groups/M4N1FEST/assets/grab.png b/groups/M4N1FEST/assets/grab.png new file mode 100644 index 0000000..215f2e8 Binary files /dev/null and b/groups/M4N1FEST/assets/grab.png differ diff --git a/groups/M4N1FEST/assets/izhan.jpg b/groups/M4N1FEST/assets/izhan.jpg new file mode 100644 index 0000000..e864cf8 Binary files /dev/null and b/groups/M4N1FEST/assets/izhan.jpg differ diff --git a/groups/M4N1FEST/assets/pln.jpg b/groups/M4N1FEST/assets/pln.jpg new file mode 100644 index 0000000..2cf444f Binary files /dev/null and b/groups/M4N1FEST/assets/pln.jpg differ diff --git a/groups/M4N1FEST/assets/telkomsel.jpg b/groups/M4N1FEST/assets/telkomsel.jpg new file mode 100644 index 0000000..84a6c5e Binary files /dev/null and b/groups/M4N1FEST/assets/telkomsel.jpg differ diff --git a/groups/M4N1FEST/assets/unilever-logo.png b/groups/M4N1FEST/assets/unilever-logo.png new file mode 100644 index 0000000..da2b92d Binary files /dev/null and b/groups/M4N1FEST/assets/unilever-logo.png differ diff --git a/groups/M4N1FEST/index.html b/groups/M4N1FEST/index.html index 3a813ee..cef1a3f 100644 --- a/groups/M4N1FEST/index.html +++ b/groups/M4N1FEST/index.html @@ -9,6 +9,8 @@ + + + + + + - - + +
    + + +
    +
    +
    +
    +
    +
    400+
    +

    + Hasil Penelitian

    +
    +
    +
    7.555
    +

    Skor SINTA

    +
    +
    +
    33
    +

    Dosen Homebase

    +
    +
    +
    10+
    +

    Kerjasama Aktif

    +
    +
    +
    518
    +

    Mahasiswa Aktif

    +
    +
    +
    +
    + + +
    +
    +
    +
    + Academic + Achievements +

    Publikasi & Penelitian Terkini

    +
    +
    + + +
    + +
    + + + +
    +
    +
    + + + + + + +
    + +
    + Tampilkan: +
    + + + + + +
    +
    +
    +

    This page was last updated on:

    + +
    + Menghitung data... +
    +
    +
    + +
    + +
    + + +
    +
    + + +
    +
    +
    + Experts + Directory +

    + Peneliti & Dosen per Kelompok Keahlian

    +

    Bagian ini menampilkan + ketua setiap KBK. Pilih kartu untuk melihat dosen lain di kelompok keahlian tersebut.

    +
    + +
    + +
    +
    +
    + + +
    +
    +
    +
    +

    Laboratorium Pendukung Riset

    +

    5 laboratorium modern dengan 93 unit PC All-in-One + yang mendukung kegiatan riset strategis dan pengembangan teknologi dosen serta mahasiswa. +

    +
    +
    +
    +
    05
    +
    Labs Modern +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    +
    + + 25 PC + + + 25 Mahasiswa + +
    +
    +

    Lab Komputer & Pemrograman

    +

    LAB/D48 — Kepala: Helen Sastypratiwi, S.T., M.Eng.

    +

    Mendukung mata kuliah pemrograman dasar hingga lanjut, pengembangan aplikasi web, mobile, dan rekayasa perangkat lunak.

    +
    + VS Code + Node.js + Android Studio + Python 3 +
    +
    + + +
    +
    +
    + +
    +
    +

    Lab Jaringan, Keamanan & IoT

    +

    LAB/02 — Kepala: Heri Priyanto, ST., MT.

    +

    Sarana praktik jaringan, konfigurasi router/switch, keamanan siber, administrasi sistem, dan pengembangan perangkat IoT.

    +
    + Cisco Packet + Wireshark + Raspberry Pi +
    +
    + + +
    +
    +
    + +
    +
    + + 25 PC + + + 25 Mahasiswa + +
    +
    +

    Lab Rekayasa PL & Data

    +

    LAB/03 — Kepala: Rudy Dwi Nyoto, ST., M.Eng.

    +

    Mendukung pembelajaran basis data, pemodelan data, data warehousing, dan analitik data skala besar.

    +
    + MySQL + PostgreSQL + MongoDB + RapidMiner +
    +
    + + +
    +
    +
    + +
    +
    + + 21 PC + + + 21 Mahasiswa + +
    +
    +

    Lab Teknologi & Sistem Informasi

    +

    LAB/04 — Kepala: Anggi Srimurdianti Sukamto, ST., MT.

    +

    Merancang arsitektur sistem informasi, memodelkan proses bisnis digital, serta menumbuhkan jiwa technopreneurship.

    +
    + Bizagi + Power BI + Figma +
    +
    + + +
    +
    +
    + +
    +
    + + 22 PC + + + 22 Mahasiswa + +
    +
    +

    Lab Komputasi & Kecerdasan Buatan

    +

    LAB/05 — Kepala: Tursina, ST, M.Cs.

    +

    Pusat riset machine learning, deep learning, computer vision, NLP, dan pengembangan sistem kecerdasan buatan berbasis data.

    +
    + TensorFlow + PyTorch + OpenCV + Jupyter +
    +
    +
    +
    +
    + + +
    +
    +
    + +
    +

    Tertarik Berkolaborasi dengan KBK Kami?

    +

    + Kami membuka peluang kerja sama riset lintas institusi, kemitraan strategis dengan industri, serta + riset mahasiswa (skripsi/tesis) di bawah supervisi ahli kami. +

    + +
    +
    +
    + + +

    @@ -1219,6 +1196,870 @@ style="object-position: bottom;">

    + + + + - + - + + + - \ No newline at end of file + + diff --git a/groups/RODA/package-lock.json b/groups/RODA/package-lock.json new file mode 100644 index 0000000..2155203 --- /dev/null +++ b/groups/RODA/package-lock.json @@ -0,0 +1,13 @@ +{ + "name": "roda", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "roda", + "version": "1.0.0", + "license": "ISC" + } + } +} diff --git a/groups/RODA/package.json b/groups/RODA/package.json new file mode 100644 index 0000000..b0606c6 --- /dev/null +++ b/groups/RODA/package.json @@ -0,0 +1,14 @@ +{ + "name": "roda", + "version": "1.0.0", + "description": "Project website untuk menampilkan sorotan dan prestasi karya mahasiswa program studi Informatika Universitas Tanjungpura.", + "main": "server.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node server.js", + "dev": "node server.js" + }, + "keywords": [], + "author": "", + "license": "ISC" +} \ No newline at end of file diff --git a/groups/RODA/server.js b/groups/RODA/server.js new file mode 100644 index 0000000..42dd119 --- /dev/null +++ b/groups/RODA/server.js @@ -0,0 +1,279 @@ +const fs = require('fs'); +const http = require('http'); +const path = require('path'); +const { URL } = require('url'); + +const ROOT_DIR = __dirname; +const PARENT_DIR = path.join(__dirname, '..', '..'); +const INDEX_PATH = path.join(ROOT_DIR, 'index.html'); +const ENV_PATH = path.join(ROOT_DIR, '.env'); +const HOST = '127.0.0.1'; +const PORT = Number(process.env.PORT || 3000); + +function logServerError(scope, error, extra = {}) { + console.error(`[${scope}]`, error && error.stack ? error.stack : error, extra); +} + +function parseEnvFile(filePath) { + if (!fs.existsSync(filePath)) { + return {}; + } + + const env = {}; + const lines = fs.readFileSync(filePath, 'utf8').split(/\r?\n/); + + for (const rawLine of lines) { + const line = rawLine.trim(); + if (!line || line.startsWith('#')) { + continue; + } + + const separatorIndex = line.indexOf('='); + if (separatorIndex === -1) { + continue; + } + + const key = line.slice(0, separatorIndex).trim(); + let value = line.slice(separatorIndex + 1).trim(); + + if ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) { + value = value.slice(1, -1); + } + + env[key] = value; + } + + return env; +} + +function getAppConfig() { + const env = { + ...parseEnvFile(ENV_PATH), + ...process.env + }; + + const dashboardProxyTemplates = [ + env.DASHBOARD_PROXY_1, + env.DASHBOARD_PROXY_2, + env.DASHBOARD_PROXY_3 + ].filter(Boolean); + + return { + dashboardTargetUrl: env.DASHBOARD_TARGET_URL || '', + dashboardProxyTemplates + }; +} + +const DASHBOARD_CACHE_TTL_MS = 5 * 60 * 1000; +let dashboardCache = { html: null, timestamp: 0 }; + +async function fetchDashboardHtml() { + const targetUrl = getAppConfig().dashboardTargetUrl; + if (!targetUrl) { + throw new Error('DASHBOARD_TARGET_URL kosong'); + } + + const now = Date.now(); + if (dashboardCache.html && (now - dashboardCache.timestamp) < DASHBOARD_CACHE_TTL_MS) { + return { html: dashboardCache.html, cached: 'HIT' }; + } + + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 12000); + try { + const response = await fetch(targetUrl, { + signal: controller.signal, + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36' + } + }); + if (!response.ok) { + throw new Error(`HTTP_ERROR_${response.status}`); + } + const html = await response.text(); + dashboardCache = { html, timestamp: Date.now() }; + return { html, cached: 'MISS' }; + } catch (error) { + if (dashboardCache.html) { + return { html: dashboardCache.html, cached: 'STALE' }; + } + throw error; + } finally { + clearTimeout(timeoutId); + } +} + +function validateAppConfig(config) { + const errors = []; + + if (!config.dashboardTargetUrl) { + errors.push('DASHBOARD_TARGET_URL belum diisi.'); + } + + if (!Array.isArray(config.dashboardProxyTemplates) || !config.dashboardProxyTemplates.length) { + errors.push('Minimal satu DASHBOARD_PROXY_* harus diisi.'); + } + + return errors; +} + +function injectConfig(html) { + const configScript = ``; + const defaultConfigScript = ''; + + if (html.includes(defaultConfigScript)) { + return html.replace(defaultConfigScript, configScript); + } + + return html.replace('', ` ${configScript}\n`); +} + +function sendHtml(res) { + try { + const html = fs.readFileSync(INDEX_PATH, 'utf8'); + const configErrors = validateAppConfig(getAppConfig()); + if (configErrors.length) { + logServerError('config', new Error('Invalid app config'), { configErrors }); + } + + const withConfig = injectConfig(html); + res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); + res.end(withConfig); + } catch (error) { + logServerError('sendHtml', error); + res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' }); + res.end('Internal Server Error'); + } +} + +function getMimeType(pathname) { + const ext = path.extname(pathname).toLowerCase(); + const mimeTypes = { + '.html': 'text/html; charset=utf-8', + '.css': 'text/css; charset=utf-8', + '.js': 'application/javascript; charset=utf-8', + '.json': 'application/json; charset=utf-8', + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.gif': 'image/gif', + '.svg': 'image/svg+xml', + '.ico': 'image/x-icon', + '.webp': 'image/webp', + '.woff': 'font/woff', + '.woff2': 'font/woff2', + '.ttf': 'font/ttf', + '.eot': 'application/vnd.ms-fontobject' + }; + return mimeTypes[ext] || 'application/octet-stream'; +} + +function sendStaticFile(res, filepath) { + try { + if (!fs.existsSync(filepath)) { + res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' }); + res.end('Not found'); + return; + } + + const content = fs.readFileSync(filepath); + const mimeType = getMimeType(filepath); + res.writeHead(200, { 'Content-Type': mimeType }); + res.end(content); + } catch (error) { + logServerError('sendStaticFile', error, { filepath }); + res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' }); + res.end('Internal Server Error'); + } +} + +function sendNotFound(res) { + res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' }); + res.end('Not found'); +} + +const server = http.createServer((req, res) => { + try { + const requestUrl = new URL(req.url, `http://${req.headers.host}`); + const pathname = requestUrl.pathname; + + if (pathname === '/' || pathname === '/index.html') { + sendHtml(res); + return; + } + + if (pathname === '/api/dashboard') { + fetchDashboardHtml() + .then(({ html, cached }) => { + res.writeHead(200, { + 'Content-Type': 'text/html; charset=utf-8', + 'X-Cache': cached + }); + res.end(html); + }) + .catch((error) => { + logServerError('api-dashboard', error); + res.writeHead(502, { 'Content-Type': 'text/plain; charset=utf-8' }); + res.end('DASHBOARD_FETCH_FAILED'); + }); + return; + } + + // Handle /assets/ from parent directory + if (pathname.startsWith('/assets/')) { + const assetsFilepath = path.join(PARENT_DIR, pathname); + const normalizedAssetsPath = path.normalize(assetsFilepath); + + if (!normalizedAssetsPath.startsWith(path.normalize(PARENT_DIR))) { + sendNotFound(res); + return; + } + + sendStaticFile(res, normalizedAssetsPath); + return; + } + + // Serve static files from ROOT_DIR + const filepath = path.join(ROOT_DIR, pathname); + const normalizedPath = path.normalize(filepath); + + // Security check: ensure the requested file is within ROOT_DIR + if (!normalizedPath.startsWith(path.normalize(ROOT_DIR))) { + sendNotFound(res); + return; + } + + sendStaticFile(res, normalizedPath); + } catch (error) { + logServerError('request-handler', error, { url: req.url }); + res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' }); + res.end('Internal Server Error'); + } +}); + +server.on('clientError', (error, socket) => { + logServerError('clientError', error); + socket.end('HTTP/1.1 400 Bad Request\r\n\r\n'); +}); + +server.listen(PORT, HOST, () => { + console.log(`Server ready at http://${HOST}:${PORT}`); +}); + +server.on('error', (error) => { + logServerError('server', error, { host: HOST, port: PORT }); + process.exitCode = 1; +}); + +process.on('uncaughtException', (error) => { + logServerError('uncaughtException', error); + process.exitCode = 1; +}); + +process.on('unhandledRejection', (reason) => { + logServerError('unhandledRejection', reason instanceof Error ? reason : new Error(String(reason))); + process.exitCode = 1; +}); diff --git a/groups/RODA/style.css b/groups/RODA/style.css index 2bc5542..4a23817 100644 --- a/groups/RODA/style.css +++ b/groups/RODA/style.css @@ -1,48 +1,310 @@ -:root { - --brand-navy: #003150; - --brand-yellow: #feb401; - --text-main: #1e293b; - --text-soft: #64748b; - --bg-soft: #f8fafc; - --line: #e2e8f0; +.bg-pattern { + background-image: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.05'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E"); } -* { 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; } +/* Navbar & Accessibility Fixes */ +#nav-wrapper { + position: fixed !important; + top: 0; + left: 0; + right: 0; + z-index: 50; + backface-visibility: hidden; + -webkit-backface-visibility: hidden; + will-change: box-shadow; +} -.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; } +#nav-wrapper>div:first-child { + transform: translateZ(0); + -webkit-transform: translateZ(0); +} -.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; } +@media (max-width: 1023px) { + #nav-wrapper>div:first-child { + min-height: 0 !important; + max-height: none; + } +} -.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); } } +html { + scroll-behavior: smooth; +} -.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; } +.ticker-wrap { + mask-image: linear-gradient(to right, transparent 0%, black 10%, black 90%, transparent 100%); + -webkit-mask-image: linear-gradient(to right, transparent 0%, black 10%, black 90%, transparent 100%); +} -.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; } +.ticker-track { + display: inline-flex; + white-space: nowrap; + min-width: max-content; + animation: tickerMove 30s linear infinite; + will-change: transform; +} -.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; } +.ticker-wrap:hover .ticker-track { + animation-play-state: paused; +} -.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; } +@keyframes tickerMove { + 0% { + transform: translateX(0); + } + + 100% { + transform: translateX(-50%); + } +} + +/* Responsive animation timing */ +@media (max-width: 768px) { + .ticker-track { + animation-duration: 25s; + } +} + +@media (min-width: 1366px) { + .ticker-track { + animation-duration: 35s; + } +} + +.line-clamp-1 { + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 1; +} + +.line-clamp-2 { + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; +} + +.line-clamp-3 { + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 3; +} + +.reveal { + opacity: 1; + transform: none; +} + +.reveal-ready .reveal { + opacity: 0; + transform: translateY(24px); + transition: opacity 0.65s ease, transform 0.65s ease; +} + +.reveal-ready .reveal.is-visible { + opacity: 1; + transform: translateY(0); +} + +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; + } + + .ticker-track { + animation: none; + } + + .reveal { + opacity: 1; + transform: none; + transition: none; + } +} + +/* Dosen grid: subgrid rows agar header & footer setiap card sejajar */ +@media (min-width: 1024px) { + #dosen-grid { + grid-template-rows: auto auto; + } + #dosen-grid > a { + grid-row: span 2; + display: grid; + grid-template-rows: subgrid; + } + /* White section: flex column so expertise sits at top, rank/link pinned to bottom */ + #dosen-grid > a > div:last-child { + display: flex !important; + flex-direction: column !important; + padding-top: 1rem !important; + } + #dosen-grid > a > div:last-child > div:last-child { + margin-top: auto; + } +} + +/* Custom responsive breakpoints - overriding Tailwind CDN defaults */ +/* Mobile: 390px and up */ +@media (min-width: 390px) { + body { + font-size: 14px; + } +} + +/* Tablet: 768px and up */ +@media (min-width: 768px) { + #kbk-card-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +/* Laptop/Desktop: 1024px and up */ +@media (min-width: 1024px) { + nav.bg-\[#feb401\] { + display: block !important; + } + + #lang-toggle-btn { + display: flex !important; + } + + .hidden.lg\:flex { + display: flex !important; + } + + /* Hide hamburger button on desktop (aligns with ACAAB lg:1024px) */ + #mobile-menu-button { + display: none !important; + } + + #kbk-card-grid { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + + #dosen-grid { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + + #riset-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } +} + +/* Desktop: 1366px and up */ +@media (min-width: 1366px) { + #kbk-card-grid { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + + #riset-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + #dosen-grid { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } +} + +/* Extra Large Desktop: 1920px and up */ +@media (min-width: 1920px) { + body { + font-size: 16px; + } +} + +/* ===== MEGA MENU STYLES FROM LIMAEM ===== */ +.mega-panel { + opacity: 0; + visibility: hidden; + transition: opacity 0.2s ease, visibility 0.2s ease; + position: absolute; + top: 100%; + left: 0; + right: 0; +} + +.mega-panel.active { + opacity: 1; + visibility: visible; +} + +#mega-overlay { + opacity: 0; + visibility: hidden; + transition: opacity 0.2s ease, visibility 0.2s ease; + pointer-events: none; +} + +#mega-overlay.active { + opacity: 1; + visibility: visible; + pointer-events: auto; +} + +.mega-link-card { + display: flex; + align-items: flex-start; + gap: 1rem; + padding: 1rem; + border-radius: 0.75rem; + border: 1px solid #e2e8f0; + background: white; + transition: all 0.2s ease; +} + +.mega-link-card:hover { + border-color: #3b82f6; + box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15); + transform: translateY(-2px); +} + +.mega-link-icon { + width: 2.5rem; + height: 2.5rem; + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + transition: all 0.2s ease; +} + +.nav-menu-btn.active { + background: rgba(255, 255, 255, 0.9); + color: #1e40af; +} + +.nav-menu-btn:hover { + background: rgba(255, 255, 255, 0.8); +} + +#main-nav { + position: relative; +} + +#mega-menu-container { + position: absolute; + top: 100%; + left: 0; + right: 0; + pointer-events: none; +} + +#mega-menu-container .mega-panel { + pointer-events: auto; +} + +.nav-menu-item { + position: relative; +} + +.nav-menu-item::after { + content: ''; + position: absolute; + bottom: -12px; + left: 0; + right: 0; + height: 12px; +} diff --git a/groups/Tapops/css/base-scoped.css b/groups/Tapops/css/base-scoped.css new file mode 100644 index 0000000..7316c29 --- /dev/null +++ b/groups/Tapops/css/base-scoped.css @@ -0,0 +1,123 @@ +/* =========================================== + Base / Reset — scoped under .tapops-doc-root + Prevents contaminating Tailwind nav/footer. + =========================================== */ + +.tapops-doc-root *, +.tapops-doc-root *::before, +.tapops-doc-root *::after { + box-sizing: border-box; +} + +/* Font is set on the root only (inherited), NOT on `*`. + A universal `.tapops-doc-root *` rule would tie Font Awesome's + `.fas` font-family in specificity and—loading later—override it, + blanking every icon. Inheritance loses to FA's explicit rule, so + icons render while normal text still gets Roboto. */ +.tapops-doc-root { + font-family: 'Roboto', sans-serif; + color: var(--text-dark); + line-height: 1.6; +} + +/* Form controls don't inherit font-family by default — opt them in. + These elements never hold Font Awesome glyphs, so this is icon-safe. */ +.tapops-doc-root input, +.tapops-doc-root select, +.tapops-doc-root textarea, +.tapops-doc-root button { + font-family: inherit; +} + +.tapops-doc-root a { + text-decoration: none; + color: inherit; +} + +.tapops-doc-root img { + display: block; +} + +/* --- Shared Section Styles --- */ + +.tapops-doc-root .section-header { + margin-bottom: 50px; +} + +.tapops-doc-root .section-tag { + font-size: 0.75rem; + font-weight: 800; + letter-spacing: 2px; + background-color: var(--secondary-gold); + color: var(--primary-blue); + display: inline-block; + padding: 6px 14px; + text-transform: uppercase; + margin-top: 10px; +} + +.tapops-doc-root .section-title { + font-size: 2.5rem; + font-weight: 800; + color: var(--primary-blue); + letter-spacing: -1px; + line-height: 1.15; +} + +/* --- Shared Button Styles --- */ + +.tapops-doc-root .btn-join, +.tapops-doc-root .btn-outline { + padding: 17px 30px; + font-weight: 800; + font-size: 0.9rem; + letter-spacing: 1px; + display: inline-flex; + align-items: center; + gap: 12px; + transition: all 0.3s ease; +} + +.tapops-doc-root .btn-join { + background-color: var(--secondary-gold); + color: var(--primary-blue); +} + +.tapops-doc-root .btn-outline { + border: 1px solid rgba(255, 255, 255, 0.18); + color: white; +} + +.tapops-doc-root .btn-join:hover, +.tapops-doc-root .btn-outline:hover { + transform: translateY(-2px); +} + +.tapops-doc-root .btn-join:hover { + background-color: #fff; + box-shadow: 0 15px 30px rgba(0, 0, 0, 0.25); +} + +.tapops-doc-root .btn-outline:hover { + border-color: var(--secondary-gold); + color: var(--secondary-gold); +} + +@media (max-width: 768px) { + .tapops-doc-root .btn-join, + .tapops-doc-root .btn-outline { + padding: 14px 24px; + font-size: 0.82rem; + width: 100%; + justify-content: center; + } +} + +@media (max-width: 480px) { + .tapops-doc-root .btn-join, + .tapops-doc-root .btn-outline { + padding: 12px 20px; + font-size: 0.78rem; + letter-spacing: 0.5px; + } +} diff --git a/groups/Tapops/css/documents.css b/groups/Tapops/css/documents.css new file mode 100644 index 0000000..b0e6e35 --- /dev/null +++ b/groups/Tapops/css/documents.css @@ -0,0 +1 @@ +@import url("repository/index.css"); diff --git a/groups/Tapops/css/hero.css b/groups/Tapops/css/hero.css new file mode 100644 index 0000000..0dd8092 --- /dev/null +++ b/groups/Tapops/css/hero.css @@ -0,0 +1,276 @@ +/* =========================================== + Hero Section (Centered & Immersive) — Tapops + =========================================== */ + +.research-hero.immersive-hero { + background: radial-gradient(circle at center, #003150 0%, #001e35 100%); + color: white; + padding: 160px 5% 180px; + position: relative; + overflow: hidden; + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + padding-bottom: 200px; +} + +.research-hero::before { + content: ''; + position: absolute; + width: 800px; + height: 800px; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + + background: conic-gradient(from 180deg at 50% 50%, + transparent 0deg, + rgba(254, 180, 1, 0.15) 180deg, + transparent 360deg); + + -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 2px), #fff 0); + mask: radial-gradient(farthest-side, transparent calc(100% - 2px), #fff 0); + + border-radius: 50%; + pointer-events: none; + z-index: 1; + animation: rotate-orbit 30s linear infinite; +} + +@keyframes rotate-orbit { + from { + transform: translate(-50%, -50%) rotate(0deg); + } + + to { + transform: translate(-50%, -50%) rotate(360deg); + } +} + +.hero-container-centered { + max-width: 900px; + position: relative; + z-index: 2; + width: 100%; + padding: 0 16px; +} + +.hero-kicker { + font-size: 0.85rem; + letter-spacing: 4px; + font-weight: 800; + color: var(--secondary-gold); + text-transform: uppercase; + margin-bottom: 24px; + display: inline-block; +} + +.hero-title { + font-size: 4.8rem; + font-weight: 800; + line-height: 1.05; + letter-spacing: -3px; + margin-bottom: 30px; +} + +.hero-tagline { + font-size: 1.25rem; + line-height: 1.6; + opacity: 0.85; + margin-bottom: 45px; +} + +.hero-actions { + display: flex; + justify-content: center; + gap: 20px; +} + +/* --- Floating Insight Bar --- */ + +.hero-floating-stat-bar { + position: absolute; + bottom: 50px; + left: 50%; + transform: translateX(-50%); + display: flex; + align-items: center; + background: rgba(255, 255, 255, 0.03); + backdrop-filter: blur(20px); + border: 1px solid rgba(255, 255, 255, 0.1); + padding: 25px 60px; + border-radius: 4px; + z-index: 3; + gap: 40px; + box-shadow: 0 30px 60px rgba(0, 0, 0, 0.3); + max-width: calc(100% - 32px); + box-sizing: border-box; +} + +.stat-bar-item { + display: flex; + flex-direction: column; + align-items: center; + white-space: nowrap; +} + +.stat-bar-value { + font-size: 2.2rem; + font-weight: 800; + color: var(--secondary-gold); + line-height: 1.1; +} + +.stat-bar-label { + font-size: 0.7rem; + letter-spacing: 1.5px; + text-transform: uppercase; + font-weight: 700; + opacity: 0.6; + margin-top: 4px; +} + +.stat-bar-divider { + width: 1px; + height: 40px; + background: rgba(255, 255, 255, 0.1); +} + +/* =========================================== + Hero Responsive + =========================================== */ + +@media (max-width: 992px) { + .research-hero.immersive-hero { + padding: 120px 5% 180px; + } + + .hero-title { + font-size: 3.5rem; + letter-spacing: -2px; + } + + .hero-tagline { + font-size: 1.1rem; + } + + .hero-floating-stat-bar { + padding: 20px 32px; + gap: 24px; + } + + .stat-bar-value { + font-size: 1.6rem; + } +} + +@media (max-width: 768px) { + .research-hero.immersive-hero { + padding: 100px 16px 130px; + } + + .hero-kicker { + font-size: 0.68rem; + letter-spacing: 2.5px; + margin-bottom: 16px; + } + + .hero-title { + font-size: 2.2rem; + letter-spacing: -1px; + margin-bottom: 18px; + } + + .hero-tagline { + font-size: 0.9rem; + line-height: 1.5; + margin-bottom: 28px; + } + + .hero-actions { + flex-direction: column; + align-items: stretch; + gap: 12px; + width: 100%; + max-width: 320px; + margin: 0 auto; + } + + .hero-floating-stat-bar { + width: 100%; + max-width: 320px; + padding: 18px 24px; + gap: 20px; + bottom: 30px; + } + + .stat-bar-value { + font-size: 1.3rem; + } + + .stat-bar-label { + font-size: 0.6rem; + letter-spacing: 1px; + } + + .stat-bar-divider { + height: 30px; + } + + .research-hero::before { + width: 400px; + height: 400px; + } +} + +@media (max-width: 480px) { + .research-hero.immersive-hero { + padding: 80px 12px 125px; + } + + .hero-kicker { + font-size: 0.6rem; + letter-spacing: 2px; + } + + .hero-title { + font-size: 1.8rem; + letter-spacing: -0.5px; + margin-bottom: 14px; + } + + .hero-tagline { + font-size: 0.82rem; + line-height: 1.45; + margin-bottom: 22px; + } + + .hero-actions { + max-width: 280px; + } + + .hero-floating-stat-bar { + flex-direction: row; + bottom: 20px; + gap: 16px; + padding: 14px 20px; + width: 100%; + max-width: 280px; + justify-content: space-evenly; + } + + .stat-bar-divider { + width: 1px; + height: 24px; + } + + .stat-bar-value { + font-size: 1.2rem; + } + + .research-hero::before { + width: 280px; + height: 280px; + } +} diff --git a/groups/Tapops/css/nav.css b/groups/Tapops/css/nav.css new file mode 100644 index 0000000..c68e1d3 --- /dev/null +++ b/groups/Tapops/css/nav.css @@ -0,0 +1,286 @@ +/* =========================================== + Navigation & Mega Menu Styles + Extracted from inline + + + + + + + + +
    + - - - - - - -
    -
    -
    - Tugas Halaman Statis -

    Berkas Penting

    -

    Kelompok 09 — TAPOPS  ·  Informatika UNTAN 2026

    -
    -
    - -

    Tulis konten halaman kelompok di sini.

    -

    Subjudul

    -

    Gunakan paragraf, list, tabel, gambar, dan elemen konten lainnya.

    - -
    -
    -
    - -
    -
    - -

    - Ikuti Kami

    -
    - - - - - - -
    - -
    - Logo UNTAN -
    -

    INFORMATIKA

    -

    - UNIVERSITAS TANJUNGPURA

    -
    -
    - -
    -

    - - Jl. Prof. Dr. H. Hadari Nawawi, Bansir Laut, Kec. Pontianak Tenggara, -

    -

    Kota Pontianak, Kalimantan Barat 78124

    -
    - -
    - E: info@informatika.untan.ac.id - - P: +62 851-7165-5161 - - F: +62(561) 739637 - - WA: +62 851-7165-5161 -
    - -
    - -
    - Footer Illustration -
    -
    - - - - - - + + + - \ No newline at end of file + diff --git a/groups/Tapops/js/app.js b/groups/Tapops/js/app.js new file mode 100644 index 0000000..b8370a2 --- /dev/null +++ b/groups/Tapops/js/app.js @@ -0,0 +1,28 @@ +import { CounterAnimation } from './components/counter-animation.js'; +import { AccessibilityToggleController } from './controllers/accessibility-toggle-controller.js'; +import { DocumentRepository } from './controllers/document-repository.js'; +import { MobileMenuController } from './controllers/mobile-menu-controller.js'; +import { NavigationController } from './controllers/navigation-controller.js'; +import { pageTemplate } from './templates/page-template.js'; + +class TapopsApp { + constructor(root = document.getElementById('tapops-page-root')) { + this.root = root; + } + + init() { + if (!this.root) return; + + this.root.innerHTML = pageTemplate(); + window.applyLang?.(); + window.applyTp?.(this.root); + + new NavigationController().init(); + new MobileMenuController().init(); + new AccessibilityToggleController().init(); + new CounterAnimation().init(); + new DocumentRepository().init(); + } +} + +new TapopsApp().init(); diff --git a/groups/Tapops/js/components/counter-animation.js b/groups/Tapops/js/components/counter-animation.js new file mode 100644 index 0000000..3639b4f --- /dev/null +++ b/groups/Tapops/js/components/counter-animation.js @@ -0,0 +1,44 @@ +export class CounterAnimation { + static DURATION_MS = 2000; + + init() { + const counters = document.querySelectorAll('.counter-value[data-target]'); + if (counters.length === 0) return; + + if ('IntersectionObserver' in window) { + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + this._animate(entry.target); + observer.unobserve(entry.target); + } + }); + }, { threshold: 0.2 }); + counters.forEach(c => observer.observe(c)); + } else { + counters.forEach(c => this._animate(c)); + } + } + + _animate(el) { + const target = Number.parseInt((el.dataset.target || '0').replace(/[^0-9]/g, ''), 10); + if (Number.isNaN(target)) return; + CounterAnimation.animateValue(el, target, el.dataset.suffix || ''); + } + + // Reusable easing tween from 0 → target. Callable for stats whose + // value is only known after an async fetch (no data-target needed). + static animateValue(el, target, suffix = '') { + if (!el || Number.isNaN(target)) return; + const startTime = performance.now(); + + const tick = (now) => { + const progress = Math.min((now - startTime) / CounterAnimation.DURATION_MS, 1); + const eased = 1 - Math.pow(1 - progress, 3); + el.textContent = Math.floor(eased * target) + suffix; + if (progress < 1) requestAnimationFrame(tick); + else el.textContent = target + suffix; + }; + requestAnimationFrame(tick); + } +} diff --git a/groups/Tapops/js/components/custom-dropdown.js b/groups/Tapops/js/components/custom-dropdown.js new file mode 100644 index 0000000..a896c36 --- /dev/null +++ b/groups/Tapops/js/components/custom-dropdown.js @@ -0,0 +1,175 @@ +export class CustomDropdown { + constructor({ container, options, defaultValue, onChange, prefixIcon }) { + this.container = container; + this.options = options; // Array of { value, text, i18nKey } + this.onChange = onChange; + this.currentValue = defaultValue || options[0]?.value; + this.prefixIcon = prefixIcon; // optional class name like 'fas fa-sort' + this.isOpen = false; + + this.elements = {}; + this._build(); + this._bindEvents(); + } + + _build() { + this.container.innerHTML = ''; + this.container.classList.add('custom-dropdown'); + + // Trigger Button + const trigger = document.createElement('button'); + trigger.className = 'custom-dropdown-trigger'; + trigger.setAttribute('aria-haspopup', 'listbox'); + trigger.setAttribute('aria-expanded', 'false'); + trigger.type = 'button'; + + const triggerLeft = document.createElement('div'); + triggerLeft.className = 'trigger-left-content'; + triggerLeft.style.display = 'flex'; + triggerLeft.style.alignItems = 'center'; + triggerLeft.style.gap = '10px'; + + if (this.prefixIcon) { + const icon = document.createElement('i'); + icon.className = this.prefixIcon; + triggerLeft.appendChild(icon); + } + + const labelSpan = document.createElement('span'); + labelSpan.className = 'trigger-label'; + + // Find default text + const defaultOpt = this.options.find(opt => opt.value === this.currentValue) || this.options[0]; + labelSpan.textContent = defaultOpt ? defaultOpt.text : ''; + if (defaultOpt?.i18nKey) { + labelSpan.setAttribute('data-tp-i18n', defaultOpt.i18nKey); + } + + triggerLeft.appendChild(labelSpan); + trigger.appendChild(triggerLeft); + + const arrowIcon = document.createElement('i'); + arrowIcon.className = 'fas fa-chevron-down trigger-arrow'; + + trigger.appendChild(arrowIcon); + this.container.appendChild(trigger); + + // Menu wrapper + const menu = document.createElement('div'); + menu.className = 'custom-dropdown-menu'; + menu.setAttribute('role', 'listbox'); + + // Populate options + this.options.forEach(opt => { + const item = document.createElement('div'); + item.className = 'custom-dropdown-item' + (opt.value === this.currentValue ? ' active' : ''); + item.setAttribute('role', 'option'); + item.setAttribute('data-value', opt.value); + + const itemText = document.createElement('span'); + itemText.textContent = opt.text; + if (opt.i18nKey) { + itemText.setAttribute('data-tp-i18n', opt.i18nKey); + } + + item.appendChild(itemText); + menu.appendChild(item); + }); + + this.container.appendChild(menu); + + this.elements = { trigger, labelSpan, menu }; + } + + _bindEvents() { + // Toggle dropdown on trigger click + this.elements.trigger.addEventListener('click', (e) => { + e.stopPropagation(); + this.toggle(); + }); + + // Handle item selection + this.elements.menu.addEventListener('click', (e) => { + const item = e.target.closest('.custom-dropdown-item'); + if (!item) return; + + const value = item.getAttribute('data-value'); + this.select(value); + this.close(); + }); + + // Close when clicking outside + document.addEventListener('click', () => { + this.close(); + }); + + // Close on escape key + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape') this.close(); + }); + } + + toggle() { + if (this.isOpen) { + this.close(); + } else { + this.open(); + } + } + + open() { + // Close other custom dropdowns + document.querySelectorAll('.custom-dropdown.active').forEach(dropdown => { + if (dropdown !== this.container) { + dropdown.classList.remove('active'); + dropdown.querySelector('.custom-dropdown-trigger')?.setAttribute('aria-expanded', 'false'); + } + }); + + this.isOpen = true; + this.container.classList.add('active'); + this.elements.trigger.setAttribute('aria-expanded', 'true'); + } + + close() { + this.isOpen = false; + this.container.classList.remove('active'); + this.elements.trigger.setAttribute('aria-expanded', 'false'); + } + + select(value) { + if (value === this.currentValue) return; + + this.currentValue = value; + + // Update active class in menu items + this.elements.menu.querySelectorAll('.custom-dropdown-item').forEach(item => { + const isCurrent = item.getAttribute('data-value') === value; + item.classList.toggle('active', isCurrent); + }); + + // Update trigger label + const selectedOpt = this.options.find(opt => opt.value === value); + if (selectedOpt) { + this.elements.labelSpan.textContent = selectedOpt.text; + if (selectedOpt.i18nKey) { + this.elements.labelSpan.setAttribute('data-tp-i18n', selectedOpt.i18nKey); + } else { + this.elements.labelSpan.removeAttribute('data-tp-i18n'); + } + } + + // Trigger callback + if (typeof this.onChange === 'function') { + this.onChange(value); + } + } + + setValue(value) { + this.select(value); + } + + getValue() { + return this.currentValue; + } +} diff --git a/groups/Tapops/js/components/date-range-picker.js b/groups/Tapops/js/components/date-range-picker.js new file mode 100644 index 0000000..2a9902d --- /dev/null +++ b/groups/Tapops/js/components/date-range-picker.js @@ -0,0 +1,663 @@ +export class DateRangePicker { + constructor({ containerId, onChange, initialStartDate = '', initialEndDate = '' }) { + this.container = document.getElementById(containerId); + if (!this.container) return; + + this.trigger = this.container.querySelector('#date-picker-trigger'); + this.popover = this.container.querySelector('#date-picker-popover'); + this.backdrop = this.container.querySelector('#date-picker-backdrop'); + this.label = this.container.querySelector('#date-picker-label'); + + this.startDisplay = this.container.querySelector('#picker-start-display'); + this.endDisplay = this.container.querySelector('#picker-end-display'); + + this.prevBtn = this.container.querySelector('#prev-month-btn'); + this.nextBtn = this.container.querySelector('#next-month-btn'); + this.leftMonthLabel = this.container.querySelector('#left-month-label'); + this.rightMonthLabel = this.container.querySelector('#right-month-label'); + + this.leftMonthName = this.container.querySelector('#left-month-name'); + this.rightMonthName = this.container.querySelector('#right-month-name'); + this.leftYearSelect = this.container.querySelector('#left-year-select'); + this.rightYearSelect = this.container.querySelector('#right-year-select'); + + this.leftPane = this.container.querySelector('#left-calendar-pane'); + this.rightPane = this.container.querySelector('#right-calendar-pane'); + + this.leftDaysContainer = this.container.querySelector('#left-days-container'); + this.rightDaysContainer = this.container.querySelector('#right-days-container'); + + this.cancelBtn = this.container.querySelector('#picker-cancel-btn'); + this.applyBtn = this.container.querySelector('#picker-apply-btn'); + + this.onChange = onChange; + + // Active dates (saved) + this.startDate = initialStartDate ? new Date(initialStartDate) : null; + this.endDate = initialEndDate ? new Date(initialEndDate) : null; + + // Temporary dates (being edited) + this.tempStartDate = this.startDate ? new Date(this.startDate) : null; + this.tempEndDate = this.endDate ? new Date(this.endDate) : null; + + // Calendar view month/year + const today = new Date(); + if (this.endDate) { + this.currentLeftMonth = this.endDate.getMonth(); + this.currentLeftYear = this.endDate.getFullYear(); + } else { + this.currentLeftMonth = today.getMonth(); + this.currentLeftYear = today.getFullYear(); + } + + this.isOpen = false; + this._init(); + } + + _init() { + this._populateYearSelects(); + this._bindEvents(); + this._bindDisplayInputs(); + this._updateTriggerLabel(); + + // Listen to language changes + document.addEventListener('languageChanged', () => { + if (this.isOpen) { + this._renderCalendars(); + } + this._updateTriggerLabel(); + }); + } + + _isEnglish() { + if (typeof window.getLang === 'function') { + const l = window.getLang(); + if (l === 'id' || l === 'en') return l === 'en'; + } + try { + const saved = localStorage.getItem('if_untan_lang'); + if (saved === 'id' || saved === 'en') return saved === 'en'; + } catch (e) {} + const nav = (navigator.language || 'id').toLowerCase(); + return nav.startsWith('en'); + } + + _clearTime(date) { + if (!date) return null; + const d = new Date(date); + d.setHours(0, 0, 0, 0); + return d; + } + + _datesEqual(d1, d2) { + if (!d1 || !d2) return false; + return d1.getFullYear() === d2.getFullYear() && + d1.getMonth() === d2.getMonth() && + d1.getDate() === d2.getDate(); + } + + _getRightMonthYear() { + let m = this.currentLeftMonth + 1; + let y = this.currentLeftYear; + if (m > 11) { + m = 0; + y += 1; + } + return { month: m, year: y }; + } + + _navigateMonths(direction) { + this.currentLeftMonth += direction; + if (this.currentLeftMonth > 11) { + this.currentLeftMonth = 0; + this.currentLeftYear += 1; + } else if (this.currentLeftMonth < 0) { + this.currentLeftMonth = 11; + this.currentLeftYear -= 1; + } + this._renderCalendars(); + } + + _renderCalendars() { + const isEn = this._isEnglish(); + const monthNames = isEn ? + ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] : + ['Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni', 'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember']; + + const dayHeaders = isEn ? + ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] : + ['Sen', 'Sel', 'Rab', 'Kam', 'Jum', 'Sab', 'Min']; + + if (this.leftMonthName) { + this.leftMonthName.textContent = monthNames[this.currentLeftMonth]; + } + if (this.leftYearSelect) { + this.leftYearSelect.value = this.currentLeftYear; + } + + const rightMY = this._getRightMonthYear(); + if (this.rightMonthName) { + this.rightMonthName.textContent = monthNames[rightMY.month]; + } + if (this.rightYearSelect) { + this.rightYearSelect.value = rightMY.year; + } + + const drawHeaders = (pane) => { + if (!pane) return; + const dayHeadersEl = pane.querySelector('.day-headers'); + if (!dayHeadersEl) return; + dayHeadersEl.innerHTML = ''; + dayHeaders.forEach(day => { + const span = document.createElement('span'); + span.textContent = day; + dayHeadersEl.appendChild(span); + }); + }; + + drawHeaders(this.leftPane); + drawHeaders(this.rightPane); + + if (this.leftDaysContainer) { + this._populateDaysContainer(this.leftDaysContainer, this.currentLeftMonth, this.currentLeftYear); + } + if (this.rightDaysContainer) { + this._populateDaysContainer(this.rightDaysContainer, rightMY.month, rightMY.year); + } + + this._updateDisplayInputs(); + this._updatePresetSelectionVisuals(); + } + + _populateDaysContainer(container, month, year) { + container.innerHTML = ''; + + const firstDay = new Date(year, month, 1); + const numDays = new Date(year, month + 1, 0).getDate(); + + let offset = (firstDay.getDay() + 6) % 7; + + for (let i = 0; i < offset; i++) { + const cell = document.createElement('div'); + cell.className = 'day-cell empty'; + container.appendChild(cell); + } + + const today = new Date(); + today.setHours(23, 59, 59, 999); + + for (let day = 1; day <= numDays; day++) { + const cell = document.createElement('div'); + cell.className = 'day-cell'; + cell.textContent = day; + + const cellDate = new Date(year, month, day); + cellDate.setHours(0, 0, 0, 0); + + if (cellDate > today) { + cell.classList.add('disabled'); + } else { + if (this.tempStartDate && this.tempEndDate) { + const start = this._clearTime(this.tempStartDate); + const end = this._clearTime(this.tempEndDate); + + if (this._datesEqual(cellDate, start)) { + cell.classList.add('range-start'); + } + if (this._datesEqual(cellDate, end)) { + cell.classList.add('range-end'); + } + if (cellDate > start && cellDate < end) { + cell.classList.add('range-between'); + } + } else if (this.tempStartDate && this._datesEqual(cellDate, this._clearTime(this.tempStartDate))) { + cell.classList.add('range-start'); + cell.classList.add('range-end'); + } + + cell.addEventListener('click', () => { + this._handleDayClick(cellDate); + }); + } + + container.appendChild(cell); + } + } + + _handleDayClick(date) { + if (!this.tempStartDate || (this.tempStartDate && this.tempEndDate)) { + this.tempStartDate = new Date(date); + this.tempEndDate = null; + } else { + if (date < this.tempStartDate) { + this.tempStartDate = new Date(date); + this.tempEndDate = null; + } else { + this.tempEndDate = new Date(date); + } + } + this._renderCalendars(); + } + + _formatDateDisplay(date) { + if (!date) return ''; + const d = new Date(date); + const dd = String(d.getDate()).padStart(2, '0'); + const mm = String(d.getMonth() + 1).padStart(2, '0'); + const yyyy = d.getFullYear(); + return `${dd}/${mm}/${yyyy}`; + } + + _updateDisplayInputs() { + if (this.startDisplay) { + this.startDisplay.value = this._formatDateDisplay(this.tempStartDate); + } + if (this.endDisplay) { + this.endDisplay.value = this._formatDateDisplay(this.tempEndDate); + } + } + + _calculatePreset(presetName) { + const today = new Date(); + today.setHours(0, 0, 0, 0); + + let start = null; + let end = null; + + switch (presetName) { + case 'today': + start = new Date(today); + end = new Date(today); + break; + case 'yesterday': + start = new Date(today); + start.setDate(today.getDate() - 1); + end = new Date(start); + break; + case 'last7': + end = new Date(today); + start = new Date(today); + start.setDate(today.getDate() - 6); + break; + case 'last14': + end = new Date(today); + start = new Date(today); + start.setDate(today.getDate() - 13); + break; + case 'last30': + end = new Date(today); + start = new Date(today); + start.setDate(today.getDate() - 29); + break; + case 'thisWeek': + end = new Date(today); + start = new Date(today); + const day = today.getDay(); + const diff = today.getDate() - day + (day === 0 ? -6 : 1); + start.setDate(diff); + break; + case 'lastWeek': + const temp = new Date(today); + const tempDay = temp.getDay(); + const tempDiff = temp.getDate() - tempDay + (tempDay === 0 ? -6 : 1); + start = new Date(temp); + start.setDate(tempDiff - 7); + end = new Date(temp); + end.setDate(tempDiff - 1); + break; + case 'thisMonth': + start = new Date(today.getFullYear(), today.getMonth(), 1); + end = new Date(today); + break; + case 'lastMonth': + start = new Date(today.getFullYear(), today.getMonth() - 1, 1); + end = new Date(today.getFullYear(), today.getMonth(), 0); + break; + case 'all': + default: + start = null; + end = null; + break; + } + return { start, end }; + } + + _handlePresetClick(presetName) { + const { start, end } = this._calculatePreset(presetName); + this.tempStartDate = start; + this.tempEndDate = end; + + const today = new Date(); + if (this.tempEndDate) { + this.currentLeftMonth = this.tempEndDate.getMonth(); + this.currentLeftYear = this.tempEndDate.getFullYear(); + } else { + this.currentLeftMonth = today.getMonth(); + this.currentLeftYear = today.getFullYear(); + } + this._renderCalendars(); + } + + _updatePresetSelectionVisuals() { + this.container.querySelectorAll('.preset-btn').forEach(btn => { + btn.classList.remove('active'); + }); + + if (!this.tempStartDate && !this.tempEndDate) { + this.container.querySelector('[data-preset="all"]')?.classList.add('active'); + return; + } + + if (this.tempStartDate && this.tempEndDate) { + const activePreset = ['today', 'yesterday', 'last7', 'last14', 'last30', 'thisWeek', 'lastWeek', 'thisMonth', 'lastMonth'].find(preset => { + const { start, end } = this._calculatePreset(preset); + return start && end && + this._datesEqual(this.tempStartDate, start) && + this._datesEqual(this.tempEndDate, end); + }); + + if (activePreset) { + this.container.querySelector(`[data-preset="${activePreset}"]`)?.classList.add('active'); + } + } + } + + _updateTriggerLabel() { + if (!this.startDate && !this.endDate) { + if (this.label) { + this.label.textContent = window.tpT?.('filter_all_time') || 'Semua Waktu'; + this.label.setAttribute('data-tp-i18n', 'filter_all_time'); + } + } else { + if (this.label) { + this.label.removeAttribute('data-tp-i18n'); + const startFormatted = this._formatDateDisplay(this.startDate); + const endFormatted = this._formatDateDisplay(this.endDate); + if (this._datesEqual(this.startDate, this.endDate)) { + this.label.textContent = startFormatted; + } else { + this.label.textContent = `${startFormatted} - ${endFormatted}`; + } + } + } + } + + _bindEvents() { + this.trigger.addEventListener('click', (e) => { + e.stopPropagation(); + this.toggle(); + }); + + this.popover.addEventListener('click', (e) => { + e.stopPropagation(); + }); + + if (this.prevBtn) { + this.prevBtn.addEventListener('click', (e) => { + e.stopPropagation(); + this._navigateMonths(-1); + }); + } + if (this.nextBtn) { + this.nextBtn.addEventListener('click', (e) => { + e.stopPropagation(); + this._navigateMonths(1); + }); + } + + const presetsList = this.container.querySelector('.presets-list'); + if (presetsList) { + presetsList.addEventListener('click', (e) => { + const btn = e.target.closest('.preset-btn'); + if (!btn) return; + e.stopPropagation(); + const preset = btn.getAttribute('data-preset'); + this._handlePresetClick(preset); + }); + } + + if (this.cancelBtn) { + this.cancelBtn.addEventListener('click', (e) => { + e.stopPropagation(); + this._handleCancel(); + }); + } + + if (this.applyBtn) { + this.applyBtn.addEventListener('click', (e) => { + e.stopPropagation(); + this._handleApply(); + }); + } + + if (this.backdrop) { + this.backdrop.addEventListener('click', (e) => { + e.stopPropagation(); + this._handleCancel(); + }); + } + + document.addEventListener('click', () => { + if (this.isOpen) { + this._handleCancel(); + } + }); + + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && this.isOpen) { + this._handleCancel(); + } + }); + } + + _bindDisplayInputs() { + const parseDate = (val) => { + const m = val.trim().match(/^(\d{1,2})[\/\-.](\d{1,2})[\/\-.](\d{4})$/); + if (!m) return null; + const d = parseInt(m[1], 10); + const mon = parseInt(m[2], 10) - 1; + const y = parseInt(m[3], 10); + + if (mon < 0 || mon > 11 || d < 1 || d > 31 || y < 1000 || y > 9999) return null; + + const testDate = new Date(y, mon, d); + if (testDate.getFullYear() !== y || testDate.getMonth() !== mon || testDate.getDate() !== d) { + return null; + } + return testDate; + }; + + const setupInput = (input, isStart) => { + if (!input) return; + + input.addEventListener('keydown', (e) => { + const CTRL_KEYS = new Set(['Backspace', 'Delete', 'Tab', 'ArrowLeft', 'ArrowRight', 'Home', 'End', 'Enter']); + if (!/^\d$/.test(e.key) && e.key !== '/' && e.key !== '-' && e.key !== '.' && !CTRL_KEYS.has(e.key)) { + e.preventDefault(); + } + }); + + input.addEventListener('input', (e) => { + if (e.inputType && e.inputType.startsWith('delete')) { + return; + } + + let val = input.value.replace(/[^\d]/g, ''); + let formatted = val; + if (val.length > 2) { + formatted = val.substring(0, 2) + '/' + val.substring(2); + } + if (val.length > 4) { + formatted = val.substring(0, 2) + '/' + val.substring(2, 4) + '/' + val.substring(4, 8); + } + input.value = formatted; + + const parsed = parseDate(formatted); + if (parsed) { + const today = new Date(); + today.setHours(23, 59, 59, 999); + if (parsed <= today) { + if (isStart) { + this.tempStartDate = parsed; + if (this.tempEndDate && this.tempEndDate < this.tempStartDate) { + this.tempEndDate = new Date(this.tempStartDate); + } + } else { + this.tempEndDate = parsed; + if (this.tempStartDate && this.tempStartDate > this.tempEndDate) { + this.tempStartDate = new Date(this.tempEndDate); + } + } + + this.currentLeftMonth = parsed.getMonth(); + this.currentLeftYear = parsed.getFullYear(); + + const rightMY = this._getRightMonthYear(); + const isEn = this._isEnglish(); + const monthNames = isEn ? + ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] : + ['Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni', 'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember']; + if (this.leftMonthName) this.leftMonthName.textContent = monthNames[this.currentLeftMonth]; + if (this.leftYearSelect) this.leftYearSelect.value = this.currentLeftYear; + if (this.rightMonthName) this.rightMonthName.textContent = monthNames[rightMY.month]; + if (this.rightYearSelect) this.rightYearSelect.value = rightMY.year; + if (this.leftDaysContainer) this._populateDaysContainer(this.leftDaysContainer, this.currentLeftMonth, this.currentLeftYear); + if (this.rightDaysContainer) this._populateDaysContainer(this.rightDaysContainer, rightMY.month, rightMY.year); + this._updatePresetSelectionVisuals(); + } + } + }); + + input.addEventListener('blur', () => { + const parsed = parseDate(input.value); + if (!parsed) { + if (isStart) { + input.value = this._formatDateDisplay(this.tempStartDate); + } else { + input.value = this._formatDateDisplay(this.tempEndDate); + } + } + }); + }; + + setupInput(this.startDisplay, true); + setupInput(this.endDisplay, false); + } + + _populateYearSelects() { + if (!this.leftYearSelect || !this.rightYearSelect) return; + + this.leftYearSelect.innerHTML = ''; + this.rightYearSelect.innerHTML = ''; + + const startYear = 2020; + const endYear = new Date().getFullYear() + 4; + + for (let y = startYear; y <= endYear; y++) { + const optLeft = document.createElement('option'); + optLeft.value = y; + optLeft.textContent = y; + this.leftYearSelect.appendChild(optLeft); + + const optRight = document.createElement('option'); + optRight.value = y; + optRight.textContent = y; + this.rightYearSelect.appendChild(optRight); + } + + this.leftYearSelect.addEventListener('change', () => { + this._handleYearChange(parseInt(this.leftYearSelect.value, 10), true); + }); + + this.rightYearSelect.addEventListener('change', () => { + this._handleYearChange(parseInt(this.rightYearSelect.value, 10), false); + }); + } + + _handleYearChange(year, isLeft) { + if (isLeft) { + this.currentLeftYear = year; + } else { + const rightMY = this._getRightMonthYear(); + const rightMonth = rightMY.month; + if (rightMonth === 0) { + this.currentLeftYear = year - 1; + } else { + this.currentLeftYear = year; + } + } + this._renderCalendars(); + } + + toggle() { + if (this.isOpen) { + this.close(); + } else { + this.open(); + } + } + + open() { + document.querySelectorAll('.custom-dropdown.active').forEach(dd => { + dd.classList.remove('active'); + dd.querySelector('.custom-dropdown-trigger')?.setAttribute('aria-expanded', 'false'); + }); + + this.isOpen = true; + this.container.classList.add('active'); + this.trigger.setAttribute('aria-expanded', 'true'); + + this.tempStartDate = this.startDate ? new Date(this.startDate) : null; + this.tempEndDate = this.endDate ? new Date(this.endDate) : null; + + const today = new Date(); + if (this.endDate) { + this.currentLeftMonth = this.endDate.getMonth(); + this.currentLeftYear = this.endDate.getFullYear(); + } else { + this.currentLeftMonth = today.getMonth(); + this.currentLeftYear = today.getFullYear(); + } + + this._renderCalendars(); + + if (window.innerWidth <= 768) { + document.body.classList.add('date-picker-open'); + } + } + + close() { + this.isOpen = false; + this.container.classList.remove('active'); + this.trigger.setAttribute('aria-expanded', 'false'); + document.body.classList.remove('date-picker-open'); + } + + _handleCancel() { + this.tempStartDate = this.startDate ? new Date(this.startDate) : null; + this.tempEndDate = this.endDate ? new Date(this.endDate) : null; + this.close(); + } + + _handleApply() { + this.startDate = this.tempStartDate; + this.endDate = this.tempEndDate; + + this._updateTriggerLabel(); + this.close(); + + const startStr = this._getDateString(this.startDate); + const endStr = this._getDateString(this.endDate); + if (this.onChange) { + this.onChange(startStr, endStr); + } + } + + _getDateString(date) { + if (!date) return ''; + const y = date.getFullYear(); + const m = String(date.getMonth() + 1).padStart(2, '0'); + const d = String(date.getDate()).padStart(2, '0'); + return `${y}-${m}-${d}`; + } +} diff --git a/groups/Tapops/js/components/document-card-view.js b/groups/Tapops/js/components/document-card-view.js new file mode 100644 index 0000000..25e2901 --- /dev/null +++ b/groups/Tapops/js/components/document-card-view.js @@ -0,0 +1,74 @@ +import { DirectusClient } from '../services/directus-client.js'; +import { FileDownloader } from '../services/file-downloader.js'; +import { DocumentFormatter } from '../utils/document-formatter.js'; + +export class DocumentCardView { + constructor(item) { + this.item = item; + this.title = item.nama_berkas; + this.tags = DocumentFormatter.parseTags(item.tag); + this.category = DocumentFormatter.categoryFromTags(this.tags); + this.fileInfo = DocumentFormatter.fileInfoFromItem(item); + this.fileType = this.fileInfo.type; + this.assetUrl = DirectusClient.assetUrl(item.file); + this.viewerUrl = DirectusClient.viewerUrl(item); + this.downloadName = DocumentFormatter.downloadFilenameFromItem(item); + this.dateIso = item.tanggal_upload || ''; + this.dateKey = this.dateIso ? this.dateIso.substring(0, 10) : ''; + } + + render() { + const card = document.createElement('article'); + card.className = 'document-card'; + card.dataset.date = this.dateKey; + + const typeUpper = this.fileType.toUpperCase(); + const typeClass = this.fileInfo.css; + const iconClass = this.fileInfo.icon; + const formattedDate = DocumentFormatter.formatDate(this.dateIso); + + const safeTitle = DocumentFormatter.escapeHtml(this.title); + const safeViewerUrl = DocumentFormatter.escapeAttr(this.viewerUrl); + const safeAssetUrl = DocumentFormatter.escapeAttr(this.assetUrl); + + // Build tag badges HTML. + const tagBadgesHtml = this.tags.length + ? `
    ${this.tags.map(t => `${DocumentFormatter.escapeHtml(DocumentFormatter.formatTag(t))}`).join('')}
    ` + : ''; + + card.innerHTML = ` +
    +

    ${safeTitle}

    +
    + ${typeUpper} + ${formattedDate} +
    + ${tagBadgesHtml} + + `; + + card.querySelector('.btn-action-outline').addEventListener('click', (e) => { + e.preventDefault(); + FileDownloader.download(this.assetUrl, this.downloadName); + }); + + // Prefetch the asset when the user hovers over the card so the + // browser cache already has the file by the time the viewer loads. + let prefetched = false; + card.addEventListener('mouseenter', () => { + if (prefetched) return; + prefetched = true; + const link = document.createElement('link'); + link.rel = 'prefetch'; + link.as = 'fetch'; + link.crossOrigin = 'anonymous'; + link.href = this.assetUrl; + document.head.appendChild(link); + }, { once: true }); + + return card; + } +} diff --git a/groups/Tapops/js/controllers/accessibility-toggle-controller.js b/groups/Tapops/js/controllers/accessibility-toggle-controller.js new file mode 100644 index 0000000..e2209c3 --- /dev/null +++ b/groups/Tapops/js/controllers/accessibility-toggle-controller.js @@ -0,0 +1,22 @@ +export class AccessibilityToggleController { + constructor({ + triggerSelector = '[data-accessibility-toggle]', + panelId = 'access-panel', + } = {}) { + this.triggers = Array.from(document.querySelectorAll(triggerSelector)); + this.panel = document.getElementById(panelId); + } + + init() { + if (this.triggers.length === 0) return; + this.triggers.forEach(trigger => { + trigger.addEventListener('click', () => this.toggle()); + }); + } + + toggle() { + if (!this.panel) return; + this.panel.classList.toggle('hidden'); + this.panel.classList.toggle('flex'); + } +} diff --git a/groups/Tapops/js/controllers/document-repository.js b/groups/Tapops/js/controllers/document-repository.js new file mode 100644 index 0000000..9d7f851 --- /dev/null +++ b/groups/Tapops/js/controllers/document-repository.js @@ -0,0 +1,466 @@ +import { DirectusClient } from '../services/directus-client.js'; +import { DocumentCardView } from '../components/document-card-view.js'; +import { CustomDropdown } from '../components/custom-dropdown.js'; +import { DateRangePicker } from '../components/date-range-picker.js'; +import { CounterAnimation } from '../components/counter-animation.js'; + +export class DocumentRepository { + static PAGE_SIZE = 6; + + constructor() { + this.gridContainer = document.getElementById('document-grid'); + this.filterWrapper = document.getElementById('tag-filter-wrapper'); + this.mobileFilterWrapper = document.getElementById('mobile-tag-filter-wrapper'); + this.searchInput = document.getElementById('doc-search'); + this.sortFilterWrapper = document.getElementById('sort-filter-wrapper'); + + // Hero stat values (filled from real API data after fetch). + this.statActiveEl = document.getElementById('stat-active-value'); + this.statUpdatesEl = document.getElementById('stat-updates-value'); + + this.cards = []; + this.paginationContainer = null; + this.state = { + filter: 'all', // 'all' or a specific tag string + search: '', + startDate: '', + endDate: '', + sort: 'newest', + page: 1, + }; + } + + + async init() { + if (!this.gridContainer) return; + this._bindControls(); + this._initPagination(); + try { + const items = await DirectusClient.fetchPublished('berkas'); + this._renderCards(items); + this._buildCategoryFilters(items); + this._renderStats(items); + this._applySort(); + this._applyFilters(); + } catch (err) { + console.error(err); + this._renderError(); + this._renderStatsError(); + } + } + + _initPagination() { + const pag = document.createElement('div'); + pag.id = 'doc-pagination'; + pag.className = 'doc-pagination'; + this.gridContainer.insertAdjacentElement('afterend', pag); + this.paginationContainer = pag; + } + + // Days within which an upload counts as a "recent update". + static RECENT_DAYS = 30; + + // Populate the hero stats from real data: + // Dokumen Aktif → number of published documents + // Pembaruan Terbaru → uploads within the last RECENT_DAYS days + _renderStats(items) { + const active = items.length; + + const windowMs = DocumentRepository.RECENT_DAYS * 24 * 60 * 60 * 1000; + const now = Date.now(); + const recent = items.filter(item => { + if (!item.tanggal_upload) return false; + const t = new Date(item.tanggal_upload).getTime(); + return !Number.isNaN(t) && now - t <= windowMs; + }).length; + + CounterAnimation.animateValue(this.statActiveEl, active); + CounterAnimation.animateValue(this.statUpdatesEl, recent); + } + + _renderStatsError() { + if (this.statActiveEl) this.statActiveEl.textContent = '—'; + if (this.statUpdatesEl) this.statUpdatesEl.textContent = '—'; + } + /** + * Fixed category definitions for the sidebar filter. + * Each has a key, an i18n key for bilingual labels, and a fallback label. + */ + static CATEGORIES = [ + { key: 'all', i18n: 'filter_all', label: 'Semua Berkas' }, + { key: 'skripsi', i18n: 'filter_skripsi', label: 'Panduan Skripsi' }, + { key: 'akademik', i18n: 'filter_akademik', label: 'Kalender Akademik' }, + { key: 'praktik', i18n: 'filter_praktik', label: 'Kerja Praktik' }, + { key: 'umum', i18n: 'filter_umum', label: 'Formulir Umum' }, + ]; + + /** + * Build category filter controls: + * – Desktop: sidebar buttons (hidden on mobile via CSS) + * – Mobile: a + + + +
    +
    + Periode Unggahan + + +
    + +
    +
    +
    +
    + +
    + - +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    + Mei +
    + + +
    +
    +
    + Juni +
    + + +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    + RENTANG WAKTU +
    + + + + + + + + + + +
    +
    +
    + + +
    +
    + +
    + Urutkan +
    +
    + +
    + Kategori Berkas +
    +
    +
    + + + +
    +
    + + Menghubungkan ke repositori berkas... +
    +
    + + + + + +
    +
    +
    +

    Tata Cara Pengajuan Berkas

    + +
    + +
    + +
    +
    +
    +
    01
    +
    +
    +
    +

    Unduh Formulir

    +

    Pilih kategori pengajuan Anda, lalu unduh formulir versi terbaru. Pastikan kode revisi pada formulir sesuai dengan yang tertera di halaman ini.

    +
    +
    + + +
    +
    +
    +
    02
    +
    +
    +
    +

    Lengkapi Formulir

    +

    Isi setiap kolom dengan lengkap dan teliti. Sertakan seluruh dokumen pendukung yang diminta sebelum pengiriman.

    +
    +
    + + +
    +
    +
    +
    03
    +
    +
    +

    Kirim Berkas

    +

    Kirimkan formulir beserta dokumen pendukung melalui email resmi atau WhatsApp admin prodi. Berkas yang lengkap akan langsung diproses oleh admin prodi.

    +
    +
    +
    +
    +
    + + +
    +
    +
    +

    Butuh Bantuan Lebih Lanjut?

    +

    Tim admin kami siap membantu Anda melalui kanal komunikasi resmi di bawah ini.

    +
    + +
    +
    +
    +
    +

    Korespondensi Resmi (Email)

    +

    Gunakan untuk permohonan form khusus non-publik atau administrasi formal lainnya.

    +
      +
    • Respon Maks. 24 Jam
    • +
    • Pelayanan Formal
    • +
    + HUBUNGI ADMIN +
    +
    + +
    +
    +
    +

    Quick Support (WhatsApp)

    +

    Konsultasi cepat alur birokrasi atau pelaporan kendala akses pada berkas repositori.

    +
      +
    • Respon Instan Jam Kerja
    • +
    • Konsultasi Teknis
    • +
    + CHAT WHATSAPP +
    +
    +
    +
    +
    + +
    `; +} diff --git a/groups/Tapops/js/templates/navigation-template.js b/groups/Tapops/js/templates/navigation-template.js new file mode 100644 index 0000000..af23dbd --- /dev/null +++ b/groups/Tapops/js/templates/navigation-template.js @@ -0,0 +1,843 @@ +export function navigationTemplate() { + return ` + + + + `; +} diff --git a/groups/Tapops/js/templates/page-template.js b/groups/Tapops/js/templates/page-template.js new file mode 100644 index 0000000..95d6ddf --- /dev/null +++ b/groups/Tapops/js/templates/page-template.js @@ -0,0 +1,7 @@ +import { navigationTemplate } from './navigation-template.js'; +import { mainContentTemplate } from './main-content-template.js'; +import { footerTemplate } from './footer-template.js'; + +export function pageTemplate() { + return [navigationTemplate(), mainContentTemplate(), footerTemplate()].join(''); +} diff --git a/groups/Tapops/js/utils/date-range-calculator.js b/groups/Tapops/js/utils/date-range-calculator.js new file mode 100644 index 0000000..7e532bd --- /dev/null +++ b/groups/Tapops/js/utils/date-range-calculator.js @@ -0,0 +1,112 @@ +export class DateRangeCalculator { + static clearTime(date) { + if (!date) return null; + const d = new Date(date); + d.setHours(0, 0, 0, 0); + return d; + } + + static datesEqual(left, right) { + if (!left || !right) return false; + return left.getFullYear() === right.getFullYear() + && left.getMonth() === right.getMonth() + && left.getDate() === right.getDate(); + } + + static calculatePreset(presetName, todayInput = new Date()) { + const today = this.clearTime(todayInput); + let start = null; + let end = null; + + switch (presetName) { + case 'today': + start = new Date(today); + end = new Date(today); + break; + case 'yesterday': + start = new Date(today); + start.setDate(today.getDate() - 1); + end = new Date(start); + break; + case 'last7': + end = new Date(today); + start = new Date(today); + start.setDate(today.getDate() - 6); + break; + case 'last14': + end = new Date(today); + start = new Date(today); + start.setDate(today.getDate() - 13); + break; + case 'last30': + end = new Date(today); + start = new Date(today); + start.setDate(today.getDate() - 29); + break; + case 'thisWeek': { + end = new Date(today); + start = new Date(today); + const day = today.getDay(); + const diff = today.getDate() - day + (day === 0 ? -6 : 1); + start.setDate(diff); + break; + } + case 'lastWeek': { + const temp = new Date(today); + const day = temp.getDay(); + const diff = temp.getDate() - day + (day === 0 ? -6 : 1); + start = new Date(temp); + start.setDate(diff - 7); + end = new Date(temp); + end.setDate(diff - 1); + break; + } + case 'thisMonth': + start = new Date(today.getFullYear(), today.getMonth(), 1); + end = new Date(today); + break; + case 'lastMonth': + start = new Date(today.getFullYear(), today.getMonth() - 1, 1); + end = new Date(today.getFullYear(), today.getMonth(), 0); + break; + case 'all': + default: + start = null; + end = null; + break; + } + + return { start, end }; + } + + static formatDisplay(date) { + if (!date) return ''; + const d = new Date(date); + const day = String(d.getDate()).padStart(2, '0'); + const month = String(d.getMonth() + 1).padStart(2, '0'); + return `${day}/${month}/${d.getFullYear()}`; + } + + static parseDisplayDate(value) { + const match = String(value || '').trim().match(/^(\d{1,2})[\/\-.](\d{1,2})[\/\-.](\d{4})$/); + if (!match) return null; + + const day = Number.parseInt(match[1], 10); + const month = Number.parseInt(match[2], 10) - 1; + const year = Number.parseInt(match[3], 10); + if (month < 0 || month > 11 || day < 1 || day > 31 || year < 1000 || year > 9999) return null; + + const date = new Date(year, month, day); + if (date.getFullYear() !== year || date.getMonth() !== month || date.getDate() !== day) return null; + return date; + } + + static toDateKey(date) { + if (!date) return ''; + const d = new Date(date); + const year = d.getFullYear(); + const month = String(d.getMonth() + 1).padStart(2, '0'); + const day = String(d.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; + } +} diff --git a/groups/Tapops/js/utils/document-formatter.js b/groups/Tapops/js/utils/document-formatter.js new file mode 100644 index 0000000..8d6be2e --- /dev/null +++ b/groups/Tapops/js/utils/document-formatter.js @@ -0,0 +1,204 @@ +export class DocumentFormatter { + static MONTHS_ID = ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des']; + + static CATEGORY_RULES = [ + { key: 'skripsi', match: ['skripsi', 'tugas akhir', 'ta'] }, + { key: 'akademik', match: ['akademik', 'kalender', 'kaldik'] }, + { key: 'praktik', match: ['praktik', 'praktek', 'kerja praktik', 'kp'] }, + ]; + + static TAG_ABBREVIATIONS = new Set(['kp', 'sop', 'ta', 'krs', 'khs', 'sks', 'pkl', 'mbkm']); + + static FILE_TYPE_RULES = [ + { extensions: ['pdf'], mimeTypes: ['application/pdf'], icon: 'fas fa-file-pdf', css: 'doc-type-pdf' }, + { + extensions: ['doc', 'docx'], + mimeTypes: [ + 'application/msword', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + ], + fallbackType: 'docx', + icon: 'fas fa-file-word', + css: 'doc-type-docx', + }, + { + extensions: ['ppt', 'pptx'], + mimeTypes: [ + 'application/vnd.ms-powerpoint', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + ], + fallbackType: 'pptx', + icon: 'fas fa-file-powerpoint', + css: 'doc-type-pptx', + }, + { + extensions: ['xls', 'xlsx'], + mimeTypes: [ + 'application/vnd.ms-excel', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + ], + fallbackType: 'xlsx', + icon: 'fas fa-file-excel', + css: 'doc-type-xlsx', + }, + { extensions: ['csv'], mimeTypes: ['text/csv'], icon: 'fas fa-file-csv', css: 'doc-type-xlsx' }, + { extensions: ['txt', 'rtf', 'md'], mimeTypes: ['text/plain', 'application/rtf'], icon: 'fas fa-file-alt', css: 'doc-type-txt' }, + { + extensions: ['zip', 'rar', '7z', 'tar', 'gz'], + mimeTypes: ['application/zip', 'application/x-rar-compressed', 'application/x-7z-compressed', 'application/gzip'], + icon: 'fas fa-file-archive', + css: 'doc-type-archive', + }, + { + extensions: ['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp'], + mimePrefix: 'image/', + icon: 'fas fa-file-image', + css: 'doc-type-image', + }, + ]; + + static formatDate(iso) { + if (!iso) return ''; + const date = new Date(iso); + if (Number.isNaN(date.getTime())) return ''; + const day = String(date.getDate()).padStart(2, '0'); + const month = this.MONTHS_ID[date.getMonth()]; + return `${day} ${month} ${date.getFullYear()}`; + } + + static categoryFromTags(tags) { + if (!tags || tags.length === 0) return 'umum'; + const lower = tags.map(tag => tag.toLowerCase()); + for (const rule of this.CATEGORY_RULES) { + if (rule.match.some(match => lower.includes(match))) return rule.key; + } + return 'umum'; + } + + static fileTypeFromName(name) { + return this.fileInfoFromName(name).type; + } + + static fileInfoFromName(name) { + const extension = this.extensionFromName(name); + const rule = this.FILE_TYPE_RULES.find(item => item.extensions.includes(extension)); + + if (!rule) { + return { + type: extension || 'file', + icon: 'fas fa-file', + css: 'doc-type-file', + }; + } + + return { + type: extension, + icon: rule.icon, + css: rule.css, + }; + } + + static fileInfoFromItem(item) { + const file = item?.file && typeof item.file === 'object' ? item.file : {}; + const nameCandidates = [ + file.filename_download, + file.filename_disk, + item?.nama_berkas, + ]; + + for (const name of nameCandidates) { + const info = this.fileInfoFromName(name); + if (info.type !== 'file') return info; + } + + return this.fileInfoFromMime(file.type); + } + + static fileInfoFromMime(type) { + const mime = String(type || '').trim().toLowerCase(); + if (!mime) return this.fileInfoFromName(''); + + const rule = this.FILE_TYPE_RULES.find(item => ( + item.mimeTypes?.includes(mime) || (item.mimePrefix && mime.startsWith(item.mimePrefix)) + )); + + if (!rule) return this.fileInfoFromName(''); + + return { + type: rule.fallbackType || rule.extensions[0], + icon: rule.icon, + css: rule.css, + }; + } + + static extensionFromName(name) { + const normalized = String(name || '').trim().toLowerCase(); + const lastSegment = normalized.split(/[\\/]/).pop() || ''; + const extensionStart = lastSegment.lastIndexOf('.'); + + if (extensionStart <= 0 || extensionStart === lastSegment.length - 1) return ''; + return lastSegment.slice(extensionStart + 1); + } + + static downloadFilename(name, type) { + const ext = `.${type}`; + if (!name) return `dokumen${ext}`; + return name.toLowerCase().endsWith(ext.toLowerCase()) ? name : `${name}${ext}`; + } + + static downloadFilenameFromItem(item) { + const file = item?.file && typeof item.file === 'object' ? item.file : {}; + const originalFilename = file.filename_download || file.filename_disk; + if (originalFilename) return originalFilename; + + const fileType = this.fileInfoFromItem(item).type; + return this.downloadFilename(item?.nama_berkas || 'dokumen', fileType); + } + + static escapeHtml(str) { + if (str == null) return ''; + return String(str) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } + + static escapeAttr(str) { + return this.escapeHtml(str); + } + + static formatTag(tag) { + const value = String(tag).trim().replace(/\s+/g, ' '); + if (!value) return value; + + return value.split(' ').map(word => { + const lower = word.toLowerCase(); + if (this.TAG_ABBREVIATIONS.has(lower) || /^[A-Z0-9]{2,}$/.test(word)) { + return lower.toUpperCase(); + } + + return lower.charAt(0).toUpperCase() + lower.slice(1); + }).join(' '); + } + + static parseTags(raw) { + if (!raw) return []; + const str = String(raw).trim(); + if (!str) return []; + + if (str.startsWith('[')) { + try { + const arr = JSON.parse(str); + if (Array.isArray(arr)) { + return arr.map(tag => String(tag).trim()).filter(Boolean); + } + } catch { + // Fall through to comma-separated parsing. + } + } + + return str.split(',').map(tag => tag.trim()).filter(Boolean); + } +} diff --git a/groups/Tapops/js/viewer.js b/groups/Tapops/js/viewer.js new file mode 100644 index 0000000..f4c57c5 --- /dev/null +++ b/groups/Tapops/js/viewer.js @@ -0,0 +1 @@ +import './viewer/viewer-app.js'; diff --git a/groups/Tapops/js/viewer/file-viewer.js b/groups/Tapops/js/viewer/file-viewer.js new file mode 100644 index 0000000..dd90530 --- /dev/null +++ b/groups/Tapops/js/viewer/file-viewer.js @@ -0,0 +1,122 @@ +import { FileDownloader } from '../services/file-downloader.js'; +import { ViewerApi } from './viewer-api.js'; + +export class FileViewer { + constructor() { + const params = new URLSearchParams(window.location.search); + this.id = params.get('id'); + this.name = params.get('name') || 'Dokumen'; + this.type = (params.get('type') || 'pdf').toLowerCase(); + + this.titleEl = document.getElementById('viewer-title'); + this.typeBadgeEl = document.getElementById('viewer-type'); + this.downloadEl = document.getElementById('viewer-download'); + this.contentEl = document.getElementById('viewer-content'); + + this.assetUrl = this.id ? ViewerApi.assetUrl(this.id) : null; + } + + async init() { + if (!this.id) { + this._renderError('Parameter berkas tidak ditemukan di URL.'); + return; + } + + document.title = `${this.name} — Pratinjau Dokumen`; + this.titleEl.textContent = this.name; + this.typeBadgeEl.textContent = this.type.toUpperCase(); + this._setupDownload(); + + try { + if (this._isOfficeType()) { + this._renderOfficeOnline(); + } else if (this.type === 'pdf') { + this._renderPdf(); + } else { + this._renderUnsupported(); + } + } catch (err) { + console.error(err); + this._renderError('Gagal memuat pratinjau berkas. Silakan unduh berkas untuk membuka secara lokal.'); + } + } + + _setupDownload() { + const ext = `.${this.type}`; + const filename = this.name.toLowerCase().endsWith(ext) ? this.name : `${this.name}${ext}`; + + this.downloadEl.href = FileDownloader.originalDownloadUrl(this.assetUrl); + this.downloadEl.download = filename; + this.downloadEl.style.cursor = 'pointer'; + this.downloadEl.addEventListener('click', async event => { + event.preventDefault(); + const originalHtml = this.downloadEl.innerHTML; + this.downloadEl.innerHTML = ' Mengunduh…'; + this.downloadEl.style.pointerEvents = 'none'; + try { + await FileDownloader.download(this.assetUrl, filename); + } catch (err) { + console.warn('Blob download failed, falling back:', err); + window.open(FileDownloader.originalDownloadUrl(this.assetUrl), '_blank'); + } finally { + this.downloadEl.innerHTML = originalHtml; + this.downloadEl.style.pointerEvents = ''; + } + }); + } + + _isOfficeType() { + return ['doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx'].includes(this.type); + } + + _renderOfficeOnline() { + const iframe = document.createElement('iframe'); + iframe.className = 'viewer-office-frame'; + iframe.title = this.name; + iframe.src = `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(this.assetUrl)}`; + iframe.setAttribute('frameborder', '0'); + iframe.setAttribute('allowfullscreen', ''); + this.contentEl.replaceChildren(iframe); + } + + _renderPdf() { + const iframe = document.createElement('iframe'); + iframe.className = 'viewer-pdf-frame'; + iframe.title = this.name; + iframe.src = this.assetUrl; + this.contentEl.replaceChildren(iframe); + } + + _renderUnsupported() { + const wrapper = document.createElement('div'); + wrapper.className = 'viewer-error'; + + const icon = document.createElement('i'); + icon.className = 'fas fa-file'; + + const span = document.createElement('span'); + span.textContent = `Pratinjau tidak tersedia untuk format ${this.type.toUpperCase()}. Silakan unduh berkas.`; + + wrapper.append(icon, span); + this.contentEl.replaceChildren(wrapper); + } + + _renderError(message) { + const wrapper = document.createElement('div'); + wrapper.className = 'viewer-error'; + + const icon = document.createElement('i'); + icon.className = 'fas fa-exclamation-triangle'; + + const span = document.createElement('span'); + span.textContent = message; + + const back = document.createElement('a'); + back.href = 'index.html#repository'; + back.className = 'btn-back-mini'; + back.textContent = 'Kembali ke Repositori'; + + wrapper.append(icon, span, back); + this.contentEl.replaceChildren(wrapper); + } +} diff --git a/groups/Tapops/js/viewer/viewer-api.js b/groups/Tapops/js/viewer/viewer-api.js new file mode 100644 index 0000000..6330217 --- /dev/null +++ b/groups/Tapops/js/viewer/viewer-api.js @@ -0,0 +1,31 @@ +export class ViewerApi { + static API_BASE = 'https://api.ifuntanhub.dev'; + + static assetUrl(uuid) { + return `${this.API_BASE}/assets/${uuid}`; + } + + static async fetchBlob(url, onProgress) { + const response = await fetch(url); + if (!response.ok) throw new Error(`Gagal mengambil berkas (HTTP ${response.status}).`); + + const contentLength = response.headers.get('Content-Length'); + if (onProgress && contentLength && response.body) { + const total = Number.parseInt(contentLength, 10); + const reader = response.body.getReader(); + const chunks = []; + let received = 0; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + chunks.push(value); + received += value.length; + onProgress(received / total); + } + return new Blob(chunks); + } + + return response.blob(); + } +} diff --git a/groups/Tapops/js/viewer/viewer-app.js b/groups/Tapops/js/viewer/viewer-app.js new file mode 100644 index 0000000..fee47e6 --- /dev/null +++ b/groups/Tapops/js/viewer/viewer-app.js @@ -0,0 +1,8 @@ +import { FileViewer } from './file-viewer.js'; +import { ViewerPrefetch } from './viewer-prefetch.js'; + +ViewerPrefetch.fromLocation(); + +document.addEventListener('DOMContentLoaded', () => { + new FileViewer().init(); +}); diff --git a/groups/Tapops/js/viewer/viewer-prefetch.js b/groups/Tapops/js/viewer/viewer-prefetch.js new file mode 100644 index 0000000..f2771fc --- /dev/null +++ b/groups/Tapops/js/viewer/viewer-prefetch.js @@ -0,0 +1,16 @@ +import { ViewerApi } from './viewer-api.js'; + +export class ViewerPrefetch { + static fromLocation(location = window.location) { + const params = new URLSearchParams(location.search); + const id = params.get('id'); + if (!id) return; + + const link = document.createElement('link'); + link.rel = 'prefetch'; + link.as = 'fetch'; + link.crossOrigin = 'anonymous'; + link.href = ViewerApi.assetUrl(id); + document.head.appendChild(link); + } +} diff --git a/groups/Tapops/tests/date-pagination.test.mjs b/groups/Tapops/tests/date-pagination.test.mjs new file mode 100644 index 0000000..91a7488 --- /dev/null +++ b/groups/Tapops/tests/date-pagination.test.mjs @@ -0,0 +1,37 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; + +import { DateRangeCalculator } from '../js/utils/date-range-calculator.js'; +import { PaginationModel } from '../js/models/pagination-model.js'; + +test('calculates date presets using an injected current day', () => { + const today = new Date('2026-06-04T12:00:00+07:00'); + + const last7 = DateRangeCalculator.calculatePreset('last7', today); + assert.equal(DateRangeCalculator.toDateKey(last7.start), '2026-05-29'); + assert.equal(DateRangeCalculator.toDateKey(last7.end), '2026-06-04'); + + const thisMonth = DateRangeCalculator.calculatePreset('thisMonth', today); + assert.equal(DateRangeCalculator.toDateKey(thisMonth.start), '2026-06-01'); + assert.equal(DateRangeCalculator.toDateKey(thisMonth.end), '2026-06-04'); + + const all = DateRangeCalculator.calculatePreset('all', today); + assert.equal(all.start, null); + assert.equal(all.end, null); +}); + +test('formats and parses dd/mm/yyyy date input safely', () => { + const date = new Date(2026, 5, 4); + assert.equal(DateRangeCalculator.formatDisplay(date), '04/06/2026'); + assert.equal(DateRangeCalculator.toDateKey(date), '2026-06-04'); + assert.equal(DateRangeCalculator.parseDisplayDate('04/06/2026')?.getFullYear(), 2026); + assert.equal(DateRangeCalculator.parseDisplayDate('31/02/2026'), null); + assert.equal(DateRangeCalculator.parseDisplayDate('04/06/10000'), null); +}); + +test('creates stable compact pagination ranges', () => { + assert.deepEqual(PaginationModel.range(1, 4), [1, 2, 3, 4]); + assert.deepEqual(PaginationModel.range(1, 10), [1, 2, '...', 10]); + assert.deepEqual(PaginationModel.range(5, 10), [1, '...', 4, 5, 6, '...', 10]); + assert.deepEqual(PaginationModel.range(10, 10), [1, '...', 9, 10]); +}); diff --git a/groups/Tapops/tests/directus-client.test.mjs b/groups/Tapops/tests/directus-client.test.mjs new file mode 100644 index 0000000..16dbbf6 --- /dev/null +++ b/groups/Tapops/tests/directus-client.test.mjs @@ -0,0 +1,34 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; + +import { DirectusClient } from '../js/services/directus-client.js'; + +test('requests berkas with expanded Directus file metadata', () => { + const url = new URL(DirectusClient.itemsUrl('berkas')); + + assert.equal(url.pathname, '/items/berkas'); + assert.equal(url.searchParams.get('limit'), '-1'); + assert.equal(url.searchParams.get('fields'), '*,file.id,file.filename_download,file.type,file.filename_disk'); +}); + +test('builds asset and viewer URLs from expanded Directus file objects', () => { + const item = { + nama_berkas: 'Panduan Penulisan Proposal Kerja Praktek', + file: { + id: '4976330e-e547-45f6-b9be-afce493c8a82', + filename_download: 'Panduan Penulisan Proposal Kerja Praktek.docx', + type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + }, + }; + + assert.equal( + DirectusClient.assetUrl(item.file), + 'https://api.ifuntanhub.dev/assets/4976330e-e547-45f6-b9be-afce493c8a82', + ); + + const viewerUrl = new URL(DirectusClient.viewerUrl(item), 'https://example.test/groups/Tapops/'); + assert.equal(viewerUrl.pathname, '/groups/Tapops/viewer.html'); + assert.equal(viewerUrl.searchParams.get('id'), '4976330e-e547-45f6-b9be-afce493c8a82'); + assert.equal(viewerUrl.searchParams.get('name'), 'Panduan Penulisan Proposal Kerja Praktek'); + assert.equal(viewerUrl.searchParams.get('type'), 'docx'); +}); diff --git a/groups/Tapops/tests/document-formatter.test.mjs b/groups/Tapops/tests/document-formatter.test.mjs new file mode 100644 index 0000000..9933fe8 --- /dev/null +++ b/groups/Tapops/tests/document-formatter.test.mjs @@ -0,0 +1,111 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; + +import { DocumentFormatter } from '../js/utils/document-formatter.js'; + +test('parses comma-separated and JSON string tags', () => { + assert.deepEqual(DocumentFormatter.parseTags('akademik, panduan, 2026'), [ + 'akademik', + 'panduan', + '2026', + ]); + assert.deepEqual(DocumentFormatter.parseTags('["kerja praktik","kp"]'), [ + 'kerja praktik', + 'kp', + ]); +}); + +test('formats tag labels without breaking abbreviations', () => { + assert.equal(DocumentFormatter.formatTag('SOP'), 'SOP'); + assert.equal(DocumentFormatter.formatTag('kp'), 'KP'); + assert.equal(DocumentFormatter.formatTag('kerja praktek'), 'Kerja Praktek'); + assert.equal(DocumentFormatter.formatTag('Kerja Praktek'), 'Kerja Praktek'); + assert.equal(DocumentFormatter.formatTag('umum'), 'Umum'); +}); + +test('maps parsed tags to fixed repository categories', () => { + assert.equal(DocumentFormatter.categoryFromTags(['TA']), 'skripsi'); + assert.equal(DocumentFormatter.categoryFromTags(['Kalender']), 'akademik'); + assert.equal(DocumentFormatter.categoryFromTags(['kp']), 'praktik'); + assert.equal(DocumentFormatter.categoryFromTags(['surat']), 'umum'); + assert.equal(DocumentFormatter.categoryFromTags([]), 'umum'); +}); + +test('derives file type and safe download filename from document name', () => { + assert.equal(DocumentFormatter.fileTypeFromName('Panduan Akademik.pdf'), 'pdf'); + assert.equal(DocumentFormatter.fileTypeFromName('Form KP.DOCX'), 'docx'); + assert.equal(DocumentFormatter.fileTypeFromName('Catatan.txt'), 'txt'); + assert.equal(DocumentFormatter.fileTypeFromName('Materi Seminar.pptx'), 'pptx'); + assert.equal(DocumentFormatter.downloadFilename('Form KP', 'docx'), 'Form KP.docx'); + assert.equal(DocumentFormatter.downloadFilename('Template lama.DOC', 'doc'), 'Template lama.DOC'); + assert.equal(DocumentFormatter.downloadFilename('Kalender.pdf', 'pdf'), 'Kalender.pdf'); +}); + +test('maps supported file names to display metadata for cards', () => { + assert.deepEqual(DocumentFormatter.fileInfoFromName('Panduan Akademik.pdf'), { + type: 'pdf', + icon: 'fas fa-file-pdf', + css: 'doc-type-pdf', + }); + assert.deepEqual(DocumentFormatter.fileInfoFromName('Form KP.DOCX'), { + type: 'docx', + icon: 'fas fa-file-word', + css: 'doc-type-docx', + }); + assert.deepEqual(DocumentFormatter.fileInfoFromName('Catatan.txt'), { + type: 'txt', + icon: 'fas fa-file-alt', + css: 'doc-type-txt', + }); + assert.deepEqual(DocumentFormatter.fileInfoFromName('Materi Seminar.pptx'), { + type: 'pptx', + icon: 'fas fa-file-powerpoint', + css: 'doc-type-pptx', + }); + assert.deepEqual(DocumentFormatter.fileInfoFromName('Lampiran.7z'), { + type: '7z', + icon: 'fas fa-file-archive', + css: 'doc-type-archive', + }); +}); + +test('derives file metadata from expanded Directus file records when display title has no extension', () => { + const pdfItem = { + nama_berkas: 'SOP Kerja Praktek', + file: { + filename_download: 'SOP Kerja Praktek.pdf', + type: 'application/pdf', + }, + }; + const docxItem = { + nama_berkas: 'Panduan Penulisan Proposal Kerja Praktek', + file: { + filename_download: 'Panduan Penulisan Proposal Kerja Praktek.docx', + type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + }, + }; + + assert.deepEqual(DocumentFormatter.fileInfoFromItem(pdfItem), { + type: 'pdf', + icon: 'fas fa-file-pdf', + css: 'doc-type-pdf', + }); + assert.deepEqual(DocumentFormatter.fileInfoFromItem(docxItem), { + type: 'docx', + icon: 'fas fa-file-word', + css: 'doc-type-docx', + }); + assert.equal(DocumentFormatter.downloadFilenameFromItem(pdfItem), 'SOP Kerja Praktek.pdf'); + assert.equal(DocumentFormatter.downloadFilenameFromItem(docxItem), 'Panduan Penulisan Proposal Kerja Praktek.docx'); +}); + +test('falls back to MIME type when Directus filename metadata has no extension', () => { + assert.deepEqual(DocumentFormatter.fileInfoFromItem({ + nama_berkas: 'Berkas Presentasi', + file: { type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation' }, + }), { + type: 'pptx', + icon: 'fas fa-file-powerpoint', + css: 'doc-type-pptx', + }); +}); diff --git a/groups/Tapops/tests/file-preview-download.test.mjs b/groups/Tapops/tests/file-preview-download.test.mjs new file mode 100644 index 0000000..25e91b1 --- /dev/null +++ b/groups/Tapops/tests/file-preview-download.test.mjs @@ -0,0 +1,197 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; + +import { FileDownloader } from '../js/services/file-downloader.js'; +import { FileViewer } from '../js/viewer/file-viewer.js'; + +class FakeElement { + constructor(tagName = 'div') { + this.tagName = tagName.toUpperCase(); + this.children = []; + this.attributes = new Map(); + this.dataset = {}; + this.eventListeners = new Map(); + this.style = {}; + this.className = ''; + this.href = ''; + this.src = ''; + this.title = ''; + this.textContent = ''; + this.innerHTML = ''; + this.download = ''; + } + + append(...nodes) { + this.children.push(...nodes); + } + + appendChild(node) { + this.children.push(node); + return node; + } + + prepend(node) { + this.children.unshift(node); + return node; + } + + removeChild(node) { + this.children = this.children.filter(child => child !== node); + return node; + } + + replaceChildren(...nodes) { + this.children = [...nodes]; + } + + remove() { + this.removed = true; + } + + setAttribute(name, value) { + this.attributes.set(name, String(value)); + } + + getAttribute(name) { + return this.attributes.get(name); + } + + removeAttribute(name) { + this.attributes.delete(name); + } + + addEventListener(type, handler) { + this.eventListeners.set(type, handler); + } + + click() { + this.clicked = true; + } +} + +function installViewerDom(search) { + const previous = { + document: globalThis.document, + window: globalThis.window, + URL: globalThis.URL, + fetch: globalThis.fetch, + setTimeout: globalThis.setTimeout, + consoleError: console.error, + consoleWarn: console.warn, + }; + const elements = { + title: new FakeElement('h1'), + type: new FakeElement('span'), + download: new FakeElement('a'), + content: new FakeElement('main'), + }; + const body = new FakeElement('body'); + const created = []; + + globalThis.document = { + title: '', + body, + getElementById(id) { + return { + 'viewer-title': elements.title, + 'viewer-type': elements.type, + 'viewer-download': elements.download, + 'viewer-content': elements.content, + }[id]; + }, + createElement(tagName) { + const node = new FakeElement(tagName); + created.push(node); + return node; + }, + }; + globalThis.window = { + location: { search }, + open(url) { + this.openedUrl = url; + }, + }; + globalThis.URL = { + createObjectURL() { + return 'blob:tapops-download'; + }, + revokeObjectURL(url) { + this.revokedUrl = url; + }, + }; + globalThis.setTimeout = callback => { + callback(); + return 1; + }; + console.error = () => {}; + console.warn = () => {}; + + return { + elements, + created, + restore() { + globalThis.document = previous.document; + globalThis.window = previous.window; + globalThis.URL = previous.URL; + globalThis.fetch = previous.fetch; + globalThis.setTimeout = previous.setTimeout; + console.error = previous.consoleError; + console.warn = previous.consoleWarn; + }, + }; +} + +test('repository download fetches the Directus original-file URL', async (t) => { + const { created, restore } = installViewerDom(''); + t.after(restore); + + let fetchedUrl = ''; + globalThis.fetch = async (url) => { + fetchedUrl = url; + return { + ok: true, + blob: async () => new Blob(['docx']), + }; + }; + + await FileDownloader.download('https://api.ifuntanhub.dev/assets/docx-id', 'Form KP.docx'); + + const clickedAnchor = created.find(node => node.tagName === 'A' && node.clicked); + assert.equal(fetchedUrl, 'https://api.ifuntanhub.dev/assets/docx-id?download'); + assert.equal(clickedAnchor.download, 'Form KP.docx'); +}); + +test('DOCX preview renders Microsoft Office Online Viewer iframe', async (t) => { + const { elements, restore } = installViewerDom('?id=docx-id&name=Form%20KP.docx&type=docx'); + t.after(restore); + + await new FileViewer().init(); + + const iframe = elements.content.children[0]; + const assetUrl = 'https://api.ifuntanhub.dev/assets/docx-id'; + assert.equal(iframe.tagName, 'IFRAME'); + assert.equal(iframe.className, 'viewer-office-frame'); + assert.equal(iframe.src, `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(assetUrl)}`); +}); + +test('viewer download uses the Directus original-file URL', async (t) => { + const { elements, created, restore } = installViewerDom('?id=pdf-id&name=Panduan.pdf&type=pdf'); + t.after(restore); + + let fetchedUrl = ''; + globalThis.fetch = async (url) => { + fetchedUrl = url; + return { + ok: true, + headers: { get: () => null }, + blob: async () => new Blob(['pdf']), + }; + }; + + await new FileViewer().init(); + await elements.download.eventListeners.get('click')({ preventDefault() {} }); + + const clickedAnchor = created.find(node => node.tagName === 'A' && node.clicked); + assert.equal(fetchedUrl, 'https://api.ifuntanhub.dev/assets/pdf-id?download'); + assert.equal(clickedAnchor.download, 'Panduan.pdf'); +}); diff --git a/groups/Tapops/tests/static-structure.test.mjs b/groups/Tapops/tests/static-structure.test.mjs new file mode 100644 index 0000000..8a61981 --- /dev/null +++ b/groups/Tapops/tests/static-structure.test.mjs @@ -0,0 +1,22 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; + +const indexHtml = readFileSync(new URL('../index.html', import.meta.url), 'utf8'); +const viewerHtml = readFileSync(new URL('../viewer.html', import.meta.url), 'utf8'); +const repositoryCardsCss = readFileSync(new URL('../css/repository/cards.css', import.meta.url), 'utf8'); + +test('Tapops index uses module scripts and no inline behavior', () => { + assert.match(indexHtml, / + + + diff --git a/package-lock.json b/package-lock.json index a16578c..451b48d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -886,9 +886,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "dev": true, "funding": [ { @@ -917,7 +917,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -926,9 +925,9 @@ } }, "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "dev": true, "funding": [ { @@ -946,7 +945,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, diff --git a/package.json b/package.json index 31e8272..b0c5938 100644 --- a/package.json +++ b/package.json @@ -2,10 +2,12 @@ "name": "web-student-safe", "version": "1.0.0", "private": true, + "type": "module", "description": "Student-safe static page assignment kit for Informatika UNTAN", "scripts": { "dev": "vite", "build": "vite build", + "test:tapops": "node --test groups/Tapops/tests/*.test.mjs", "preview": "vite preview" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..1d449a0 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,654 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + vite: + specifier: ^7.1.0 + version: 7.3.3 + +packages: + + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@rollup/rollup-android-arm-eabi@4.60.4': + resolution: {integrity: sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.60.4': + resolution: {integrity: sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.60.4': + resolution: {integrity: sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.60.4': + resolution: {integrity: sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.60.4': + resolution: {integrity: sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.60.4': + resolution: {integrity: sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.60.4': + resolution: {integrity: sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.60.4': + resolution: {integrity: sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.60.4': + resolution: {integrity: sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.60.4': + resolution: {integrity: sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.60.4': + resolution: {integrity: sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.60.4': + resolution: {integrity: sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.60.4': + resolution: {integrity: sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.60.4': + resolution: {integrity: sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.60.4': + resolution: {integrity: sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.60.4': + resolution: {integrity: sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.60.4': + resolution: {integrity: sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.60.4': + resolution: {integrity: sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.60.4': + resolution: {integrity: sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.60.4': + resolution: {integrity: sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.60.4': + resolution: {integrity: sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.60.4': + resolution: {integrity: sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.60.4': + resolution: {integrity: sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.60.4': + resolution: {integrity: sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.60.4': + resolution: {integrity: sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==} + cpu: [x64] + os: [win32] + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + engines: {node: ^10 || ^12 || >=14} + + rollup@4.60.4: + resolution: {integrity: sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + + vite@7.3.3: + resolution: {integrity: sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + +snapshots: + + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + + '@rollup/rollup-android-arm-eabi@4.60.4': + optional: true + + '@rollup/rollup-android-arm64@4.60.4': + optional: true + + '@rollup/rollup-darwin-arm64@4.60.4': + optional: true + + '@rollup/rollup-darwin-x64@4.60.4': + optional: true + + '@rollup/rollup-freebsd-arm64@4.60.4': + optional: true + + '@rollup/rollup-freebsd-x64@4.60.4': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.60.4': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.60.4': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.60.4': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.60.4': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.60.4': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.60.4': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-x64-musl@4.60.4': + optional: true + + '@rollup/rollup-openbsd-x64@4.60.4': + optional: true + + '@rollup/rollup-openharmony-arm64@4.60.4': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.60.4': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.60.4': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.60.4': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.60.4': + optional: true + + '@types/estree@1.0.8': {} + + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + fsevents@2.3.3: + optional: true + + nanoid@3.3.12: {} + + picocolors@1.1.1: {} + + picomatch@4.0.4: {} + + postcss@8.5.15: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + rollup@4.60.4: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.60.4 + '@rollup/rollup-android-arm64': 4.60.4 + '@rollup/rollup-darwin-arm64': 4.60.4 + '@rollup/rollup-darwin-x64': 4.60.4 + '@rollup/rollup-freebsd-arm64': 4.60.4 + '@rollup/rollup-freebsd-x64': 4.60.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.4 + '@rollup/rollup-linux-arm-musleabihf': 4.60.4 + '@rollup/rollup-linux-arm64-gnu': 4.60.4 + '@rollup/rollup-linux-arm64-musl': 4.60.4 + '@rollup/rollup-linux-loong64-gnu': 4.60.4 + '@rollup/rollup-linux-loong64-musl': 4.60.4 + '@rollup/rollup-linux-ppc64-gnu': 4.60.4 + '@rollup/rollup-linux-ppc64-musl': 4.60.4 + '@rollup/rollup-linux-riscv64-gnu': 4.60.4 + '@rollup/rollup-linux-riscv64-musl': 4.60.4 + '@rollup/rollup-linux-s390x-gnu': 4.60.4 + '@rollup/rollup-linux-x64-gnu': 4.60.4 + '@rollup/rollup-linux-x64-musl': 4.60.4 + '@rollup/rollup-openbsd-x64': 4.60.4 + '@rollup/rollup-openharmony-arm64': 4.60.4 + '@rollup/rollup-win32-arm64-msvc': 4.60.4 + '@rollup/rollup-win32-ia32-msvc': 4.60.4 + '@rollup/rollup-win32-x64-gnu': 4.60.4 + '@rollup/rollup-win32-x64-msvc': 4.60.4 + fsevents: 2.3.3 + + source-map-js@1.2.1: {} + + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + vite@7.3.3: + dependencies: + esbuild: 0.27.7 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.15 + rollup: 4.60.4 + tinyglobby: 0.2.16 + optionalDependencies: + fsevents: 2.3.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..00f6fc4 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +allowBuilds: + esbuild: set this to true or false diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..e65cacf --- /dev/null +++ b/vite.config.js @@ -0,0 +1,125 @@ +import { cpSync, existsSync, readdirSync } from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { defineConfig } from 'vite'; + +const root = path.dirname(fileURLToPath(import.meta.url)); +const excludedDirs = new Set(['.git', 'dist', 'node_modules']); +const rodaDashboardUrl = 'https://dashboard.informatika.untan.ac.id/index.php?penelitian'; + +let rodaDashboardHtmlCache; + +function collectHtmlInputs(dir) { + const inputs = {}; + const entries = readdirSync(dir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + + if (entry.isDirectory()) { + if (!excludedDirs.has(entry.name)) { + Object.assign(inputs, collectHtmlInputs(fullPath)); + } + continue; + } + + if (entry.isFile() && entry.name.endsWith('.html')) { + const relativePath = path.relative(root, fullPath); + const inputName = relativePath + .replace(/\.html$/, '') + .replace(/[^a-zA-Z0-9_-]+/g, '_'); + + inputs[inputName] = fullPath; + } + } + + return inputs; +} + +function copyStaticAssets() { + return { + name: 'copy-static-assets', + closeBundle() { + const source = path.join(root, 'assets'); + const destination = path.join(root, 'dist', 'assets'); + + if (existsSync(source)) { + cpSync(source, destination, { recursive: true }); + } + }, + }; +} + +async function fetchRodaDashboardHtml() { + if (rodaDashboardHtmlCache !== undefined) { + return rodaDashboardHtmlCache; + } + + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 12000); + + try { + const response = await fetch(rodaDashboardUrl, { + signal: controller.signal, + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36', + }, + }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + + rodaDashboardHtmlCache = await response.text(); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + console.warn(`[roda-dashboard] Build-time dashboard fetch skipped: ${message}`); + rodaDashboardHtmlCache = ''; + } finally { + clearTimeout(timeoutId); + } + + return rodaDashboardHtmlCache; +} + +function escapeJsonScriptContent(value) { + return JSON.stringify(value) + .replaceAll('<', '\\u003c') + .replaceAll('>', '\\u003e') + .replaceAll('&', '\\u0026') + .replaceAll('\u2028', '\\u2028') + .replaceAll('\u2029', '\\u2029'); +} + +function embedRodaDashboardData() { + let command = 'serve'; + + return { + name: 'embed-roda-dashboard-data', + configResolved(config) { + command = config.command; + }, + async transformIndexHtml(html, context) { + const filename = context?.filename ? path.resolve(context.filename) : ''; + const rodaIndex = path.join(root, 'groups', 'RODA', 'index.html'); + + if (filename !== rodaIndex || command !== 'build') { + return html.replace('', ''); + } + + const dashboardHtml = await fetchRodaDashboardHtml(); + const payload = ``; + + return html.replace('', payload); + }, + }; +} + +export default defineConfig({ + plugins: [embedRodaDashboardData(), copyStaticAssets()], + build: { + rollupOptions: { + input: collectHtmlInputs(root), + }, + }, +});