184 Commits

Author SHA1 Message Date
tisto 0dd0ff488a merge upstream 2026-06-10 08:06:44 +00:00
Syariffullah 0fea8bddb1 fix: replacing mangrove forst with sylva forest 2026-06-10 14:16:41 +07:00
izu 034c958909 Merge pull request 'fix(Jellyfish.Corp): revisi bagian karir dan mitra kerja sama' (#30) from miaaurl/student-web-if-development-kit:fix/karir into main
Reviewed-on: izu/student-web-if-development-kit#30
Approved
2026-06-05 16:20:52 +00:00
miaaurl c87e4eba3d fix: tambah bilingual dan responsive konten baru 2026-06-05 22:53:40 +07:00
L4zyL4mb 0cd144673c fix: perbaiki bilingual dan responsive konten baru 2026-06-05 22:51:44 +07:00
izu af8a3f6a98 Merge pull request 'fix(C4A): change staff profile image based on UAT feedback' (#28) from AnderiSaputra/student-web-if-development-kit:main into main
Reviewed-on: izu/student-web-if-development-kit#28
Approved
2026-06-05 13:59:16 +00:00
izu 54a317d147 Merge pull request 'fix(RODA): perbaiki fetch data riset agar real-time di production' (#29) from Rayhan/roda-student-web-if-development-kit:feat/RODA into main
Reviewed-on: izu/student-web-if-development-kit#29
Approved
2026-06-05 13:59:05 +00:00
GuavaPopper 00611a921b feat(RODA): data riset kini real-time via proxy Netlify 2026-06-05 20:16:53 +07:00
AufaHafizah 7958934e45 Merge pull request 'fix(c4a): update staff profile image based on UAT feedback' (#11) from feature/halaman-stafPendukung-SOP into main
Reviewed-on: AnderiSaputra/student-web-if-development-kit#11
2026-06-05 12:29:17 +00:00
cygouw c7d855f822 chore(c4a): update staff profile image based on UAT feedback 2026-06-05 19:22:08 +07:00
Raditya_Indra_Putranto d772f4573b merge upstream 2026-06-05 12:02:39 +00:00
GuavaPopper 3d2f0b23ad refactor: Perbaiki Proxy Pipeline dan simplify dashboard configuration 2026-06-05 18:57:23 +07:00
GuavaPopper e18641ff52 fix: run RODA with Vite dev 2026-06-05 17:55:51 +07:00
GuavaPopper f5dfb68bcc Use RODA server for dashboard proxy 2026-06-05 17:06:39 +07:00
izu 52f6588316 Merge pull request 'prod' (#22) from Rayhan/roda-student-web-if-development-kit:prod into main
Reviewed-on: izu/student-web-if-development-kit#22
Approved
2026-06-05 09:22:25 +00:00
izu 1dcf227bcb Merge pull request 'main' (#26) from Araya/student-web-if-development-kit:main into main
Reviewed-on: izu/student-web-if-development-kit#26
Approved
2026-06-05 08:55:45 +00:00
Araya's Project 3cc65cf437 Fix logic API 2026-06-05 15:52:29 +07:00
Araya's Project 02dd1141d3 Fix logic API 2026-06-05 15:50:25 +07:00
izu f65e64effc Merge pull request 'fix(c4a): fix README templates and resolve "SOP tidak ditemukan" visibility logic' (#24) from AnderiSaputra/student-web-if-development-kit:main into main
Reviewed-on: izu/student-web-if-development-kit#24
Approved
2026-06-05 08:28:36 +00:00
ceciliayg 6f87030147 merge upstream 2026-06-05 08:20:24 +00:00
izu 09b287357f Merge pull request 'main' (#23) from tisto/student-web-if-development-kit:main into main
Reviewed-on: izu/student-web-if-development-kit#23
Approved
2026-06-05 08:19:14 +00:00
Syariffullah b3adc54221 Merge branch 'main' of https://git.ifuntanhub.dev/tisto/student-web-if-development-kit 2026-06-05 15:14:31 +07:00
AufaHafizah e074940509 Merge pull request 'feature/halaman-stafPendukung-SOP' (#10) from feature/halaman-stafPendukung-SOP into main
Reviewed-on: AnderiSaputra/student-web-if-development-kit#10
2026-06-05 08:03:14 +00:00
cygouw 8e02c85c19 merge(c4a): resolve merge conflicts with origin/main 2026-06-05 15:02:23 +07:00
ceciliayg 3830c1beeb merge upstream 2026-06-05 07:57:53 +00:00
cygouw c894f91f7d merge(c4a): combine bilingual and UI fix commits into feature branch 2026-06-05 14:56:58 +07:00
cygouw 24e1d6ec17 fix(c4a): fix 'SOP tidak ditemukan' visibility logic on page load 2026-06-05 14:51:46 +07:00
Syariffullah add634b89b Merge branch 'main' of https://git.ifuntanhub.dev/tisto/student-web-if-development-kit 2026-06-05 14:48:42 +07:00
Syariffullah 7cf590eee5 feat: Add reveal animations for smoother element transitions in style.css 2026-06-05 14:48:39 +07:00
GuavaPopper 1a90411509 Merge remote-tracking branch 'origin/feat/RODA' into feat/RODA 2026-06-05 14:43:20 +07:00
GuavaPopper 9755149741 Merge branch 'prod' into feat/RODA 2026-06-05 14:42:01 +07:00
GuavaPopper 35f20b86f7 Aktifkan proxy dashboard di Vite dev 2026-06-05 14:39:18 +07:00
Raditya_Indra_Putranto afe51be562 merge upstream 2026-06-05 07:37:36 +00:00
Raditya_Indra_Putranto 5c50801bd5 merge upstream 2026-06-05 07:37:27 +00:00
AufaHafizah 561251efe5 Merge pull request 'docs: revisi README C4A untuk menyesuaikan format data JSON bilingual' (#9) from docs/c4a-readme into main
Reviewed-on: AnderiSaputra/student-web-if-development-kit#9
2026-06-05 07:34:51 +00:00
izu 7775e3f17d Merge pull request 'feat: header dan footer sesuai standar pada halaman informasi penerimaan mahasiswa baru' (#21) from FH/student-web-if-development-kit:main into main
Reviewed-on: izu/student-web-if-development-kit#21
Approved
2026-06-05 07:34:25 +00:00
tistopandita d8c33d7a3d localization global 2026-06-05 14:28:40 +07:00
FH 71433c3392 Merge pull request 'feat: Migrasi konten grup ke template header/footer' (#6) from develop into main
Reviewed-on: FH/student-web-if-development-kit#6
2026-06-05 07:24:57 +00:00
izu df63f211ec Merge pull request 'feat: tambah konten kelompok Jellyfish.Corp' (#20) from miaaurl/student-web-if-development-kit:main into main
Reviewed-on: izu/student-web-if-development-kit#20
Approved
2026-06-05 07:22:44 +00:00
FH 695e5cde91 Merge pull request 'feat: integrasi isi content sesuai dengan template header dan footer' (#5) from feature/integrasi-header-footer-template into develop
Reviewed-on: FH/student-web-if-development-kit#5
2026-06-05 07:21:21 +00:00
cygouw 946497214b docs: revisi README C4A untuk menyesuaikan format data JSON bilingual 2026-06-05 14:18:12 +07:00
FH 82b48ad366 feat: integrasi isi content sesuai dengan template header dan footer 2026-06-05 14:07:46 +07:00
miaaurl 8d7e212e5b merge upstream 2026-06-05 06:57:18 +00:00
Raditya_Indra_Putranto 06fcc936e5 merge upstream 2026-06-05 06:31:03 +00:00
izu 1548152225 Merge pull request 'Pull Request' (#18) from andrie/student-web-if-development-kit:main into main
Reviewed-on: izu/student-web-if-development-kit#18
Approved
2026-06-05 06:30:07 +00:00
GuavaPopper 7957e6d50f Aktifkan fallback proxy paralel publikasi:failover 2026-06-05 13:30:05 +07:00
andrie 40d53d1201 Merge branch 'main' into main 2026-06-05 06:28:32 +00:00
GuavaPopper 8b6364c53a Aktifkan fallback proxy paralel publikasi 2026-06-05 13:21:37 +07:00
miaaurl 6a46baad86 Merge remote-tracking branch 'upstream/main' 2026-06-05 13:20:15 +07:00
Andrie 5dc770dfe3 Update localization 2026-06-05 13:19:40 +07:00
izu 46dab9afef Merge pull request 'main(C4A): implementasi halaman staf pendukung dan SOP' (#16) from AnderiSaputra/student-web-if-development-kit:main into main
Reviewed-on: izu/student-web-if-development-kit#16
Approved
2026-06-05 06:01:49 +00:00
izu 7d1354d16a Merge pull request 'fix title index.html' (#17) from Rayhan/roda-student-web-if-development-kit:feat/RODA into main
Reviewed-on: izu/student-web-if-development-kit#17
Approved
2026-06-05 06:01:38 +00:00
Rayhan 25dbff3e7b Merge pull request 'Fix Title index.html' (#5) from prod into feat/RODA
Reviewed-on: Rayhan/roda-student-web-if-development-kit#5
2026-06-05 05:59:37 +00:00
ceciliayg ce2cb1413f merge upstream 2026-06-05 05:56:07 +00:00
AufaHafizah f48536e979 Merge pull request 'feat: tambah terjemahan bilingual untuk data SOP, FAQ, dan Staf' (#8) from feature/bilingual-support into main
Reviewed-on: AnderiSaputra/student-web-if-development-kit#8
2026-06-05 05:55:57 +00:00
cygouw 754c73caf5 feat: tambah terjemahan bilingual untuk data SOP, FAQ, dan Staf 2026-06-05 12:55:11 +07:00
z0rayy 1a29397bb3 docs: update html tittle menjadi Riset & Kelompok Bidang Keahlian | Informatika UNTAN 2026-06-05 12:54:35 +07:00
natban_8 b5bc7bad0c feat(ApeNameTeamE): add new scholarship entries 2026-06-05 12:51:28 +07:00
pancaa 13fc797791 Merge pull request 'feat: add lazyloading' (#8) from feature/lazyloading into main
Reviewed-on: #8
Reviewed-by: pancaa <d1041231077@student.untan.ac.id>
2026-06-05 05:46:20 +00:00
izu 6a5045a2ac Merge pull request '"fix(ACAAB): improve LCP, heading hierarchy, and accessibility"' (#15) from andrie/student-web-if-development-kit:fixing-performance into main
Reviewed-on: izu/student-web-if-development-kit#15
Approved
2026-06-05 05:44:04 +00:00
AufaHafizah 847ea787ca Merge pull request 'feat(C4A): implement bilingual support with i18n localization' (#7) from feature/bilingual-support into main
Reviewed-on: AnderiSaputra/student-web-if-development-kit#7
2026-06-05 05:25:59 +00:00
cygouw 6d4f5be825 feat(C4A): implement bilingual support with i18n localization 2026-06-05 12:24:48 +07:00
miaaurl 1508c5f613 Merge pull request 'fix: perbaiki filter prestasi' (#3) from fix/filter-prestasi into main
Reviewed-on: miaaurl/student-web-if-development-kit#3
Reviewed-by: Gladys23 <d1041231011@student.untan.ac.id>
2026-06-05 05:22:19 +00:00
pancaa af7d7ee75a Merge pull request 'feature/multilanguage' (#7) from feature/multilanguage into main
Reviewed-on: #7
Reviewed-by: requestha <d1041231091@student.untan.ac.id>
Reviewed-by: pancaa <d1041231077@student.untan.ac.id>
2026-06-05 05:17:21 +00:00
miaaurl 938e8284a2 fix: perbaiki filter prestasi 2026-06-05 12:06:41 +07:00
andrie e693fa3843 Merge pull request '"fix(ACAAB): improve LCP, heading hierarchy, and accessibility"' (#5) from fixing-performance into main
Reviewed-on: andrie/student-web-if-development-kit#5
2026-06-05 05:05:48 +00:00
AleksanderWilliam 49081cdfbd Merge pull request 'chore(localization): merge upstream updates and preserve C4A keys' (#6) from feature/bilingual-support into main
Reviewed-on: AnderiSaputra/student-web-if-development-kit#6
2026-06-05 05:01:40 +00:00
cygouw e8b69506b7 chore(localization): merge upstream updates and preserve C4A keys 2026-06-05 11:59:12 +07:00
Andrie 95197fab1f testing fix performance 2026-06-05 11:56:01 +07:00
cygouw a9e7ff451a Merge remote-tracking branch 'upstream/main' 2026-06-05 11:44:10 +07:00
izu 3c28313d43 Merge pull request 'Pull Request project halaman Prestasi dan Karya Mahasiswa Informatika - ACAAB' (#6) from andrie/student-web-if-development-kit:main into main
Reviewed-on: izu/student-web-if-development-kit#6
Approved
2026-06-05 04:40:49 +00:00
andrie f6999381e4 merge upstream 2026-06-05 04:34:13 +00:00
izu 8efef62933 Merge pull request 'merge team LimaEm: Kehidupan Mahasiswa dan Himpunan (HMIF)' (#14) from MuhammadFarras/student-web-if-development-kit:main into main
Reviewed-on: izu/student-web-if-development-kit#14
Approved
2026-06-05 04:33:13 +00:00
Andrie d98ddc201a Merge branch 'main' of https://git.ifuntanhub.dev/izu/student-web-if-development-kit 2026-06-05 11:30:46 +07:00
Farras 1690dcae3e Fix merge conflict dari upstream 2026-06-05 11:24:36 +07:00
andrie 994fec5456 Merge pull request 'fix(ACAAB): memperbaiki conflict localization' (#4) from fixing-layout into main
Reviewed-on: andrie/student-web-if-development-kit#4
2026-06-05 04:17:53 +00:00
AufaHafizah 992bbd7684 Merge pull request 'chore(localization): merge upstream updates and preserve C4A keys' (#5) from feature/bilingual-support into main
Reviewed-on: AnderiSaputra/student-web-if-development-kit#5
2026-06-05 04:11:15 +00:00
cygouw 0c9ece813a chore(localization): merge upstream updates and preserve C4A keys 2026-06-05 11:10:05 +07:00
Farras da38e3eb11 Fix merge konflik 2026-06-05 11:06:58 +07:00
ilham652 9b9241e923 Merge pull request 'resolve merge conflict and bug' (#7) from fix/divisi-kepengurusan-hover into main
Reviewed-on: MuhammadFarras/student-web-if-development-kit#7
2026-06-05 04:00:46 +00:00
ilham_gmail df7041340a resolve merge conflict and bug 2026-06-05 11:00:07 +07:00
izu b19c014ca6 Merge pull request 'Fix/Percepat Data Fetch dari Proxy Pertama' (#13) from Rayhan/roda-student-web-if-development-kit:feat/RODA into main
Reviewed-on: izu/student-web-if-development-kit#13
Approved
2026-06-05 03:57:02 +00:00
ilham652 0f65b47757 Merge pull request 'fix/divisi-kepengurusan-hover' (#6) from fix/divisi-kepengurusan-hover into main
Reviewed-on: MuhammadFarras/student-web-if-development-kit#6
2026-06-05 03:56:36 +00:00
ilham_gmail 48e5d9a4a9 Merge remote-tracking branch 'origin/main' into fix/divisi-kepengurusan-hover 2026-06-05 10:56:03 +07:00
destra dd27ca2d54 chore(ApeNameTeamE): hapus-file-yang-tidak-digunakan 2026-06-05 10:54:36 +07:00
Raditya_Indra_Putranto 350f2be836 merge upstream 2026-06-05 03:52:53 +00:00
Farras 78eebd2099 Merge branch 'main' of https://git.ifuntanhub.dev/MuhammadFarras/student-web-if-development-kit 2026-06-05 10:48:27 +07:00
ilham_gmail d346b7e415 move media and hover style to external style.css 2026-06-05 10:44:43 +07:00
izu 080683c34e Merge pull request 'main' (#9) from Mhanif26/student-web-if-development-kit:main into main
Reviewed-on: izu/student-web-if-development-kit#9
Approved
2026-06-05 03:43:15 +00:00
AufaHafizah b31ba4ed34 Merge pull request 'docs/c4a-readme' (#4) from docs/c4a-readme into main
Reviewed-on: AnderiSaputra/student-web-if-development-kit#4
2026-06-05 03:42:40 +00:00
AufaHafizah 83208bb622 Merge pull request 'feature/bilingual-support' (#3) from feature/bilingual-support into main
Reviewed-on: AnderiSaputra/student-web-if-development-kit#3
2026-06-05 03:42:18 +00:00
cygouw d765783877 fix(C4A): resolve index.html conflict 2026-06-05 10:41:27 +07:00
Mhanif26 28cbd24338 Merge upstream/main into main, preserving our merged localization.js 2026-06-05 10:40:27 +07:00
powji 1acefb4516 Merge pull request 'feature/fix-ukuran-foto-about' (#5) from feature/fix-ukuran-foto-about into main
Reviewed-on: MuhammadFarras/student-web-if-development-kit#5
2026-06-05 03:39:13 +00:00
powji 1e1f2a33e5 Merge pull request 'feature/fix-ukuran-foto-about' (#5) from feature/fix-ukuran-foto-about into main
Reviewed-on: MuhammadFarras/student-web-if-development-kit#5
2026-06-05 03:39:13 +00:00
Farras 9af3a1a731 Fix merge conflicts in localization.js and LimaEm index.html 2026-06-05 10:36:03 +07:00
Farras 7028fc4f30 Fix merge conflicts in localization.js and LimaEm index.html 2026-06-05 10:36:03 +07:00
cygouw f1cd7a8f5f merge branch 'main' into feature/bilingual-support 2026-06-05 10:33:20 +07:00
Mhanif26 067e19a952 chore(localization): merge upstream updates and preserve m4n1fest keys 2026-06-05 10:33:16 +07:00
cygouw ce3c2e6ae8 fix(C4A): resolve merge conflict 2026-06-05 05:24:40 +02:00
GuavaPopper 2166057a8b Percepat fetch data publikasi 2026-06-05 10:20:38 +07:00
cygouw 6430aee79e fix(C4A): resolve merge conflict on index.html - fix h1 title and remove BOM 2026-06-05 05:17:00 +02:00
powji17 4fb6fe8e84 Memperbaiki ukuran tinggi gambar pada section about agar lebih proporsional 2026-06-05 10:13:22 +07:00
powji17 f343a1c7c6 Memperbaiki ukuran tinggi gambar pada section about agar lebih proporsional 2026-06-05 10:13:22 +07:00
cygouw 2fc6b7b6bc fix(C4A): resolve conflict - fix h1 title in index.html 2026-06-05 05:11:07 +02:00
Farras 68e7c3df2a Merge branch 'feature/konten-Lima-em' 2026-06-05 10:06:45 +07:00
Farras d3d1fce164 Merge branch 'feature/konten-Lima-em' 2026-06-05 10:06:45 +07:00
Farras 103fdabc48 feat(LimaEm): memperbaikir deskripsi Study Excursion 2026-06-05 10:05:39 +07:00
Farras 9d408b1a06 feat(LimaEm): memperbaikir deskripsi Study Excursion 2026-06-05 10:05:39 +07:00
Mhanif26 3b85dff0b9 fix(hero): perbaikan tulisan prodi informatika pada bahasa indonesia 2026-06-05 10:03:52 +07:00
Bayu e7a01dd4a1 chore(LimaEm): hapus fix.js sesuai permintaan setelah sinkronisasi 2026-06-05 10:00:42 +07:00
Bayu cd792dd5f4 chore(LimaEm): hapus fix.js sesuai permintaan setelah sinkronisasi 2026-06-05 10:00:42 +07:00
Arif1031 9f22d4f11b fix(ACAAB): memperbaiki conflict localization 2026-06-05 10:00:41 +07:00
cygouw d6db9cc464 chore(localization): sync translation keys with upstream main 2026-06-05 09:33:14 +07:00
Nayyara_04 baf8843d55 Merge pull request 'development' (#7) from development into main
Reviewed-on: Mhanif26/student-web-if-development-kit#7
2026-06-05 02:32:01 +00:00
Nayyara_04 a09ddac2c6 Merge pull request 'fix(user): tambah mobile responsive padding dan perataan baris skill' (#6) from feat/responsive-layout into development
Reviewed-on: Mhanif26/student-web-if-development-kit#6
2026-06-05 02:31:01 +00:00
Mhanif26 bc173ff4ec fix(m4n1fest): tambah mobile responsive padding dan perataan baris skil 2026-06-05 09:26:44 +07:00
cygouw ab4493e589 fix: resolve merge conflict from feature/bilingual-support 2026-06-05 09:10:14 +07:00
Farras 6080ed2252 feat(LimaEm): menambahkan beberapa konten dan memperbarui beberapa link 2026-06-05 08:50:53 +07:00
Farras 22f957e0f6 feat(LimaEm): menambahkan beberapa konten dan memperbarui beberapa link 2026-06-05 08:50:53 +07:00
Nayyara_04 c9634c619f Merge pull request 'development' (#5) from development into main
Reviewed-on: Mhanif26/student-web-if-development-kit#5
2026-06-05 01:50:05 +00:00
Farras 4e38e7524b feat(LimaEm): menambahkan beberapa konten dan memperbarui beberapa link 2026-06-05 08:49:33 +07:00
Farras 9c870cf28c feat(LimaEm): menambahkan beberapa konten dan memperbarui beberapa link 2026-06-05 08:49:33 +07:00
Nayyara_04 b844ca8b23 Merge pull request 'feat(m4n1fest): menerapkan clean code pada kode dan tambah komen untuk memudahkan pembacaan' (#4) from feat/clean-code into development
Reviewed-on: Mhanif26/student-web-if-development-kit#4
2026-06-05 01:47:47 +00:00
andrie f410b390e8 Merge pull request 'fixing-layout' (#3) from fixing-layout into main
Reviewed-on: andrie/student-web-if-development-kit#3
2026-06-05 01:43:58 +00:00
Mhanif26 471ed6cd18 feat(m4n1fest): menerapkan clean code pada kode dan tambah komen untuk memudahkan pembacaan 2026-06-05 08:36:26 +07:00
cygouw 154550d3b4 Merge branch 'feature/bilingual-support' 2026-06-05 08:33:51 +07:00
Nayyara_04 874c185dc9 Merge pull request 'fix(m4n1fest): perbaikan jumlah alumni di hero dan swap table-chart layout' (#3) from fix/m4n1fest-stats-layout into development
Reviewed-on: Mhanif26/student-web-if-development-kit#3
2026-06-05 01:25:17 +00:00
Mhanif26 fe5c645cbd chore: resolve merge conflict in index.html 2026-06-05 08:10:06 +07:00
AufaHafizah 3975798ba2 Merge pull request 'feature/wa-link-protection' (#2) from feature/wa-link-protection into main
Reviewed-on: AnderiSaputra/student-web-if-development-kit#2
2026-06-05 00:50:40 +00:00
Mhanif26 ec0288fd8a fix(m4n1fest): perbaikan jumlah alumni di hero dan swap table-chart layout 2026-06-05 07:48:56 +07:00
AufaHafizah b943518500 Merge pull request 'feature/halaman-stafPendukung-SOP' (#1) from feature/halaman-stafPendukung-SOP into main
Reviewed-on: AnderiSaputra/student-web-if-development-kit#1
2026-06-05 00:48:19 +00:00
Nayyara_04 7cf671d826 Merge pull request 'feat: menambahkan logo perusahaan dan gambar dokumentasi alumni high achievers' (#2) from feature/add-assets-high-achievers into development
Reviewed-on: Mhanif26/student-web-if-development-kit#2
2026-06-04 21:09:19 +00:00
cygouw 3ead384ec9 chore(c4a): update assignment page title 2026-06-05 01:46:57 +07:00
cygouw d2ae63fe62 docs(c4a): add readme for data management and template 2026-06-04 23:50:12 +07:00
cygouw 5ccdba561e fix(ui): re-init animations after language toggle to prevent content from disappearing 2026-06-04 23:40:06 +07:00
cygouw 2edbcb0c88 feat(ui): add language switcher for Indonesian and English 2026-06-04 23:31:09 +07:00
cygouw 39adcc21ee feat(staff): protect WhatsApp links from basic web scraping 2026-06-04 23:06:53 +07:00
Gladys23 d52bd7f30d Merge pull request 'feat: tambah fitur bilingual' (#2) from feature/bilingual into main
Reviewed-on: miaaurl/student-web-if-development-kit#2
Reviewed-by: LuthfiHadiNugroho <d1041231033@student.untan.ac.id>
Reviewed-by: Gladys23 <d1041231011@student.untan.ac.id>
2026-06-04 15:38:30 +00:00
cygouw 981bb4d93b feat(C4A): implementasi hero section, staf section, sop section, faq section 2026-06-04 22:32:49 +07:00
ilham652 563af0ec56 Merge pull request 'Fix hover behavior on divisi kepengurusan section and color in himpunan (HMIF)' (#3) from fix/divisi-kepengurusan-hover into main
Reviewed-on: MuhammadFarras/student-web-if-development-kit#3
2026-06-04 15:28:13 +00:00
ilham652 f8500db1dd Merge pull request 'Fix hover behavior on divisi kepengurusan section and color in himpunan (HMIF)' (#3) from fix/divisi-kepengurusan-hover into main
Reviewed-on: MuhammadFarras/student-web-if-development-kit#3
2026-06-04 15:28:13 +00:00
ilham_gmail a942826266 Fix hover behavior on divisi kepengurusan section and color in himpunan (HMIF) 2026-06-04 22:25:17 +07:00
ilham_gmail 7f2df4b08e Fix hover behavior on divisi kepengurusan section and color in himpunan (HMIF) 2026-06-04 22:25:17 +07:00
miaaurl 8718202321 feat: tambah fitur bilingual 2026-06-04 22:22:28 +07:00
Mhanif26 c772aa2d83 feat: menambahkan logo perusahaan dan gambar dokumentasi alumni high achievers 2026-06-04 21:28:48 +07:00
Bayu 153de4dc92 fix(LimaEm): partner aktif ring kuning + glow, partner legacy warna asli saat hover 2026-06-04 21:19:04 +07:00
Bayu fa46a434c2 fix(LimaEm): partner aktif ring kuning + glow, partner legacy warna asli saat hover 2026-06-04 21:19:04 +07:00
Bayu e792afec16 fix(LimaEm): revert Kegiatan Unggulan to standard max-w-7xl container per user request 2026-06-04 21:07:51 +07:00
Bayu 3076c922e3 fix(LimaEm): perfect full-width layout matching max-w-7xl standard container and precise gradient inline style 2026-06-04 21:02:58 +07:00
Bayu 3e87d77b04 chore(LimaEm): add fix.js 2026-06-04 21:00:36 +07:00
Nayyara_04 f75c04c052 Merge pull request 'feat(alumni): integrasi data dinamis skill alumni dari google sheets' (#1) from feature/integrasi-data-skill into development
Reviewed-on: Mhanif26/student-web-if-development-kit#1
2026-06-04 13:57:10 +00:00
Bayu 6a6f316e5f Revert "fix(LimaEm): re-apply full-width layout and inline hero styles after merging origin/main"
This reverts commit 3b52480c81.
2026-06-04 20:55:56 +07:00
Bayu 3b52480c81 fix(LimaEm): re-apply full-width layout and inline hero styles after merging origin/main 2026-06-04 20:52:07 +07:00
Bayu eef091582d Merge branch 'main' into enhance/hover-effects-and-interactions 2026-06-04 20:50:36 +07:00
muthianura 73964fe874 feat(alumni): integrasi data dinamis skill alumni dari google sheets 2026-06-04 20:46:39 +07:00
Bayu 766a930542 fix(LimaEm): Hero 100% inline style — gradient biru, badge kuning, heading, dan button cocok referensi 2026-06-04 20:41:40 +07:00
cw dfc84bcbe9 fix layout mobile 2026-06-04 20:40:37 +07:00
powji17 c8cb265394 fix(lima-em): perbaiki layout ukuran card kegiatan unggulan dan grid galeri keseruan 2026-06-04 20:38:55 +07:00
michelle_olivia c103c837ea Merge pull request 'feat: tambah isi konten keseluruhan' (#1) from feature/content into main
Reviewed-on: miaaurl/student-web-if-development-kit#1
Reviewed-by: Gladys23 <d1041231011@student.untan.ac.id>
2026-06-04 13:33:21 +00:00
Bayu 35f1dd3865 fix(LimaEm): break sections out of container so content fills full viewport width edge-to-edge 2026-06-04 20:27:33 +07:00
BobbyAndrean 141eeeb629 menyesuaikan layout setelah migrasi ke html, terkhusus pada layout card achievement dan portofolio 2026-06-04 20:19:00 +07:00
Bayu 54b00194a8 fix(LimaEm): make all sections full-width, fix button hover inline style, add yellow glow on active partners 2026-06-04 20:16:33 +07:00
miaaurl 0768a9c04a feat: tambah isi konten keseluruhan 2026-06-04 20:09:31 +07:00
powji b42edb3356 Merge pull request 'feature/konten-lima-em' (#2) from feature/konten-lima-em into main
Reviewed-on: MuhammadFarras/student-web-if-development-kit#2
2026-06-04 13:05:55 +00:00
powji17 7996ee0186 feat(lima-em): menambahkan implementasi fitur bilingual ID-EN pada konten 2026-06-04 20:01:23 +07:00
Bayu bedac5851e enhance(LimaEm): restore missing hover/interaction effects across all sections 2026-06-04 20:01:05 +07:00
Bayu 034b131c12 fix(LimaEm): tidy up Ruang Tumbuh layout and add Kehidupan Mahasiswa hero gradient 2026-06-04 19:40:03 +07:00
Mhanif26 3310c152c3 Feat: Push awal seluruh kode proyek ke repo student-web-if-development-kit 2026-06-04 19:30:55 +07:00
Andrie 09498578e9 push editan terbaru hasil migrasi ke index.html 2026-06-04 19:22:18 +07:00
Andrie 7620308dea Migrasi dari tech stack sebelumnya ke tech stack native vanilla 2026-06-04 19:21:54 +07:00
Mhanif26 429d6aa4d0 Feat: Push awal seluruh kode proyek ke repo student-web-if-development-kit 2026-06-04 19:21:53 +07:00
powji17 243541d1e4 feat(lima-em): migrasi seluruh konten section dan script ke development kit 2026-06-04 18:32:30 +07:00
powji e07d70093b Merge pull request 'feat/LimaEm-group' (#1) from feat/LimaEm-group into main
Reviewed-on: MuhammadFarras/student-web-if-development-kit#1
2026-06-04 10:54:09 +00:00
Farras 260c896230 feat(LimaEm): tambah assets gambar (compressed) 2026-06-04 17:03:11 +07:00
Farras 7bc9588a4a feat(LimaEm): tambah halaman Kehidupan Mahasiswa & Himpunan (HMIF) 2026-06-04 16:55:39 +07:00
Andrie c5a476c6bd Merging 2026-06-04 16:07:17 +07:00
Andrie fd477e4c22 fix ACAAB language sync with header selection 2026-06-04 15:57:07 +07:00
BobbyAndrean b5fba9238e fixing data fetching from external API, spesifically in achievement and projects categories and name of team member 2026-06-04 15:47:47 +07:00
Mhanif26 3516c5a3de Sync: Pull ulang kode legacy dari upstream student-web-if-development-kit 2026-06-04 09:24:50 +07:00
Mhanif26 7df1862dcc Re-upload: Struktur folder bersih tanpa submodule 2026-06-04 09:15:05 +07:00
Mhanif26 b0b2c4fda3 Fix: Mengubah submodule menjadi folder biasa dan push seluruh kode program 2026-06-04 09:08:38 +07:00
Mhanif26 ad6f7a13c6 Initial commit: Proyek Halaman Informasi Alumni 2026-06-04 08:58:08 +07:00
Andrie b8099c1dca feat(acaab): implementasi halaman prestasi & karya mahasiswa 2026-05-29 10:30:26 +07:00
95 changed files with 21163 additions and 5296 deletions
+2921 -1600
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
node_modules/
Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

+364 -33
View File
@@ -1,5 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="id" class="scroll-smooth"> <html lang="id" class="scroll-smooth">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -7,9 +8,12 @@
<link rel="stylesheet" href="../../assets/global/output.css"> <link rel="stylesheet" href="../../assets/global/output.css">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto+Condensed:ital,wght@0,100..900;1,100..900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet"> <link
href="https://fonts.googleapis.com/css2?family=Roboto+Condensed:ital,wght@0,100..900;1,100..900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"
rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style> <link rel="stylesheet" href="./style.css">
<style>
/* ===== MOBILE NAV STABILITY FIX ===== /* ===== MOBILE NAV STABILITY FIX =====
Prevents logo bar from collapsing/shifting on Android when Prevents logo bar from collapsing/shifting on Android when
address bar hides/shows and triggers viewport reflow. address bar hides/shows and triggers viewport reflow.
@@ -293,6 +297,7 @@
} }
</style> </style>
</head> </head>
<body class="text-slate-800 antialiased bg-white selection:bg-untan-yellow selection:text-black"> <body class="text-slate-800 antialiased bg-white selection:bg-untan-yellow selection:text-black">
<a href="#main-content" class="skip-link" data-i18n="skip_to_content">Langsung ke Konten Utama</a> <a href="#main-content" class="skip-link" data-i18n="skip_to_content">Langsung ke Konten Utama</a>
@@ -1139,18 +1144,341 @@
</div> </div>
</div> </div>
<main id="main-content" class="pt-[88px] lg:pt-[160px] min-h-screen bg-white"> <main id="main-content" class="pt-[88px] lg:pt-[160px] min-h-screen bg-white">
<div class="container mx-auto px-4 lg:px-8 py-12"> <div class="container mx-auto px-4 lg:px-8 py-12">
<div class="mb-8 pb-8 border-b border-slate-200"> <div class="mb-8 pb-8 border-b border-slate-200">
<span class="bg-untan-yellow text-slate-900 text-[10px] font-bold uppercase tracking-widest px-2 py-0.5 inline-block mb-3">Tugas Halaman Statis</span> <span
class="bg-untan-yellow text-slate-900 text-[10px] font-bold uppercase tracking-widest px-2 py-0.5 inline-block mb-3">Tugas
Halaman Statis</span>
<h1 class="text-3xl lg:text-4xl font-bold text-slate-900 mb-2">Sorotan dan Prestasi Karya Mahasiswa</h1> <h1 class="text-3xl lg:text-4xl font-bold text-slate-900 mb-2">Sorotan dan Prestasi Karya Mahasiswa</h1>
<p class="text-slate-500 text-sm">Kelompok 06 &mdash; ACAAB &nbsp;&middot;&nbsp; Informatika UNTAN 2026</p> <p class="text-slate-500 text-sm">Kelompok 06 &mdash; ACAAB &nbsp;&middot;&nbsp; Informatika UNTAN 2026
</p>
</div> </div>
<div id="assignment-body" class="prose prose-slate max-w-none"> <div id="assignment-body" class="prose prose-slate max-w-none">
<!-- ================= START: BAGIAN YANG DIEDIT KELOMPOK ================= --> <!-- ================= START: BAGIAN YANG DIEDIT KELOMPOK ================= -->
<p>Tulis konten halaman kelompok di sini.</p>
<h3>Subjudul</h3> <!-- ── BREADCRUMB ─────────────────────────────────────────────────────── -->
<p>Gunakan paragraf, list, tabel, gambar, dan elemen konten lainnya.</p> <nav class="not-prose py-3" aria-label="Breadcrumb">
<ol class="flex items-center gap-1 text-[10px] text-slate-500 font-medium tracking-wide uppercase">
<li><a href="https://informatika.untan.ac.id" target="_blank" rel="noopener noreferrer"
class="hover:text-blue-600 transition-colors">Beranda</a></li>
<li><span class="mx-1 text-slate-300" aria-hidden="true">/</span></li>
<li><span class="text-slate-800 font-bold" aria-current="page"
data-i18n="grp_acaab_hero_badge">Mahasiswa &amp; Prestasi</span></li>
</ol>
</nav>
<!-- ── HERO ───────────────────────────────────────────────────────────── -->
<section id="acaab-hero"
class="not-prose acaab-full-bleed bg-untan-navy text-white relative overflow-hidden"
style="padding-top:3rem;padding-bottom:0;margin-top:0.5rem;">
<!-- Decorative glow -->
<div class="absolute top-0 right-0 w-80 h-80 rounded-full pointer-events-none"
style="background:rgba(254,180,1,0.08);filter:blur(80px);margin-right:-5rem;margin-top:-5rem;">
</div>
<div class="container mx-auto px-4 lg:px-8">
<div style="display:grid;grid-template-columns:1fr;gap:1.5rem;align-items:center;"
class="lg-hero-grid">
<!-- Left: text -->
<div style="padding:1.5rem 0 2rem;max-width:640px;">
<span class="acaab-fade-in"
style="display:inline-block;background:#feb401;color:#0f172a;font-size:0.65rem;font-weight:700;letter-spacing:0.15em;text-transform:uppercase;padding:0.2rem 0.625rem;margin-bottom:0.75rem;"
data-i18n="grp_acaab_hero_badge">MAHASISWA &amp; PRESTASI</span>
<h2 class="acaab-fade-in"
style="font-size:clamp(1.875rem,3.5vw,2.5rem);font-weight:700;line-height:1.2;letter-spacing:-0.01em;margin:0 0 0.75rem;font-family:'Roboto Condensed',sans-serif;"
data-i18n="grp_acaab_hero_title">Sorotan dan Prestasi Karya Mahasiswa</h2>
<p class="acaab-fade-in"
style="color:rgba(255,255,255,0.7);font-size:0.9375rem;line-height:1.7;margin:0;"
data-i18n="grp_acaab_hero_desc">Temukan pencapaian luar biasa mahasiswa Informatika
UNTAN.</p>
</div>
<!-- Right: image -->
<div id="acaab-hero-img-wrap"
style="display:flex;justify-content:flex-end;align-items:flex-end;position:relative;">
<div
style="position:absolute;bottom:0;width:280px;height:280px;background:rgba(103,232,249,0.06);border-radius:50%;filter:blur(60px);">
</div>
<div style="position:relative;">
<img src="./hero.png" alt="Mahasiswa Informatika UNTAN"
width="612" height="407"
fetchpriority="high"
style="position:relative;z-index:10;width:100%;max-width:320px;height:auto;object-fit:contain;opacity:0.95;filter:drop-shadow(0 15px 35px rgba(0,0,0,0.25));transition:transform 0.5s;"
onerror="this.style.display='none'">
<!-- Gradient fades over image -->
<div
style="position:absolute;bottom:0;left:0;width:100%;height:8rem;background:linear-gradient(to top,#003150,transparent);z-index:20;pointer-events:none;">
</div>
<div
style="position:absolute;top:0;left:0;height:100%;width:4rem;background:linear-gradient(to right,#003150,transparent);z-index:20;pointer-events:none;">
</div>
</div>
</div>
</div>
</div>
<!-- Responsive: 2-column on desktop -->
<style>
@media (min-width:1024px) {
.lg-hero-grid {
grid-template-columns: 1fr 1fr !important;
}
#acaab-hero-img-wrap img {
max-width: 420px !important;
}
}
</style>
</section>
<!-- ── STATS (yellow card) ────────────────────────────────────────────── -->
<div id="acaab-stats" class="not-prose" style="padding:1.5rem 0 0;">
<div
style="background:#feb401;border-radius:0.75rem;box-shadow:0 10px 30px -10px rgba(253,184,19,0.4);padding:1.25rem 1.5rem 1.5rem;">
<div style="display:flex;flex-wrap:wrap;gap:1.5rem;">
<!-- Prestasi Block -->
<div style="flex:4;min-width:240px;">
<div style="background:#003150;border-radius:0.5rem;padding:0.5rem 0.75rem;text-align:center;margin-bottom:1.25rem;">
<span style="color:white;font-size:0.875rem;font-weight:800;letter-spacing:0.12em;text-transform:uppercase;"
data-i18n="grp_acaab_stats_total_ach">Total Prestasi</span>
</div>
<div style="display:flex;gap:0.25rem;">
<!-- Prestasi Mahasiswa (total) -->
<div style="flex:1;text-align:center;padding:0 0.15rem;">
<div id="stat-total-ach"
style="font-size:clamp(2rem,4.5vw,4rem);font-weight:900;color:#003150;line-height:1;font-family:'Roboto Condensed',sans-serif;">
0</div>
<div style="font-size:clamp(0.5rem,1.2vw,0.85rem);font-weight:800;text-transform:uppercase;letter-spacing:0.08em;color:#003150;margin-top:0.5rem;line-height:1.3;word-break:break-word;"
data-i18n="grp_acaab_stats_label_ach">Prestasi<br>Mahasiswa</div>
</div>
<!-- Internasional -->
<div style="flex:1;text-align:center;padding:0 0.15rem;">
<div id="stat-intl"
style="font-size:clamp(2rem,4.5vw,4rem);font-weight:900;color:#003150;line-height:1;font-family:'Roboto Condensed',sans-serif;">
0</div>
<div style="font-size:clamp(0.5rem,1.2vw,0.85rem);font-weight:800;text-transform:uppercase;letter-spacing:0.08em;color:#003150;margin-top:0.5rem;word-break:break-word;"
data-i18n="grp_acaab_stats_intl">Internasional</div>
</div>
<!-- Nasional -->
<div style="flex:1;text-align:center;padding:0 0.15rem;">
<div id="stat-national"
style="font-size:clamp(2rem,4.5vw,4rem);font-weight:900;color:#003150;line-height:1;font-family:'Roboto Condensed',sans-serif;">
0</div>
<div style="font-size:clamp(0.5rem,1.2vw,0.85rem);font-weight:800;text-transform:uppercase;letter-spacing:0.08em;color:#003150;margin-top:0.5rem;word-break:break-word;"
data-i18n="grp_acaab_stats_national">Nasional</div>
</div>
<!-- Regional -->
<div style="flex:1;text-align:center;padding:0 0.15rem;">
<div id="stat-regional"
style="font-size:clamp(2rem,4.5vw,4rem);font-weight:900;color:#003150;line-height:1;font-family:'Roboto Condensed',sans-serif;">
0</div>
<div style="font-size:clamp(0.5rem,1.2vw,0.85rem);font-weight:800;text-transform:uppercase;letter-spacing:0.08em;color:#003150;margin-top:0.5rem;word-break:break-word;"
data-i18n="grp_acaab_stats_regional">Regional</div>
</div>
</div>
</div>
<!-- Proyek Block -->
<div style="flex:1;min-width:120px;">
<div style="background:#003150;border-radius:0.5rem;padding:0.5rem 0.75rem;text-align:center;margin-bottom:1.25rem;">
<span style="color:white;font-size:0.875rem;font-weight:800;letter-spacing:0.12em;text-transform:uppercase;"
data-i18n="grp_acaab_stats_total_proj">Total Portofolio</span>
</div>
<div style="display:flex;justify-content:center;">
<!-- Proyek Mahasiswa (total) -->
<div style="text-align:center;padding:0 0.15rem;">
<div id="stat-total-proj"
style="font-size:clamp(2rem,4.5vw,4rem);font-weight:900;color:#003150;line-height:1;font-family:'Roboto Condensed',sans-serif;">
0</div>
<div style="font-size:clamp(0.5rem,1.2vw,0.85rem);font-weight:800;text-transform:uppercase;letter-spacing:0.08em;color:#003150;margin-top:0.5rem;line-height:1.3;word-break:break-word;"
data-i18n="grp_acaab_stats_label_proj">Proyek<br>Mahasiswa</div>
</div>
</div>
</div>
</div>
</div>
<!-- Data note -->
<p id="acaab-data-note"
style="font-size:0.6rem;font-weight:600;color:#94a3b8;letter-spacing:0.05em;text-align:right;padding-right:0.25rem;margin:0.5rem 0 0;">
</p>
</div>
<!-- ── ACHIEVEMENT GRID ────────────────────────────────────────────────── -->
<section id="acaab-ach-section" class="not-prose" style="padding:2.5rem 0 3rem;">
<div style="margin-bottom:2rem;">
<h2 style="font-size:clamp(1.5rem,3vw,2.25rem);font-weight:900;color:#003150;margin:0 0 0.5rem;font-family:'Roboto Condensed',sans-serif;"
data-i18n="grp_acaab_ach_title">Prestasi Mahasiswa</h2>
<p style="color:#64748b;font-size:1rem;max-width:42rem;margin:0;line-height:1.6;"
data-i18n="grp_acaab_ach_desc">Daftar pencapaian dan penghargaan yang diraih oleh mahasiswa Informatika UNTAN di berbagai kompetisi.</p>
</div>
<!-- Filter panel — gold top border matching original -->
<div id="acaab-ach-filters"
style="background:rgba(255,255,255,0.8);backdrop-filter:blur(12px);padding:1rem;border-radius:0.5rem;box-shadow:0 1px 3px rgba(0,0,0,0.08);border-top:4px solid #feb401;margin-bottom:2rem;">
<!-- Row 1: Label + Reset -->
<div
style="display:flex;align-items:center;justify-content:space-between;margin-bottom:0.75rem;">
<div
style="display:flex;align-items:center;gap:0.5rem;font-size:0.875rem;font-weight:700;color:#003150;font-family:'Roboto Condensed',sans-serif;">
<i class="fas fa-filter" style="font-size:0.8rem;"></i>
<span>Filter Prestasi</span>
</div>
<button id="acaab-ach-reset"
style="display:none;align-items:center;gap:0.25rem;font-size:0.75rem;font-weight:700;color:#64748b;background:none;border:none;cursor:pointer;"
data-i18n="btn_reset_filter">
<i class="fas fa-times" style="font-size:0.7rem;"></i> Reset Filter
</button>
</div>
<!-- Row 2: Search -->
<div style="position:relative;margin-bottom:0.75rem;">
<i class="fas fa-search"
style="position:absolute;left:0.75rem;top:50%;transform:translateY(-50%);color:#94a3b8;font-size:0.8rem;pointer-events:none;"></i>
<input id="acaab-ach-search" type="search"
style="width:100%;background:#f8fafc;color:#334155;font-size:0.875rem;border-radius:0.375rem;padding:0.5rem 0.75rem 0.5rem 2.25rem;border:1px solid #e2e8f0;outline:none;box-sizing:border-box;"
placeholder="Cari prestasi..." data-i18n-placeholder="grp_acaab_ach_search">
</div>
<!-- Row 3: Dropdowns -->
<div style="display:flex;flex-wrap:wrap;gap:0.625rem;">
<select id="acaab-ach-level" aria-label="Filter tingkat prestasi"
style="flex:1;min-width:130px;background:#f8fafc;color:#334155;font-size:0.8rem;font-weight:700;border-radius:0.375rem;padding:0.5rem 0.75rem;border:1px solid #e2e8f0;cursor:pointer;"></select>
<select id="acaab-ach-cat" aria-label="Filter kategori prestasi"
style="flex:1;min-width:130px;background:#f8fafc;color:#334155;font-size:0.8rem;font-weight:700;border-radius:0.375rem;padding:0.5rem 0.75rem;border:1px solid #e2e8f0;cursor:pointer;"></select>
<select id="acaab-ach-year" aria-label="Filter tahun prestasi"
style="flex:1;min-width:120px;background:#f8fafc;color:#334155;font-size:0.8rem;font-weight:700;border-radius:0.375rem;padding:0.5rem 0.75rem;border:1px solid #e2e8f0;cursor:pointer;"></select>
</div>
</div>
<!-- Result count -->
<p id="acaab-ach-count" style="font-size:0.8rem;color:#64748b;margin-bottom:1rem;"></p>
<!-- Cards grid -->
<div id="acaab-ach-grid"
style="display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:2rem;justify-content:center;">
</div>
<!-- Pagination buttons -->
<div style="text-align:center;margin-top:2rem;display:flex;gap:0.75rem;justify-content:center;">
<button id="acaab-ach-more"
style="display:none;padding:0.625rem 1.5rem;background:#003150;color:white;border:none;border-radius:0.5rem;font-weight:700;font-size:0.875rem;cursor:pointer;"
data-i18n="grp_acaab_ach_load_more">Tampilkan Lebih Banyak</button>
<button id="acaab-ach-less"
style="display:none;padding:0.625rem 1.5rem;background:white;color:#003150;border:1px solid #003150;border-radius:0.5rem;font-weight:700;font-size:0.875rem;cursor:pointer;"
data-i18n="grp_acaab_ach_show_less">Tampilkan Lebih Sedikit</button>
</div>
</section>
<!-- ── PROJECT CAROUSEL ───────────────────────────────────────────────── -->
<section id="acaab-proj-section" class="not-prose acaab-full-bleed" style="padding:2.5rem 0 3rem;background:#f8fafc;">
<div class="container mx-auto px-4 lg:px-8">
<!-- Section header row: title + scroll buttons (seperti di React source) -->
<div
style="display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:1rem;margin-bottom:1.5rem;">
<div style="flex:1;min-width:250px;">
<h2 style="font-size:clamp(1.5rem,3vw,2.25rem);font-weight:900;color:#003150;margin:0 0 0.5rem;font-family:'Roboto Condensed',sans-serif;"
data-i18n="grp_acaab_proj_title">Top Projects Portofolio</h2>
<p style="color:#64748b;font-size:1rem;max-width:42rem;margin:0;line-height:1.6;"
data-i18n="grp_acaab_proj_desc">Jelajahi inovasi digital dan aplikasi nyata yang dikembangkan oleh mahasiswa Informatika UNTAN.</p>
</div>
</div>
<!-- Filter panel -->
<div id="acaab-proj-filters"
style="background:rgba(255,255,255,0.8);backdrop-filter:blur(12px);padding:1rem;border-radius:0.5rem;box-shadow:0 1px 3px rgba(0,0,0,0.08);border-top:4px solid #feb401;margin-bottom:1.25rem;">
<div
style="display:flex;align-items:center;justify-content:space-between;margin-bottom:0.75rem;">
<div
style="display:flex;align-items:center;gap:0.5rem;font-size:0.875rem;font-weight:700;color:#003150;font-family:'Roboto Condensed',sans-serif;">
<i class="fas fa-filter" style="font-size:0.8rem;"></i>
<span>Filter Portofolio</span>
</div>
<button id="acaab-proj-reset"
style="display:none;align-items:center;gap:0.25rem;font-size:0.75rem;font-weight:700;color:#64748b;background:none;border:none;cursor:pointer;"
data-i18n="btn_reset_filter">
<i class="fas fa-times" style="font-size:0.7rem;"></i> Reset Filter
</button>
</div>
<div style="position:relative;margin-bottom:0.75rem;">
<i class="fas fa-search"
style="position:absolute;left:0.75rem;top:50%;transform:translateY(-50%);color:#94a3b8;font-size:0.8rem;pointer-events:none;"></i>
<input id="acaab-proj-search" type="search"
style="width:100%;background:#f8fafc;color:#334155;font-size:0.875rem;border-radius:0.375rem;padding:0.5rem 0.75rem 0.5rem 2.25rem;border:1px solid #e2e8f0;outline:none;box-sizing:border-box;"
placeholder="Cari karya..." data-i18n-placeholder="grp_acaab_proj_search">
</div>
<div style="display:flex;flex-wrap:wrap;gap:0.625rem;">
<select id="acaab-proj-cat" aria-label="Filter kategori portofolio"
style="flex:1;min-width:130px;background:#f8fafc;color:#334155;font-size:0.8rem;font-weight:700;border-radius:0.375rem;padding:0.5rem 0.75rem;border:1px solid #e2e8f0;cursor:pointer;"></select>
<select id="acaab-proj-year" aria-label="Filter tahun portofolio"
style="flex:1;min-width:120px;background:#f8fafc;color:#334155;font-size:0.8rem;font-weight:700;border-radius:0.375rem;padding:0.5rem 0.75rem;border:1px solid #e2e8f0;cursor:pointer;"></select>
</div>
</div>
<!-- Scroll hint -->
<p style="font-size:0.7rem;color:#94a3b8;text-align:center;margin-bottom:0.75rem;"
data-i18n="grp_acaab_proj_scroll_hint">← Geser untuk melihat lebih banyak →</p>
<!-- Carousel track -->
<div id="acaab-proj-track" class="acaab-carousel-track acaab-hide-scrollbar"
style="display:flex;gap:1.25rem;overflow-x:auto;padding-bottom:1rem;"></div>
<!-- Scroll control buttons (bawah) -->
<div style="display:flex;justify-content:center;align-items:center;gap:1rem;margin-top:1rem;">
<button id="acaab-proj-prev" aria-label="Geser kiri"
style="width:3rem;height:3rem;border-radius:50%;border:2px solid rgba(0,49,80,0.2);display:flex;align-items:center;justify-content:center;color:#003150;background:white;cursor:pointer;transition:all 0.2s;">
<i class="fas fa-chevron-left" style="font-size:1rem;"></i>
</button>
<button id="acaab-proj-next" aria-label="Geser kanan"
style="width:3rem;height:3rem;border-radius:50%;border:2px solid rgba(0,49,80,0.2);display:flex;align-items:center;justify-content:center;color:#003150;background:white;cursor:pointer;transition:all 0.2s;">
<i class="fas fa-chevron-right" style="font-size:1rem;"></i>
</button>
</div>
<!-- Load more -->
<div style="text-align:center;margin-top:1.5rem;">
<button id="acaab-proj-more"
style="display:none;padding:0.625rem 1.5rem;background:#003150;color:white;border:none;border-radius:0.5rem;font-weight:700;font-size:0.875rem;cursor:pointer;"
data-i18n="grp_acaab_proj_load_more">Tampilkan Lebih Banyak</button>
</div>
</div><!-- /inner container -->
</section>
<!-- ── CTA ────────────────────────────────────────────────────────────── -->
<section class="not-prose acaab-full-bleed bg-untan-navy"
style="padding:3.5rem 0; margin-bottom: 3rem;">
<div class="container mx-auto px-4 lg:px-8">
<!-- Inner card: kuning transparan dengan border, rounded-2xl -->
<div
style="background:rgba(254,180,1,0.08);border:1px solid rgba(254,180,1,0.2);border-radius:1rem;padding:2.5rem 2rem;">
<div class="acaab-cta-inner">
<!-- Kiri: teks -->
<div>
<span
style="display:inline-block;background:#feb401;color:#0f172a;font-size:0.625rem;font-weight:700;letter-spacing:0.15em;text-transform:uppercase;padding:0.2rem 0.5rem;border-radius:2px;margin-bottom:1rem;"
data-i18n="grp_acaab_cta_badge">HUBUNGI KAMI</span>
<h2 style="color:white;font-size:clamp(1.5rem,2.5vw,1.875rem);font-weight:700;margin:0 0 0.75rem;font-family:'Roboto Condensed',sans-serif;line-height:1.2;"
data-i18n="grp_acaab_cta_title">Ingin Tahu Lebih Lanjut?</h2>
<p style="color:#cbd5e1;font-size:0.9375rem;line-height:1.65;max-width:480px;margin:0;"
data-i18n="grp_acaab_cta_desc">Tertarik bergabung dengan Program Studi
Informatika UNTAN, mendaftarkan prestasi mahasiswa, atau menjalin kerja sama?
Hubungi kami sekarang.</p>
</div>
<!-- Kanan: tombol WA -->
<div style="flex-shrink:0;">
<a id="acaab-cta-wa" href="#" target="_blank" rel="noopener noreferrer"
style="display:inline-flex;align-items:center;gap:0.625rem;background:#25D366;color:white;padding:0.75rem 1.5rem;border-radius:9999px;font-weight:700;font-size:0.9375rem;text-decoration:none;box-shadow:0 4px 15px rgba(37,211,102,0.25);white-space:nowrap;transition:background 0.2s,transform 0.2s;">
<i class="fab fa-whatsapp" style="font-size:1.125rem;"></i>
<span data-i18n="grp_acaab_cta_btn">Chat via WhatsApp</span>
</a>
</div>
</div>
</div>
</div>
</section>
<!-- Modal container (diisi oleh main.js) -->
<div id="acaab-modal" class="not-prose"></div>
<!-- ================= END: BAGIAN YANG DIEDIT KELOMPOK ================= --> <!-- ================= END: BAGIAN YANG DIEDIT KELOMPOK ================= -->
</div> </div>
</div> </div>
@@ -1215,8 +1543,8 @@
</div> </div>
<div class="w-full relative z-0 mt-4 pointer-events-none flex justify-center"> <div class="w-full relative z-0 mt-4 pointer-events-none flex justify-center">
<img src="../../assets/global/footer.png" alt="Footer Illustration" class="w-full h-auto object-cover object-bottom" <img src="../../assets/global/footer.png" alt="Footer Illustration"
style="object-position: bottom;"> class="w-full h-auto object-cover object-bottom" style="object-position: bottom;">
</div> </div>
</footer> </footer>
@@ -1312,27 +1640,30 @@
})(); })();
</script> </script>
<script> <script>
(function() { (function () {
const mobileBtn = document.getElementById('mobile-menu-button'); const mobileBtn = document.getElementById('mobile-menu-button');
const mobileMenu = document.getElementById('mobile-menu'); const mobileMenu = document.getElementById('mobile-menu');
const closeMobileBtn= document.getElementById('close-mobile-menu'); const closeMobileBtn = document.getElementById('close-mobile-menu');
if (mobileBtn && mobileMenu) { if (mobileBtn && mobileMenu) {
mobileBtn.addEventListener('click', function() { mobileBtn.addEventListener('click', function () {
const isOpen = !mobileMenu.classList.contains('-translate-x-full'); const isOpen = !mobileMenu.classList.contains('-translate-x-full');
mobileMenu.classList.toggle('-translate-x-full'); mobileMenu.classList.toggle('-translate-x-full');
mobileBtn.setAttribute('aria-expanded', isOpen ? 'false' : 'true'); mobileBtn.setAttribute('aria-expanded', isOpen ? 'false' : 'true');
}); });
} }
if (closeMobileBtn && mobileMenu) { if (closeMobileBtn && mobileMenu) {
closeMobileBtn.addEventListener('click', function() { closeMobileBtn.addEventListener('click', function () {
mobileMenu.classList.add('-translate-x-full'); mobileMenu.classList.add('-translate-x-full');
if (mobileBtn) mobileBtn.setAttribute('aria-expanded', 'false'); if (mobileBtn) mobileBtn.setAttribute('aria-expanded', 'false');
}); });
} }
})(); })();
</script> </script>
<script src="../../assets/global/localization.js"></script> <script src="../../assets/global/localization.js"></script>
<script src="./localization.js"></script>
<script src="./main.js"></script>
</body> </body>
</html>
</html>
+123
View File
@@ -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);
}());
+837
View File
@@ -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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function getLang() {
try {
return localStorage.getItem(LANG_KEY) ||
(navigator.language && navigator.language.startsWith('en') ? 'en' : 'id');
} catch (_) { return 'id'; }
}
// Baca dari translations global yang didefinisikan localization.js
function t(key) {
try {
const lang = getLang();
if (typeof translations !== 'undefined' && translations[lang] && translations[lang][key]) {
return translations[lang][key];
}
} catch (_) {}
return key;
}
// ─── Data Transformation ──────────────────────────────────────────────────────
function extractRank(title) {
const m = (title || '').match(/(Juara\s+\d+|Juara\s+Harapan\s*\d*|Top\s+\d+|Finalis|Peringkat\s+\d+|Medali\s+\w+)/i);
return m ? m[0] : 'Pemenang';
}
function extractLevel(title, category) {
const text = ((title || '') + ' ' + (category || '')).toLowerCase();
if (/internasional|international|global|world|asean|unesco/.test(text)) return 'Internasional';
if (/regional|kalbar|kalimantan|provinsi|kota|kabupaten|lokal/.test(text)) return 'Regional';
return 'Nasional';
}
function extractCompetition(title) {
return (title || '')
.replace(/^(Juara\s+\d+|Juara\s+Harapan\s*\d*|Top\s+\d+|Finalis|Peringkat\s+\d+|Medali\s+\w+)\s+(di\s+|pada\s+|dalam\s+)?/i, '')
.trim();
}
function getImageUrl(item, seed) {
if (item.galeri && Array.isArray(item.galeri) && item.galeri.length > 0) {
const first = item.galeri[0];
const imgId = typeof first === 'string' ? first : (first.directus_files_id || first.id);
if (imgId) return `${API_BASE}/assets/${imgId}`;
}
if (item.foto) return `${API_BASE}/assets/${item.foto}`;
return `https://picsum.photos/seed/${seed}/800/600`;
}
function transformAchievement(item) {
const id = String(item.id);
const title = item.nama_prestasi || 'Untitled Achievement';
let category = 'Umum';
if (item.bidang_id) {
category = (typeof item.bidang_id === 'object' && item.bidang_id.nama_bidang)
? item.bidang_id.nama_bidang
: (BIDANG_MAP[item.bidang_id] || 'Umum');
}
const rank = extractRank(title);
const level = extractLevel(title, category);
const competition = extractCompetition(title);
const year = parseInt(item.tahun) || new Date().getFullYear();
const anggota = Array.isArray(item.anggota) ? item.anggota : [];
const team = item.nama_tim || (anggota.length > 0 ? anggota[0].nama : 'Mahasiswa');
const avatar = (item.nama_tim || title || 'IF').substring(0, 2).toUpperCase();
const description = item.deskripsi ||
`${team} berhasil meraih prestasi luar biasa sebagai ${rank} dalam ajang ${competition} tahun ${year} pada bidang ${category.toLowerCase()}.`;
return {
id, title, category, competition, rank, year, level, description,
team,
nim: item.nim || '',
generation: item.angkatan ? `${t('grp_acaab_modal_batch_prefix')} ${item.angkatan}` : 'Informatika',
image: getImageUrl(item, `prestasi-${id}`),
avatar,
members: anggota.map(m => ({ nama: m.nama || '', angkatan: m.angkatan ? parseInt(m.angkatan) : undefined })),
};
}
function transformProject(item) {
const id = String(item.id);
const title = item.nama_proyek || 'Untitled Project';
const anggota = Array.isArray(item.anggota) ? item.anggota : [];
const studentName = item.nama_mahasiswa || (anggota.length > 0 ? anggota[0].nama : 'Mahasiswa');
const year = parseInt(item.tahun) || new Date().getFullYear();
const description = item.deskripsi || 'Tidak ada deskripsi.';
let categories = [];
if (item.bidang && Array.isArray(item.bidang)) {
categories = item.bidang.map(b => {
if (b.bidang_id && typeof b.bidang_id === 'object' && b.bidang_id.nama_bidang) return b.bidang_id.nama_bidang;
if (typeof b.bidang_id === 'number') return BIDANG_MAP[b.bidang_id] || 'Umum';
return 'Umum';
}).filter(Boolean);
}
if (categories.length === 0) categories = ['Umum'];
let link = item.url || null;
if (link && !/^https?:\/\//i.test(link)) link = `https://${link}`;
return {
id, title, studentName, year, description, categories, link,
nim: item.nim || '',
image: getImageUrl(item, `project-${id}`),
catatan: item.catatan || '',
members: anggota.map(m => ({ nama: m.nama || '', nim: m.nim || '' })),
};
}
// ─── API Fetch ────────────────────────────────────────────────────────────────
async function fetchAchievements() {
const res = await fetch(`${API_BASE}/items/prestasi?fields=*.*`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const { data } = await res.json();
return (data || [])
.filter(item => item.status === 'published')
.map(transformAchievement);
}
async function fetchProjects() {
const res = await fetch(`${API_BASE}/items/portfolio?fields=*,bidang.bidang_id.nama_bidang,galeri.*`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const { data } = await res.json();
return (data || [])
.filter(item => item.status === 'published')
.map(transformProject);
}
// ─── App State ────────────────────────────────────────────────────────────────
let achievements = [];
let projects = [];
let achVisible = 3;
let projVisible = 6;
let achFilter = { search: '', level: '', cat: '', year: '' };
let projFilter = { search: '', cat: '', year: '' };
// ─── Init ─────────────────────────────────────────────────────────────────────
document.addEventListener('DOMContentLoaded', async () => {
initWaButton();
// Loading placeholders
showSkeleton('acaab-ach-grid', 3);
showSkeleton('acaab-proj-track', 4);
const [achResult, projResult] = await Promise.allSettled([
fetchAchievements(),
fetchProjects(),
]);
if (achResult.status === 'fulfilled') {
achievements = achResult.value.sort((a, b) => b.year - a.year);
} else {
console.error('[ACAAB] achievements error:', achResult.reason);
showError('acaab-ach-grid', t('grp_acaab_ach_error'));
}
if (projResult.status === 'fulfilled') {
projects = projResult.value;
} else {
console.error('[ACAAB] projects error:', projResult.reason);
showError('acaab-proj-track', t('grp_acaab_proj_error'));
}
initStats();
initAchievements();
initProjects();
updateDataNote();
});
// Re-render saat bahasa berubah dari kit navbar
document.addEventListener('languageChanged', () => {
updateStats();
renderAchievements();
buildAchFilterOptions();
renderProjects();
buildProjFilterOptions();
updateWaButton();
updateDataNote();
updateSearchPlaceholders();
});
// ─── Skeleton / Error ─────────────────────────────────────────────────────────
function showSkeleton(containerId, count) {
const el = document.getElementById(containerId);
if (!el) return;
el.innerHTML = Array(count).fill(0).map(() =>
`<div class="acaab-skeleton" style="height:220px;border-radius:0.75rem;"></div>`
).join('');
}
function showError(containerId, msg) {
const el = document.getElementById(containerId);
if (el) el.innerHTML = `<p style="color:#ef4444;padding:1rem 0;grid-column:1/-1;">${escHtml(msg)}</p>`;
}
// ─── Stats ────────────────────────────────────────────────────────────────────
function initStats() {
updateStats();
const statsEl = document.getElementById('acaab-stats');
if (!statsEl) return;
const intl = achievements.filter(a => a.level === 'Internasional').length;
const national = achievements.filter(a => a.level === 'Nasional').length;
const regional = achievements.filter(a => a.level === 'Regional').length;
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (!entry.isIntersecting) return;
animateCount('stat-total-ach', achievements.length, 1600);
animateCount('stat-total-proj', projects.length, 1600);
animateCount('stat-intl', intl, 1200);
animateCount('stat-national', national, 1350);
animateCount('stat-regional', regional, 1500);
observer.disconnect();
});
}, { threshold: 0.4 });
observer.observe(statsEl);
}
function updateStats() {
const set = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; };
set('stat-total-ach', achievements.length);
set('stat-total-proj', projects.length);
set('stat-intl', achievements.filter(a => a.level === 'Internasional').length);
set('stat-national', achievements.filter(a => a.level === 'Nasional').length);
set('stat-regional', achievements.filter(a => a.level === 'Regional').length);
}
function animateCount(elId, target, duration) {
const el = document.getElementById(elId);
if (!el) return;
const start = performance.now();
(function step(now) {
const p = Math.min((now - start) / duration, 1);
const eased = 1 - Math.pow(1 - p, 4); // easeOutQuart
el.textContent = Math.round(target * eased).toLocaleString();
if (p < 1) requestAnimationFrame(step);
})(start);
}
function updateDataNote() {
const el = document.getElementById('acaab-data-note');
if (!el) return;
const lang = getLang();
const locale = lang === 'en' ? 'en-US' : 'id-ID';
const date = new Date().toLocaleDateString(locale, { day: 'numeric', month: 'long', year: 'numeric' });
el.textContent = `${t('grp_acaab_stats_data_note')} ${date}`;
}
// ─── Achievements ─────────────────────────────────────────────────────────────
const ACH_INIT = 3;
const ACH_MORE = 3;
const LEVEL_CLASS = {
Internasional: 'acaab-badge-intl',
Nasional: 'acaab-badge-nasional',
Regional: 'acaab-badge-regional',
};
function initAchievements() {
buildAchFilterOptions();
renderAchievements();
bindAchEvents();
}
function buildAchFilterOptions() {
const levelSel = document.getElementById('acaab-ach-level');
const catSel = document.getElementById('acaab-ach-cat');
const yearSel = document.getElementById('acaab-ach-year');
if (!levelSel || !catSel || !yearSel) return;
const levels = ['Internasional', 'Nasional', 'Regional'];
const cats = [...new Set(achievements.map(a => a.category))].sort();
const years = [...new Set(achievements.map(a => String(a.year)))].filter(Boolean).sort((a, b) => b - a);
levelSel.innerHTML = `<option value="">${escHtml(t('grp_acaab_ach_filter_level'))}</option>` +
levels.map(l => `<option value="${escHtml(l)}"${achFilter.level === l ? ' selected' : ''}>${escHtml(l)}</option>`).join('');
catSel.innerHTML = `<option value="">${escHtml(t('grp_acaab_ach_filter_cat'))}</option>` +
cats.map(c => `<option value="${escHtml(c)}"${achFilter.cat === c ? ' selected' : ''}>${escHtml(c)}</option>`).join('');
yearSel.innerHTML = `<option value="">${escHtml(t('grp_acaab_ach_filter_year'))}</option>` +
years.map(y => `<option value="${escHtml(y)}"${achFilter.year === y ? ' selected' : ''}>${escHtml(y)}</option>`).join('');
}
function filterAchievements() {
const q = achFilter.search.toLowerCase();
return achievements.filter(a => {
const matchSearch = !q || [a.title, a.team, a.category, a.competition]
.some(s => (s || '').toLowerCase().includes(q));
const matchLevel = !achFilter.level || a.level === achFilter.level;
const matchCat = !achFilter.cat || a.category === achFilter.cat;
const matchYear = !achFilter.year || String(a.year) === achFilter.year;
return matchSearch && matchLevel && matchCat && matchYear;
});
}
function renderAchievements() {
const grid = document.getElementById('acaab-ach-grid');
const countEl = document.getElementById('acaab-ach-count');
const moreBtn = document.getElementById('acaab-ach-more');
const lessBtn = document.getElementById('acaab-ach-less');
const resetBtn= document.getElementById('acaab-ach-reset');
if (!grid) return;
const filtered = filterAchievements();
const visible = filtered.slice(0, achVisible);
const hasFilter = achFilter.search || achFilter.level || achFilter.cat || achFilter.year;
if (countEl) countEl.textContent =
`${t('grp_acaab_ach_showing')} ${filtered.length} ${t('grp_acaab_ach_results')}`;
if (resetBtn) resetBtn.style.display = hasFilter ? 'flex' : 'none';
if (filtered.length === 0) {
grid.innerHTML = `<p style="color:#64748b;padding:0.5rem 0;grid-column:1/-1;">${escHtml(t('grp_acaab_ach_empty'))}</p>`;
if (moreBtn) moreBtn.style.display = 'none';
if (lessBtn) lessBtn.style.display = 'none';
return;
}
grid.innerHTML = visible.map((a, i) => achCardHtml(a, i)).join('');
if (moreBtn) moreBtn.style.display = achVisible < filtered.length ? 'inline-block' : 'none';
if (lessBtn) lessBtn.style.display = achVisible > ACH_INIT ? 'inline-block' : 'none';
grid.querySelectorAll('.acaab-ach-card').forEach(card => {
card.addEventListener('click', (e) => {
// Biarkan tombol yang merupakan link tetap berfungsi
if (e.target.closest('a')) return;
const ach = achievements.find(a => a.id === card.dataset.id);
if (ach) openAchModal(ach);
});
});
}
function achCardHtml(a, idx) {
return `
<article class="acaab-ach-card acaab-fade-in" data-id="${escHtml(a.id)}" style="animation-delay:${idx * 0.05}s;cursor:pointer;">
<div class="acaab-ach-top-bar"></div>
<!-- Image h-56 (224px) -->
<div style="position:relative;height:224px;overflow:hidden;background:#f1f5f9;">
<img src="${escHtml(a.image)}" alt="${escHtml(a.title)}" loading="lazy" class="acaab-card-img"
style="width:100%;height:100%;object-fit:cover;"
onerror="this.src='https://placehold.co/800x600/003150/FDB813?text=Informatika+UNTAN'">
<!-- Rank badge top-left: white pill dengan trophy icon -->
<div style="position:absolute;top:0.75rem;left:0.75rem;background:rgba(255,255,255,0.95);backdrop-filter:blur(4px);color:#003150;font-weight:700;padding:0.25rem 0.625rem;border-radius:9999px;box-shadow:0 1px 3px rgba(0,0,0,0.15);display:flex;align-items:center;gap:0.375rem;font-size:0.72rem;z-index:2;">
<i class="fas fa-trophy" style="color:#feb401;font-size:0.65rem;"></i>
${escHtml(a.rank)}
</div>
</div>
<!-- Content p-6 -->
<div style="padding:1.5rem;display:flex;flex-direction:column;flex-grow:1;">
<!-- Category + Year row -->
<div style="display:flex;align-items:center;gap:0.75rem;margin-bottom:0.75rem;">
<span style="color:#1e40af;font-size:0.72rem;font-weight:700;text-transform:uppercase;letter-spacing:0.05em;">${escHtml(a.category)}</span>
<span style="color:#94a3b8;font-size:0.72rem;font-weight:500;display:flex;align-items:center;gap:0.25rem;border-left:1px solid #e2e8f0;padding-left:0.625rem;">
<i class="fas fa-calendar-alt" style="font-size:0.6rem;"></i> ${escHtml(String(a.year))}
</span>
</div>
<!-- Title -->
<h3 class="acaab-card-title acaab-line-clamp-2" style="font-size:1.125rem;font-weight:700;color:#003150;margin:0 0 0.75rem;line-height:1.35;font-family:'Roboto Condensed',sans-serif;">${escHtml(a.title)}</h3>
<!-- Description 3 lines -->
<p class="acaab-line-clamp-3" style="color:#475569;font-size:0.875rem;line-height:1.6;margin:0 0 1.5rem;flex-grow:1;">${escHtml(a.description)}</p>
<!-- Bottom -->
<div style="border-top:1px solid #f1f5f9;padding-top:1rem;margin-top:auto;">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;">
<!-- Avatar + team + generation -->
<div style="display:flex;align-items:center;gap:0.625rem;">
<div style="width:2rem;height:2rem;border-radius:0.375rem;background:rgba(0,49,80,0.05);border:1px solid rgba(0,49,80,0.1);display:flex;align-items:center;justify-content:center;color:#003150;font-weight:700;font-size:0.65rem;flex-shrink:0;">${escHtml(a.avatar)}</div>
<div>
<span style="font-weight:700;color:#1e293b;display:block;font-size:0.8125rem;line-height:1.2;">${escHtml(a.team)}</span>
<span style="color:#64748b;display:block;font-size:0.72rem;margin-top:0.1rem;">${escHtml(a.generation)}</span>
</div>
</div>
<!-- Level badge -->
<span style="background:#f1f5f9;color:#475569;font-size:0.575rem;padding:0.2rem 0.5rem;border-radius:0.25rem;font-weight:700;text-transform:uppercase;letter-spacing:0.05em;white-space:nowrap;">${escHtml(a.level)}</span>
</div>
<!-- Full-width CTA button -->
<button class="acaab-ach-readmore acaab-ach-read-btn" data-id="${escHtml(a.id)}"
style="width:100%;padding:0.625rem;border-radius:0.375rem;background:rgba(0,49,80,0.05);color:#003150;font-weight:700;font-size:0.875rem;border:none;cursor:pointer;transition:background 0.2s,color 0.2s;min-height:44px;">
${escHtml(t('grp_acaab_modal_read_more'))}
</button>
</div>
</div>
</article>`;
}
function bindAchEvents() {
const search = document.getElementById('acaab-ach-search');
const level = document.getElementById('acaab-ach-level');
const cat = document.getElementById('acaab-ach-cat');
const year = document.getElementById('acaab-ach-year');
const reset = document.getElementById('acaab-ach-reset');
const moreBtn = document.getElementById('acaab-ach-more');
const lessBtn = document.getElementById('acaab-ach-less');
const onChange = () => {
achFilter.search = search ? search.value.trim() : '';
achFilter.level = level ? level.value : '';
achFilter.cat = cat ? cat.value : '';
achFilter.year = year ? year.value : '';
achVisible = ACH_INIT;
renderAchievements();
};
if (search) search.addEventListener('input', onChange);
if (level) level.addEventListener('change', onChange);
if (cat) cat.addEventListener('change', onChange);
if (year) year.addEventListener('change', onChange);
if (reset) reset.addEventListener('click', () => {
achFilter = { search: '', level: '', cat: '', year: '' };
if (search) search.value = '';
if (level) level.value = '';
if (cat) cat.value = '';
if (year) year.value = '';
achVisible = ACH_INIT;
renderAchievements();
});
if (moreBtn) moreBtn.addEventListener('click', () => { achVisible += ACH_MORE; renderAchievements(); });
if (lessBtn) lessBtn.addEventListener('click', () => {
achVisible = ACH_INIT;
document.getElementById('acaab-ach-section')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
renderAchievements();
});
}
// ─── Achievement Modal ────────────────────────────────────────────────────────
function infoRow(icon, label, value) {
if (!value) return '';
return `<div style="display:flex;gap:0.625rem;align-items:flex-start;">
<span style="color:#feb401;margin-top:0.125rem;flex-shrink:0;"><i class="fas fa-${icon}" style="font-size:0.75rem;"></i></span>
<div>
<p style="font-size:0.6rem;font-weight:700;text-transform:uppercase;letter-spacing:0.08em;color:#94a3b8;margin:0 0 0.125rem;line-height:1;">${escHtml(label)}</p>
<p style="font-size:0.875rem;font-weight:600;color:#1e293b;margin:0;line-height:1.3;">${escHtml(value)}</p>
</div>
</div>`;
}
function memberItem(nama, sub) {
const initial = (nama || '?').charAt(0).toUpperCase();
return `<li style="display:flex;align-items:center;gap:0.5rem;background:#f8fafc;padding:0.5rem 0.75rem;border-radius:0.375rem;">
<span style="width:1.5rem;height:1.5rem;border-radius:50%;background:rgba(254,180,1,0.2);color:#b45309;display:flex;align-items:center;justify-content:center;font-size:0.72rem;font-weight:700;flex-shrink:0;">${initial}</span>
<div style="display:flex;flex-direction:column;">
<span style="font-size:0.875rem;font-weight:600;color:#334155;line-height:1.2;">${escHtml(nama)}</span>
${sub ? `<span style="font-size:0.65rem;color:#94a3b8;margin-top:0.125rem;">${escHtml(sub)}</span>` : ''}
</div>
</li>`;
}
function openAchModal(a) {
const membersHtml = a.members && a.members.length > 0 ? `
<div style="border-top:1px solid #f1f5f9;padding-top:1rem;margin-bottom:1rem;">
<p style="font-size:0.6rem;font-weight:700;text-transform:uppercase;letter-spacing:0.08em;color:#94a3b8;margin:0 0 0.5rem;">${t('grp_acaab_modal_members')}</p>
<ul style="list-style:none;margin:0;padding:0;display:grid;grid-template-columns:1fr 1fr;gap:0.5rem;">
${a.members.map(m => memberItem(m.nama, m.angkatan ? `${t('grp_acaab_modal_batch_prefix')} ${m.angkatan}` : '')).join('')}
</ul>
</div>` : '';
renderModal(`
<!-- Image header -->
<div style="position:relative;border-radius:0.75rem 0.75rem 0 0;background:#0f172a;flex-shrink:0;">
<img src="${escHtml(a.image)}" alt="${escHtml(a.title)}" style="width:100%;max-height:450px;object-fit:contain;display:block;"
onerror="this.src='https://placehold.co/800x600/0f172a/FDB813?text=Informatika+UNTAN'">
<div style="position:absolute;inset:0;background:linear-gradient(to top,rgba(0,0,0,0.8) 0%,rgba(0,0,0,0.1) 40%,transparent 100%);pointer-events:none;"></div>
<!-- Close button: white rounded-full -->
<button id="acaab-modal-close" aria-label="Tutup"
style="position:absolute;top:0.75rem;right:0.75rem;width:2.25rem;height:2.25rem;background:rgba(255,255,255,0.9);backdrop-filter:blur(4px);border:none;border-radius:50%;display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 1px 3px rgba(0,0,0,0.2);">
<i class="fas fa-times" style="color:#334155;font-size:0.875rem;"></i>
</button>
<!-- Two badges bottom-left -->
<div style="position:absolute;bottom:1rem;left:1rem;display:flex;gap:0.5rem;flex-wrap:wrap;">
<span style="background:#feb401;color:#0f172a;font-size:0.72rem;font-weight:700;padding:0.3rem 0.75rem;border-radius:0.25rem;display:flex;align-items:center;gap:0.375rem;">
<i class="fas fa-trophy" style="font-size:0.65rem;"></i>${escHtml(a.rank)}
</span>
<span style="background:rgba(255,255,255,0.2);backdrop-filter:blur(4px);color:white;font-size:0.72rem;font-weight:700;padding:0.3rem 0.75rem;border-radius:0.25rem;text-transform:uppercase;letter-spacing:0.05em;">
${escHtml(a.level)}
</span>
</div>
</div>
<!-- Content -->
<div style="padding:1.5rem;">
<span style="color:#1e40af;font-size:0.72rem;font-weight:700;text-transform:uppercase;letter-spacing:0.05em;">${escHtml(a.category)}</span>
<h2 style="font-size:1.125rem;font-weight:700;color:#003150;line-height:1.3;margin:0.25rem 0 1.25rem;font-family:'Roboto Condensed',sans-serif;">${escHtml(a.title)}</h2>
<!-- Info grid: slate-50 bg -->
<div style="display:grid;grid-template-columns:1fr 1fr;gap:1rem;padding:1rem;background:#f8fafc;border-radius:0.5rem;margin-bottom:1.25rem;">
${infoRow('users', t('grp_acaab_modal_team'), a.team)}
${a.nim ? infoRow('id-card', 'NIM', a.nim) : ''}
${infoRow('user-graduate', t('grp_acaab_modal_generation'), a.generation)}
${infoRow('calendar-alt', t('grp_acaab_modal_year'), String(a.year))}
${infoRow('award', t('grp_acaab_modal_competition'), a.competition)}
${infoRow('tag', t('grp_acaab_modal_field'), a.category)}
</div>
${membersHtml}
<!-- Description -->
<div style="border-top:1px solid #f1f5f9;padding-top:1rem;">
<p style="font-size:0.6rem;font-weight:700;text-transform:uppercase;letter-spacing:0.08em;color:#94a3b8;margin:0 0 0.5rem;">${t('grp_acaab_modal_description')}</p>
<p style="color:#475569;font-size:0.875rem;line-height:1.65;margin:0;">${escHtml(a.description)}</p>
</div>
<!-- Close button full-width -->
<button id="acaab-modal-close-btn" style="margin-top:1.5rem;width:100%;padding:0.75rem;background:#003150;color:white;font-weight:700;border:none;border-radius:0.5rem;cursor:pointer;font-size:0.875rem;">
${t('grp_acaab_modal_close')}
</button>
</div>
`, true);
}
// ─── Projects ─────────────────────────────────────────────────────────────────
const PROJ_INIT = 6;
const PROJ_MORE = 3;
function initProjects() {
buildProjFilterOptions();
renderProjects();
bindProjEvents();
}
function buildProjFilterOptions() {
const catSel = document.getElementById('acaab-proj-cat');
const yearSel = document.getElementById('acaab-proj-year');
if (!catSel || !yearSel) return;
const cats = [...new Set(projects.flatMap(p => p.categories))].sort();
const years = [...new Set(projects.map(p => String(p.year)))].filter(Boolean).sort((a, b) => b - a);
catSel.innerHTML = `<option value="">${escHtml(t('grp_acaab_proj_filter_cat'))}</option>` +
cats.map(c => `<option value="${escHtml(c)}"${projFilter.cat === c ? ' selected' : ''}>${escHtml(c)}</option>`).join('');
yearSel.innerHTML = `<option value="">${escHtml(t('grp_acaab_proj_filter_year'))}</option>` +
years.map(y => `<option value="${escHtml(y)}"${projFilter.year === y ? ' selected' : ''}>${escHtml(y)}</option>`).join('');
}
function filterProjects() {
const q = projFilter.search.toLowerCase();
const result = projects.filter(p => {
const matchSearch = !q || [p.title, p.studentName, p.description, ...p.categories]
.some(s => (s || '').toLowerCase().includes(q));
const matchCat = !projFilter.cat || p.categories.includes(projFilter.cat);
const matchYear = !projFilter.year || String(p.year) === projFilter.year;
return matchSearch && matchCat && matchYear;
});
// Sort descending by year
result.sort((a, b) => (b.year || 0) - (a.year || 0));
return result;
}
function renderProjects() {
const track = document.getElementById('acaab-proj-track');
const moreBtn = document.getElementById('acaab-proj-more');
const resetBtn = document.getElementById('acaab-proj-reset');
if (!track) return;
const filtered = filterProjects();
const visible = filtered.slice(0, projVisible);
const hasFilter = projFilter.search || projFilter.cat || projFilter.year;
if (resetBtn) resetBtn.style.display = hasFilter ? 'flex' : 'none';
if (filtered.length === 0) {
track.innerHTML = `<p style="color:#64748b;padding:0.5rem 0;">${escHtml(t('grp_acaab_proj_empty'))}</p>`;
if (moreBtn) moreBtn.style.display = 'none';
return;
}
track.innerHTML = visible.map((p, i) => projCardHtml(p, i)).join('');
if (moreBtn) moreBtn.style.display = projVisible < filtered.length ? 'inline-block' : 'none';
// Responsif: Sembunyikan scroll button & center jika cuma 1 data
const prevBtn = document.getElementById('acaab-proj-prev');
const nextBtn = document.getElementById('acaab-proj-next');
if (prevBtn) prevBtn.style.display = filtered.length <= 1 ? 'none' : 'flex';
if (nextBtn) nextBtn.style.display = filtered.length <= 1 ? 'none' : 'flex';
track.style.justifyContent = filtered.length === 1 ? 'center' : 'flex-start';
track.querySelectorAll('.acaab-proj-card-wrap').forEach(card => {
card.addEventListener('click', (e) => {
if (e.target.closest('a') && e.target.closest('a').href) return;
const proj = projects.find(p => p.id === card.dataset.id);
if (proj) openProjModal(proj);
});
});
}
function projCardHtml(p, idx) {
const catBadges = p.categories.map(c => `
<span style="display:inline-flex;align-items:center;gap:0.2rem;font-size:0.6rem;font-weight:700;color:#feb401;text-transform:uppercase;letter-spacing:0.05em;background:rgba(0,0,0,0.3);padding:0.2rem 0.45rem;border-radius:0.2rem;backdrop-filter:blur(4px);">
<i class="fas fa-tag" style="font-size:0.5rem;"></i>${escHtml(c)}
</span>`).join('');
return `
<div class="acaab-proj-card acaab-proj-card-wrap acaab-fade-in" data-id="${escHtml(p.id)}" style="animation-delay:${idx * 0.05}s;cursor:pointer;">
<div class="acaab-proj-left-bar"></div>
<!-- Image h-52 (208px) dengan title overlay -->
<div style="height:208px;overflow:hidden;position:relative;">
<img src="${escHtml(p.image)}" alt="${escHtml(p.title)}" loading="lazy" class="acaab-card-img"
style="width:100%;height:100%;object-fit:cover;"
onerror="this.src='https://placehold.co/800x600/003150/FDB813?text=Informatika+UNTAN'">
<!-- Gradient overlay: title + categories di dalam gambar -->
<div style="position:absolute;inset:0;background:linear-gradient(to top,rgba(0,49,80,0.85),transparent);padding:1.25rem;display:flex;flex-direction:column;justify-content:flex-end;pointer-events:none;">
<h4 style="font-size:1rem;font-weight:700;color:white;line-height:1.25;margin:0 0 0.375rem;font-family:'Roboto Condensed',sans-serif;">${escHtml(p.title)}</h4>
<div style="display:flex;flex-wrap:wrap;gap:0.3rem;">${catBadges}</div>
</div>
</div>
<!-- Body p-5 -->
<div style="padding:1.25rem;display:flex;flex-direction:column;flex-grow:1;">
<!-- Meta: student + year -->
<div style="display:flex;flex-wrap:wrap;align-items:center;gap:0.625rem;margin-bottom:0.75rem;font-size:0.72rem;color:#64748b;font-weight:500;">
${p.studentName ? `<span style="display:flex;align-items:center;gap:0.25rem;"><i class="fas fa-user" style="color:#94a3b8;font-size:0.6rem;"></i>${escHtml(p.studentName)}</span>` : ''}
${p.year ? `<span style="display:flex;align-items:center;gap:0.25rem;"><i class="fas fa-calendar-alt" style="color:#94a3b8;font-size:0.6rem;"></i>${escHtml(String(p.year))}</span>` : ''}
</div>
<!-- Description 3 lines -->
<p class="acaab-line-clamp-3" style="color:#475569;font-size:0.875rem;line-height:1.6;margin:0 0 1.25rem;flex-grow:1;">${escHtml(p.description)}</p>
<!-- Actions -->
<div style="display:flex;align-items:center;gap:0.625rem;margin-top:auto;">
<button class="acaab-proj-readmore" data-id="${escHtml(p.id)}"
style="color:#003150;font-weight:700;font-size:0.875rem;background:none;border:none;cursor:pointer;display:flex;align-items:center;gap:0.375rem;min-height:44px;">
${escHtml(t('grp_acaab_modal_read_more'))} <i class="fas fa-arrow-right" style="font-size:0.7rem;"></i>
</button>
${p.link ? `
<a href="${escHtml(p.link)}" target="_blank" rel="noopener noreferrer"
style="margin-left:auto;display:flex;align-items:center;gap:0.375rem;font-size:0.72rem;font-weight:700;color:#64748b;border:1px solid #e2e8f0;border-radius:0.375rem;padding:0.375rem 0.625rem;text-decoration:none;min-height:44px;transition:color 0.2s,border-color 0.2s;">
<i class="fas fa-external-link-alt" style="font-size:0.65rem;"></i>${escHtml(t('grp_acaab_modal_visit'))}
</a>` : ''}
</div>
</div>
</div>`;
}
function bindProjEvents() {
const search = document.getElementById('acaab-proj-search');
const cat = document.getElementById('acaab-proj-cat');
const year = document.getElementById('acaab-proj-year');
const reset = document.getElementById('acaab-proj-reset');
const moreBtn = document.getElementById('acaab-proj-more');
const prevBtn = document.getElementById('acaab-proj-prev');
const nextBtn = document.getElementById('acaab-proj-next');
const onChange = () => {
projFilter.search = search ? search.value.trim() : '';
projFilter.cat = cat ? cat.value : '';
projFilter.year = year ? year.value : '';
projVisible = PROJ_INIT;
renderProjects();
};
if (search) search.addEventListener('input', onChange);
if (cat) cat.addEventListener('change', onChange);
if (year) year.addEventListener('change', onChange);
if (reset) reset.addEventListener('click', () => {
projFilter = { search: '', cat: '', year: '' };
if (search) search.value = '';
if (cat) cat.value = '';
if (year) year.value = '';
projVisible = PROJ_INIT;
renderProjects();
});
if (moreBtn) moreBtn.addEventListener('click', () => { projVisible += PROJ_MORE; renderProjects(); });
const track = document.getElementById('acaab-proj-track');
if (prevBtn && track) prevBtn.addEventListener('click', () => {
const cardW = (track.firstElementChild ? track.firstElementChild.offsetWidth : 300) + 20;
track.scrollBy({ left: -cardW, behavior: 'smooth' });
});
if (nextBtn && track) nextBtn.addEventListener('click', () => {
const cardW = (track.firstElementChild ? track.firstElementChild.offsetWidth : 300) + 20;
track.scrollBy({ left: cardW, behavior: 'smooth' });
});
}
// ─── Project Modal ────────────────────────────────────────────────────────────
function openProjModal(p) {
const membersHtml = p.members && p.members.length > 0 ? `
<div style="border-top:1px solid #f1f5f9;padding-top:1rem;margin-bottom:1rem;">
<p style="font-size:0.6rem;font-weight:700;text-transform:uppercase;letter-spacing:0.08em;color:#94a3b8;margin:0 0 0.5rem;">${t('grp_acaab_modal_members')}</p>
<ul style="list-style:none;margin:0;padding:0;display:grid;grid-template-columns:1fr 1fr;gap:0.5rem;">
${p.members.map(m => memberItem(m.nama, m.nim || '')).join('')}
</ul>
</div>` : '';
const catBadgesImg = p.categories.map(c => `
<span style="background:#feb401;color:#0f172a;font-size:0.72rem;font-weight:700;padding:0.3rem 0.75rem;border-radius:0.25rem;display:inline-flex;align-items:center;gap:0.375rem;text-transform:uppercase;letter-spacing:0.05em;">
<i class="fas fa-tag" style="font-size:0.6rem;"></i>${escHtml(c)}
</span>`).join('');
const linkSection = p.link
? `<a href="${escHtml(p.link)}" target="_blank" rel="noopener noreferrer"
style="display:flex;align-items:center;justify-content:center;gap:0.5rem;width:100%;padding:0.75rem;background:#003150;color:white;font-weight:700;border-radius:0.5rem;font-size:0.875rem;text-decoration:none;margin-bottom:0.625rem;">
<i class="fas fa-external-link-alt" style="font-size:0.8rem;"></i>${escHtml(t('grp_acaab_modal_visit'))}
</a>`
: `<div style="display:flex;align-items:center;justify-content:center;gap:0.5rem;width:100%;padding:0.75rem;background:#f1f5f9;color:#94a3b8;font-weight:700;border-radius:0.5rem;font-size:0.875rem;margin-bottom:0.625rem;cursor:not-allowed;">
<i class="fas fa-file-alt" style="font-size:0.8rem;"></i>${escHtml(t('grp_acaab_modal_no_link'))}
</div>`;
renderModal(`
<!-- Image header -->
<div style="position:relative;border-radius:0.75rem 0.75rem 0 0;background:#0f172a;flex-shrink:0;">
<img src="${escHtml(p.image)}" alt="${escHtml(p.title)}" style="width:100%;max-height:450px;object-fit:contain;display:block;"
onerror="this.src='https://placehold.co/800x600/0f172a/FDB813?text=Informatika+UNTAN'">
<div style="position:absolute;inset:0;background:linear-gradient(to top,rgba(0,0,0,0.8) 0%,rgba(0,0,0,0.1) 40%,transparent 100%);pointer-events:none;"></div>
<!-- Close button -->
<button id="acaab-modal-close" aria-label="Tutup"
style="position:absolute;top:0.75rem;right:0.75rem;width:2.25rem;height:2.25rem;background:rgba(255,255,255,0.9);backdrop-filter:blur(4px);border:none;border-radius:50%;display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 1px 3px rgba(0,0,0,0.2);">
<i class="fas fa-times" style="color:#334155;font-size:0.875rem;"></i>
</button>
<!-- Category badges bottom-left -->
<div style="position:absolute;bottom:1rem;left:1rem;display:flex;flex-wrap:wrap;gap:0.375rem;">${catBadgesImg}</div>
</div>
<!-- Content -->
<div style="padding:1.5rem;">
<h2 style="font-size:1.125rem;font-weight:700;color:#003150;line-height:1.3;margin:0 0 1.25rem;font-family:'Roboto Condensed',sans-serif;">${escHtml(p.title)}</h2>
<!-- Info grid -->
<div style="display:grid;grid-template-columns:1fr 1fr;gap:1rem;padding:1rem;background:#f8fafc;border-radius:0.5rem;margin-bottom:1.25rem;">
${p.studentName ? infoRow('users', t('grp_acaab_modal_team'), p.studentName) : ''}
${p.nim ? infoRow('id-card', 'NIM', p.nim) : ''}
${infoRow('calendar-alt', t('grp_acaab_modal_year'), String(p.year))}
${infoRow('tag', t('grp_acaab_modal_field'), p.categories.join(', '))}
</div>
${membersHtml}
<!-- Description -->
<div style="border-top:1px solid #f1f5f9;padding-top:1rem;margin-bottom:1.25rem;">
<p style="font-size:0.6rem;font-weight:700;text-transform:uppercase;letter-spacing:0.08em;color:#94a3b8;margin:0 0 0.5rem;">${t('grp_acaab_modal_description')}</p>
<p style="color:#475569;font-size:0.875rem;line-height:1.65;margin:0;">${escHtml(p.description)}</p>
${p.catatan ? `
<div style="margin-top:1rem;padding:0.75rem;background:rgba(239,246,255,0.5);border:1px solid #bfdbfe;border-radius:0.5rem;">
<p style="font-size:0.6rem;font-weight:700;text-transform:uppercase;color:#93c5fd;margin:0 0 0.25rem;">${t('grp_acaab_modal_note')}</p>
<p style="font-size:0.875rem;color:#475569;font-style:italic;margin:0;">${escHtml(p.catatan)}</p>
</div>` : ''}
</div>
<!-- Link + close buttons -->
${linkSection}
<button id="acaab-modal-close-btn" style="width:100%;padding:0.625rem;border:2px solid #e2e8f0;color:#475569;font-weight:700;background:white;border-radius:0.5rem;cursor:pointer;font-size:0.875rem;transition:border-color 0.2s,background 0.2s;">
${t('grp_acaab_modal_close')}
</button>
</div>
`, true);
}
// ─── Modal shared ─────────────────────────────────────────────────────────────
function renderModal(innerHtml, hasCloseBtn = false) {
const container = document.getElementById('acaab-modal');
if (!container) return;
container.innerHTML = `
<div class="acaab-modal-backdrop" id="acaab-modal-bd">
<div style="background:white;border-radius:0.75rem;max-width:672px;width:100%;max-height:90vh;overflow-y:auto;box-shadow:0 20px 60px rgba(0,0,0,0.35);">
${innerHtml}
</div>
</div>`;
document.body.classList.add('acaab-modal-open');
document.getElementById('acaab-modal-close')?.addEventListener('click', closeModal);
if (hasCloseBtn) {
document.getElementById('acaab-modal-close-btn')?.addEventListener('click', closeModal);
}
document.getElementById('acaab-modal-bd').addEventListener('click', e => {
if (e.target === e.currentTarget) closeModal();
});
document.addEventListener('keydown', onEsc);
}
function closeModal() {
const c = document.getElementById('acaab-modal');
if (c) c.innerHTML = '';
document.body.classList.remove('acaab-modal-open');
document.removeEventListener('keydown', onEsc);
}
function onEsc(e) { if (e.key === 'Escape') closeModal(); }
// ─── CTA / WhatsApp ───────────────────────────────────────────────────────────
const WA_NUMBER = '6285177655161';
function initWaButton() { updateWaButton(); }
function updateWaButton() {
const btn = document.getElementById('acaab-cta-wa');
if (!btn) return;
const msg = getLang() === 'en'
? 'Hello! I would like to know more about Informatika UNTAN student achievements and projects.'
: 'Halo! Saya ingin mengetahui lebih lanjut tentang prestasi dan karya mahasiswa Informatika UNTAN.';
btn.href = `https://wa.me/${WA_NUMBER}?text=${encodeURIComponent(msg)}`;
}
// ─── Placeholder sync ─────────────────────────────────────────────────────────
function updateSearchPlaceholders() {
const s1 = document.getElementById('acaab-ach-search');
const s2 = document.getElementById('acaab-proj-search');
if (s1) s1.placeholder = t('grp_acaab_ach_search');
if (s2) s2.placeholder = t('grp_acaab_proj_search');
}
+212 -38
View File
@@ -1,48 +1,222 @@
:root { /* =====================================================
--brand-navy: #003150; ACAAB Prestasi & Karya Mahasiswa
--brand-yellow: #feb401; Custom styles yang tidak dicakup output.css
--text-main: #1e293b; ===================================================== */
--text-soft: #64748b;
--bg-soft: #f8fafc; /* Cegah horizontal scroll tanpa clip full-bleed */
--line: #e2e8f0; 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; } /* --- Sembunyikan scrollbar carousel tapi tetap scrollable --- */
html, body { margin: 0; padding: 0; } .acaab-hide-scrollbar {
body { font-family: "Roboto", Arial, sans-serif; color: var(--text-main); background: #fff; line-height: 1.6; } -ms-overflow-style: none;
.container { width: min(1100px, 92%); margin-inline: auto; } 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; } /* --- Card ukuran responsif di carousel --- */
.brand { font-size: 1.1rem; font-weight: 700; letter-spacing: .02em; } .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; } /* --- Snap scroll carousel --- */
.hero-bg { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; filter: brightness(0.45); } .acaab-carousel-track {
.hero-inner { position: relative; z-index: 1; padding: 36px 0; } scroll-snap-type: x mandatory;
.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; } -webkit-overflow-scrolling: touch;
.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; } /* --- Animasi fade-in untuk cards --- */
@media (min-width: 1024px) { .layout { grid-template-columns: minmax(0, 3fr) minmax(260px, 1fr); } } @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; } /* --- Badge level warna --- */
.cover { background: var(--bg-soft); border: 1px solid var(--line); margin-bottom: 1.5rem; } .acaab-badge-intl { background-color: #ca8a04; color: #fff; }
.cover img { width: 100%; height: auto; display: block; } .acaab-badge-nasional { background-color: #1d4ed8; color: #fff; }
.acaab-badge-regional { background-color: #64748b; color: #fff; }
.article-body > * + * { margin-top: 1.1em; } /* --- Skeleton loading --- */
.article-body h2 { font-size: 1.35rem; margin: 1.6em 0 .5em; } .acaab-skeleton {
.article-body h3 { font-size: 1.1rem; margin: 1.4em 0 .4em; } background: linear-gradient(90deg, #e2e8f0 25%, #f1f5f9 50%, #e2e8f0 75%);
.article-body p { margin: 0 0 1em; } background-size: 200% 100%;
.article-body ul, .article-body ol { padding-left: 1.4rem; } animation: acaab-skeleton-pulse 1.5s infinite;
.article-body blockquote { border-left: 4px solid #005eb8; background: #eef6ff; padding: .8rem 1rem; color: #334155; } border-radius: 0.5rem;
.article-body a { color: #005eb8; } }
.article-body img { max-width: 100%; border-radius: 8px; } @keyframes acaab-skeleton-pulse {
.article-body code { background: #f1f5f9; padding: .1em .35em; border-radius: 4px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; } 0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.note { border: 1px solid var(--line); background: var(--bg-soft); padding: 1rem; } /* --- Modal backdrop --- */
.note h3 { margin: 0 0 .5rem; font-size: .76rem; text-transform: uppercase; letter-spacing: .12em; color: #475569; } .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); } /* --- Scroll lock saat modal terbuka --- */
.footer-inner { display: flex; align-items: center; justify-content: center; gap: .75rem; padding: 1rem 0; } body.acaab-modal-open {
.footer-logo { height: 28px; width: auto; } overflow: hidden;
.footer-ornament { width: 100%; display: block; opacity: .9; } }
/* --- 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;
}
}
-273
View File
@@ -1,273 +0,0 @@
# 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: 4864px bold | H2: 3240px semibold | H3: 2428px semibold
- Body: 1618px regular | Caption: 1214px
---
## 📄 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
-329
View File
@@ -1,329 +0,0 @@
# 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 | 4864px | 700 (Bold) |
| Heading H2 | Poppins / Inter | 3240px | 600 (SemiBold) |
| Heading H3 | Poppins / Inter | 2428px | 600 (SemiBold) |
| Body Text | Inter / Poppins | 1618px | 400 (Regular) |
| Caption/Label | Inter / Poppins | 1214px | 400500 |
| Navbar | Inter | 1416px | 500 (Medium) |
| Button | Inter | 1416px | 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, 34 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 (7681279px) | 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 | 34 kolom |
| Teks Body | 16px | 1617px | 1718px |
| Padding Section | 24px 16px | 40px 32px | 80px 64px |
| Footer | Stack vertikal | 2 kolom | 34 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 `<<include>>` 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.
-337
View File
@@ -1,337 +0,0 @@
# 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<br>2. Sistem tampilkan hero, statistik, daftar beasiswa<br>3. Kartu ditampilkan grid: 3 kolom desktop / 2 tablet / 1 mobile<br>4. Tiap kartu: thumbnail, badge status, nama, deskripsi singkat, tanggal, tombol "Selengkapnya"<br>5. Paginasi tampil di bawah grid jika beasiswa > 1 halaman |
| Alt Flow | 3a. Koneksi lambat → skeleton loading<br>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<br>2. Pengunjung klik tab<br>3. Sistem real-time filter beasiswa sesuai kategori<br>4. Tab aktif ditandai bold/underline/warna<br>5. Jumlah hasil diperbarui |
| Alt Flow | 3a. Tidak ada hasil → "Tidak ada beasiswa pada kategori ini."<br>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<br>2. Ketik kata kunci (misal: "Pertamina", "KIP", "luar negeri")<br>3. Sistem real-time filter kartu yang nama/deskripsinya cocok<br>4. Hapus kata kunci → kembali ke tampilan penuh<br>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 →"<br>2. Sistem tampilkan modal popup berisi: nama + thumbnail, badge status, syarat umum ringkasan, kontak, tombol "Info Lebih Lanjut" (→ URL sumber resmi di tab baru)<br>3. Background di-overlay gelap<br>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<br>2. Sistem buka URL sumber resmi di tab baru<br>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?")<br>2. Tampil dua tombol: "Hubungi Admin" (WhatsApp) + "Instagram Beasiswa" (IG kemahasiswaan)<br>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<br>2. Tampil kartu testimoni: foto, nama, nama beasiswa, kutipan singkat<br>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<br>2. Klik bahasa yang diinginkan<br>3. Seluruh teks berubah tanpa reload<br>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<br>2. Edit file data beasiswa (nama, kategori, status, syarat, kontak, URL)<br>3. Simpan dan push ke GitHub<br>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)
+18 -17
View File
@@ -26,7 +26,7 @@
return s; // "Segera Dibuka" atau nilai lain: tampilkan apa adanya return s; // "Segera Dibuka" atau nilai lain: tampilkan apa adanya
} }
// Petakan satu item Directus ke skema yang diharapkan filter.js / popup.js // Petakan satu item (API remote maupun lokal) ke skema yang diharapkan filter.js / popup.js
function normalizeItem(item) { function normalizeItem(item) {
const parts = [item.jenjang, item.negara, item.tipe_pendanaan].filter(Boolean); const parts = [item.jenjang, item.negara, item.tipe_pendanaan].filter(Boolean);
return { return {
@@ -34,14 +34,16 @@
nama: item.nama || '', nama: item.nama || '',
status: normalizeStatus(item.status), status: normalizeStatus(item.status),
kategori: item.tipe_pendanaan || '', kategori: item.tipe_pendanaan || '',
thumbnail: '', thumbnail: item.thumbnail || '',
deskripsi_singkat: parts.join(' · ') || 'Info beasiswa tersedia di sumber resmi.', deskripsi_singkat: item.deskripsi_singkat || parts.join(' · ') || 'Info beasiswa tersedia di sumber resmi.',
deskripsi_singkat_en: item.deskripsi_singkat_en || '',
tanggal_buka: item.tanggal_buka || '', tanggal_buka: item.tanggal_buka || '',
tanggal_tutup: item.tenggat_waktu || '', tanggal_tutup: item.tenggat_waktu || '',
url_sumber: item.url || '', url_sumber: item.url || '',
syarat_umum: [], syarat_umum: item.syarat_umum || [],
kontak: '', syarat_umum_en: item.syarat_umum_en || [],
tags: parts, kontak: item.kontak || '',
tags: item.tags || parts,
}; };
} }
@@ -56,23 +58,22 @@
const res = await fetch(CONFIG.localSource); const res = await fetch(CONFIG.localSource);
if (!res.ok) throw new Error(`Gagal memuat data lokal (HTTP ${res.status})`); if (!res.ok) throw new Error(`Gagal memuat data lokal (HTTP ${res.status})`);
const json = await res.json(); const json = await res.json();
return Array.isArray(json) ? json : json.data || []; const raw = Array.isArray(json) ? json : json.data || [];
return raw.map(normalizeItem);
} }
async function getBeasiswa() { async function getBeasiswa() {
try { try {
// Prioritas: data lokal (lengkap: url_sumber, syarat_umum, kontak tersedia) const data = await fetchRemote();
// Remote API digunakan hanya sebagai fallback jika data lokal tidak ditemukan. return { data, source: 'remote' };
const data = await fetchLocal();
return { data, source: 'local' };
} catch (err) { } catch (err) {
console.warn('[BeasiswaAPI] Data lokal gagal, fallback ke API remote:', err.message); console.warn('[BeasiswaAPI] API remote gagal, fallback ke data lokal:', err.message);
try { try {
const data = await fetchRemote(); const data = await fetchLocal();
return { data, source: 'remote' }; return { data, source: 'local' };
} catch (remoteErr) { } catch (localErr) {
console.error('[BeasiswaAPI] Semua sumber gagal:', remoteErr.message); console.error('[BeasiswaAPI] Semua sumber gagal:', localErr.message);
throw remoteErr; throw localErr;
} }
} }
} }
+227 -58
View File
@@ -1,12 +1,19 @@
[ [
{ {
"id": "bsw-001", "id": 101,
"nama": "Boustany Foundation Scholarship, MBA - Harvard University", "nama": "Boustany Foundation Scholarship, MBA - Harvard University",
"nama_en": "Boustany Foundation Scholarship, MBA - Harvard University", "nama_en": "Boustany Foundation Scholarship, MBA - Harvard University",
"kategori": "Swasta",
"kategori_en": "Private",
"status": "Buka", "status": "Buka",
<<<<<<< HEAD
"tipe_pendanaan": "Swasta",
"jenjang": "S2",
"negara": "Amerika Serikat",
"tanggal_buka": "2026-05-21",
"tenggat_waktu": "2027-05-31",
"url": "https://www.schoters.com/id/beasiswa/boustany-foundation-scholarship-mba-harvard-university-1",
"thumbnail": "https://images.unsplash.com/photo-1541339907198-e08756dedf3f?w=400&q=80&auto=format&fit=crop", "thumbnail": "https://images.unsplash.com/photo-1541339907198-e08756dedf3f?w=400&q=80&auto=format&fit=crop",
=======
>>>>>>> b5bc7bad0c7de6e63a60018da0fe06e923bf094c
"deskripsi_singkat": "Beasiswa bergengsi dari Boustany Foundation untuk program MBA di Harvard University, Amerika Serikat, bagi calon pemimpin bisnis dunia.", "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.", "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": [ "syarat_umum": [
@@ -22,18 +29,23 @@
"Complete the scholarship application online through the official Boustany Foundation website" "Complete the scholarship application online through the official Boustany Foundation website"
], ],
"kontak": "Boustany Foundation — boustany-foundation.com", "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"] "tags": ["S2", "Swasta", "Amerika Serikat", "Partially Funded", "MBA"]
}, },
{ {
"id": "bsw-002", "id": 102,
"nama": "La Trobe University - La Trobe High Achiever Scholarship (S1)", "nama": "La Trobe University - La Trobe High Achiever Scholarship (S1)",
"kategori": "Swasta", "nama_en": "La Trobe University - La Trobe High Achiever Scholarship (S1)",
"kategori_en": "Private",
"status": "Buka", "status": "Buka",
<<<<<<< HEAD
"tipe_pendanaan": "Swasta",
"jenjang": "S1",
"negara": "Australia",
"tanggal_buka": "2023-09-29",
"tenggat_waktu": "2026-12-31",
"url": "https://www.schoters.com/id/beasiswa/la-trobe-university-la-trobe-high-achiever-scholarship-2",
"thumbnail": "https://images.unsplash.com/photo-1523240795612-9a054b0db644?w=400&q=80&auto=format&fit=crop", "thumbnail": "https://images.unsplash.com/photo-1523240795612-9a054b0db644?w=400&q=80&auto=format&fit=crop",
=======
>>>>>>> b5bc7bad0c7de6e63a60018da0fe06e923bf094c
"deskripsi_singkat": "Beasiswa dari La Trobe University Australia untuk mahasiswa internasional jenjang S1 berprestasi tinggi yang ingin melanjutkan studi di Melbourne.", "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.", "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": [ "syarat_umum": [
@@ -49,18 +61,23 @@
"Meet La Trobe University's entry and English language requirements" "Meet La Trobe University's entry and English language requirements"
], ],
"kontak": "La Trobe University Admissions — latrobe.edu.au", "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"] "tags": ["S1", "Swasta", "Australia", "Partially Funded"]
}, },
{ {
"id": "bsw-003", "id": 103,
"nama": "Charles Sturt University - International Student Support Scholarship", "nama": "Charles Sturt University - International Student Support Scholarship",
"kategori": "Swasta", "nama_en": "Charles Sturt University - International Student Support Scholarship",
"kategori_en": "Private",
"status": "Buka", "status": "Buka",
<<<<<<< HEAD
"tipe_pendanaan": "Swasta",
"jenjang": "S1",
"negara": "Australia",
"tanggal_buka": "2026-05-21",
"tenggat_waktu": "2026-12-31",
"url": "https://www.schoters.com/id/beasiswa/charles-sturt-university-international-student-support-scholarship-3",
"thumbnail": "https://images.unsplash.com/photo-1560472354-b33ff0c44a43?w=400&q=80&auto=format&fit=crop", "thumbnail": "https://images.unsplash.com/photo-1560472354-b33ff0c44a43?w=400&q=80&auto=format&fit=crop",
=======
>>>>>>> b5bc7bad0c7de6e63a60018da0fe06e923bf094c
"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": "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.", "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": [ "syarat_umum": [
@@ -76,18 +93,23 @@
"Apply through the official international admission pathway" "Apply through the official international admission pathway"
], ],
"kontak": "Charles Sturt University International — csu.edu.au", "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"] "tags": ["S1", "Swasta", "Australia", "Partially Funded"]
}, },
{ {
"id": "bsw-004", "id": 104,
"nama": "La Trobe University - La Trobe High Achiever Scholarship (S2)", "nama": "La Trobe University - La Trobe High Achiever Scholarship (S2)",
"kategori": "Swasta", "nama_en": "La Trobe University - La Trobe High Achiever Scholarship (S2)",
"kategori_en": "Private",
"status": "Buka", "status": "Buka",
<<<<<<< HEAD
"tipe_pendanaan": "Swasta",
"jenjang": "S2",
"negara": "Australia",
"tanggal_buka": "2023-09-29",
"tenggat_waktu": "2026-12-31",
"url": "https://www.schoters.com/id/beasiswa/la-trobe-university-la-trobe-high-achiever-scholarship-4",
"thumbnail": "https://images.unsplash.com/photo-1529156069898-49953e39b3ac?w=400&q=80&auto=format&fit=crop", "thumbnail": "https://images.unsplash.com/photo-1529156069898-49953e39b3ac?w=400&q=80&auto=format&fit=crop",
=======
>>>>>>> b5bc7bad0c7de6e63a60018da0fe06e923bf094c
"deskripsi_singkat": "Beasiswa dari La Trobe University Australia untuk mahasiswa internasional jenjang S2 (Postgraduate) dengan pencapaian akademik tinggi.", "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.", "deskripsi_singkat_en": "Scholarship from La Trobe University Australia for international postgraduate students with high academic achievements.",
"syarat_umum": [ "syarat_umum": [
@@ -103,18 +125,23 @@
"Not currently receiving another scholarship from the same university" "Not currently receiving another scholarship from the same university"
], ],
"kontak": "La Trobe University Admissions — latrobe.edu.au", "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"] "tags": ["S2", "Swasta", "Australia", "Partially Funded"]
}, },
{ {
"id": "bsw-005", "id": 105,
"nama": "ETH Zurich - Excellence Scholarship & Opportunity Programme (ESOP)", "nama": "ETH Zurich - Excellence Scholarship & Opportunity Programme (ESOP)",
"kategori": "Pemerintah", "nama_en": "ETH Zurich - Excellence Scholarship & Opportunity Programme (ESOP)",
"kategori_en": "Government",
"status": "Buka", "status": "Buka",
<<<<<<< HEAD
"tipe_pendanaan": "Pemerintah",
"jenjang": "S2",
"negara": "Swiss",
"tanggal_buka": "2026-11-01",
"tenggat_waktu": "2026-11-30",
"url": "https://www.schoters.com/id/beasiswa/eth-zurich-excellence-scholarship-opportunity-programme-esop-5",
"thumbnail": "https://images.unsplash.com/photo-1497366754035-f200968a6e72?w=400&q=80&auto=format&fit=crop", "thumbnail": "https://images.unsplash.com/photo-1497366754035-f200968a6e72?w=400&q=80&auto=format&fit=crop",
=======
>>>>>>> b5bc7bad0c7de6e63a60018da0fe06e923bf094c
"deskripsi_singkat": "Beasiswa bergengsi dari ETH Zurich (salah satu universitas teknik terbaik dunia) untuk mahasiswa S2 internasional yang unggul secara akademik.", "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.", "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": [ "syarat_umum": [
@@ -132,18 +159,23 @@
"Obtain a recommendation letter from authorized academics" "Obtain a recommendation letter from authorized academics"
], ],
"kontak": "ETH Zurich Scholarship Office — ethz.ch", "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"] "tags": ["S2", "Pemerintah", "Swiss", "Fully Funded", "Prestasi"]
}, },
{ {
"id": "bsw-006", "id": 106,
"nama": "STUST - Scholarship for International Students (S3, Fully Funded)", "nama": "STUST - Scholarship for International Students (S3, Fully Funded)",
"kategori": "Swasta", "nama_en": "STUST - Scholarship for International Students (S3, Fully Funded)",
"kategori_en": "Private",
"status": "Buka", "status": "Buka",
<<<<<<< HEAD
"tipe_pendanaan": "Swasta",
"jenjang": "S3",
"negara": "Taiwan",
"tanggal_buka": "2026-09-15",
"tenggat_waktu": "2026-11-30",
"url": "https://www.schoters.com/id/beasiswa/southern-taiwan-university-of-science-and-technology-the-stust-scholarship-for-international-students-6",
"thumbnail": "https://images.unsplash.com/photo-1580582932707-520aed937b7b?w=400&q=80&auto=format&fit=crop", "thumbnail": "https://images.unsplash.com/photo-1580582932707-520aed937b7b?w=400&q=80&auto=format&fit=crop",
=======
>>>>>>> b5bc7bad0c7de6e63a60018da0fe06e923bf094c
"deskripsi_singkat": "Beasiswa penuh (fully funded) dari Southern Taiwan University of Science and Technology untuk mahasiswa internasional jenjang S3 (Doktoral).", "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.", "deskripsi_singkat_en": "Fully funded scholarship from Southern Taiwan University of Science and Technology for international doctoral students.",
"syarat_umum": [ "syarat_umum": [
@@ -161,18 +193,23 @@
"Obtain approval from a prospective supervisor at STUST" "Obtain approval from a prospective supervisor at STUST"
], ],
"kontak": "STUST International Affairs Office — stust.edu.tw", "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"] "tags": ["S3", "Swasta", "Taiwan", "Fully Funded"]
}, },
{ {
"id": "bsw-007", "id": 107,
"nama": "STUST - Scholarship for International Students (S2, Partially Funded)", "nama": "STUST - Scholarship for International Students (S2, Partially Funded)",
"kategori": "Swasta", "nama_en": "STUST - Scholarship for International Students (S2, Partially Funded)",
"kategori_en": "Private",
"status": "Buka", "status": "Buka",
<<<<<<< HEAD
"tipe_pendanaan": "Swasta",
"jenjang": "S2",
"negara": "Taiwan",
"tanggal_buka": "2026-09-15",
"tenggat_waktu": "2026-11-30",
"url": "https://www.schoters.com/id/beasiswa/southern-taiwan-university-of-science-and-technology-the-stust-scholarship-for-international-students-7",
"thumbnail": "https://images.unsplash.com/photo-1606761568499-6d2451b23c66?w=400&q=80&auto=format&fit=crop", "thumbnail": "https://images.unsplash.com/photo-1606761568499-6d2451b23c66?w=400&q=80&auto=format&fit=crop",
=======
>>>>>>> b5bc7bad0c7de6e63a60018da0fe06e923bf094c
"deskripsi_singkat": "Beasiswa parsial (partially funded) dari Southern Taiwan University of Science and Technology untuk mahasiswa internasional jenjang S2 (Magister).", "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.", "deskripsi_singkat_en": "Partially funded scholarship from Southern Taiwan University of Science and Technology for international master's students.",
"syarat_umum": [ "syarat_umum": [
@@ -190,18 +227,23 @@
"Include academic transcripts and a motivation letter" "Include academic transcripts and a motivation letter"
], ],
"kontak": "STUST International Affairs Office — stust.edu.tw", "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"] "tags": ["S2", "Swasta", "Taiwan", "Partially Funded"]
}, },
{ {
"id": "bsw-008", "id": 108,
"nama": "University of Melbourne - Melbourne International Undergraduate Scholarship", "nama": "University of Melbourne - Melbourne International Undergraduate Scholarship",
"kategori": "Swasta", "nama_en": "University of Melbourne - Melbourne International Undergraduate Scholarship",
"kategori_en": "Private",
"status": "Buka", "status": "Buka",
<<<<<<< HEAD
"tipe_pendanaan": "Swasta",
"jenjang": "S1",
"negara": "Australia",
"tanggal_buka": "2026-05-21",
"tenggat_waktu": "2026-10-31",
"url": "https://www.schoters.com/id/beasiswa/university-of-melbourne-melbourne-international-undergraduate-scholarship-8",
"thumbnail": "https://images.unsplash.com/photo-1504711434969-e33886168f5c?w=400&q=80&auto=format&fit=crop", "thumbnail": "https://images.unsplash.com/photo-1504711434969-e33886168f5c?w=400&q=80&auto=format&fit=crop",
=======
>>>>>>> b5bc7bad0c7de6e63a60018da0fe06e923bf094c
"deskripsi_singkat": "Beasiswa untuk mahasiswa internasional berprestasi yang mendaftar program S1 di University of Melbourne, salah satu universitas terbaik di Australia.", "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.", "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": [ "syarat_umum": [
@@ -219,18 +261,23 @@
"The scholarship is awarded automatically based on academic merit, no separate application required" "The scholarship is awarded automatically based on academic merit, no separate application required"
], ],
"kontak": "University of Melbourne Future Students — unimelb.edu.au", "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"] "tags": ["S1", "Swasta", "Australia", "Partially Funded", "Prestasi"]
}, },
{ {
"id": "bsw-009", "id": 109,
"nama": "Charles Darwin University - CDU Global Merit Scholarship", "nama": "Charles Darwin University - CDU Global Merit Scholarship",
"kategori": "Swasta", "nama_en": "Charles Darwin University - CDU Global Merit Scholarship",
"kategori_en": "Private",
"status": "Buka", "status": "Buka",
<<<<<<< HEAD
"tipe_pendanaan": "Swasta",
"jenjang": "S1",
"negara": "Australia",
"tanggal_buka": "2026-05-21",
"tenggat_waktu": "2026-10-09",
"url": "https://www.schoters.com/id/beasiswa/charles-darwin-university-cdu-global-merit-scholarship-9",
"thumbnail": "https://images.unsplash.com/photo-1434030216411-0b793f4b4173?w=400&q=80&auto=format&fit=crop", "thumbnail": "https://images.unsplash.com/photo-1434030216411-0b793f4b4173?w=400&q=80&auto=format&fit=crop",
=======
>>>>>>> b5bc7bad0c7de6e63a60018da0fe06e923bf094c
"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": "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.", "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": [ "syarat_umum": [
@@ -248,18 +295,23 @@
"Not currently receiving any other tuition fee discount from CDU" "Not currently receiving any other tuition fee discount from CDU"
], ],
"kontak": "CDU International — cdu.edu.au", "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"] "tags": ["S1", "Swasta", "Australia", "Partially Funded"]
}, },
{ {
"id": "bsw-010", "id": 110,
"nama": "Deakin University - Vice-Chancellor's International Scholarship", "nama": "Deakin University - Vice-Chancellor's International Scholarship",
"kategori": "Swasta", "nama_en": "Deakin University - Vice-Chancellor's International Scholarship",
"kategori_en": "Private",
"status": "Buka", "status": "Buka",
<<<<<<< HEAD
"tipe_pendanaan": "Swasta",
"jenjang": "S1 & S2",
"negara": "Australia",
"tanggal_buka": "2026-05-21",
"tenggat_waktu": "2026-09-25",
"url": "https://www.schoters.com/id/beasiswa/deakin-university-vice-chancellor-s-international-scholarship-10",
"thumbnail": "https://images.unsplash.com/photo-1541339907198-e08756dedf3f?w=400&q=80&auto=format&fit=crop", "thumbnail": "https://images.unsplash.com/photo-1541339907198-e08756dedf3f?w=400&q=80&auto=format&fit=crop",
=======
>>>>>>> b5bc7bad0c7de6e63a60018da0fe06e923bf094c
"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": "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.", "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": [ "syarat_umum": [
@@ -277,9 +329,126 @@
"Submit the scholarship application before the specified closing date" "Submit the scholarship application before the specified closing date"
], ],
"kontak": "Deakin University International — deakin.edu.au", "kontak": "Deakin University International — deakin.edu.au",
<<<<<<< HEAD
"tags": ["S1 & S2", "Swasta", "Australia", "Partially Funded", "Prestasi"]
}
]
=======
"tanggal_buka": "2026-05-21", "tanggal_buka": "2026-05-21",
"tanggal_tutup": "2026-09-25", "tanggal_tutup": "2026-09-25",
"url_sumber": "https://www.schoters.com/id/beasiswa/deakin-university-vice-chancellor-s-international-scholarship-10", "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"] "tags": ["S1 & S2", "S2", "Swasta", "Australia", "Partially Funded", "Prestasi"]
},
{
"id": "bsw-011",
"nama": "Djarum Beasiswa Plus 2026/2027",
"nama_en": "Djarum Plus Scholarship 2026/2027",
"kategori": "Swasta",
"status": "Buka",
"deskripsi_singkat": "Program beasiswa prestasi dari Djarum Foundation untuk mahasiswa S1.",
"deskripsi_singkat_en": "Merit-based scholarship program from Djarum Foundation for undergraduate students.",
"syarat_umum": [
"Mahasiswa S1/D4 semester 4 dari semua disiplin ilmu.",
"IPK minimum 3.00 pada semester 3."
],
"syarat_umum_en": [
"4th-semester undergraduate (S1/D4) students from all disciplines.",
"Minimum GPA of 3.00 in the 3rd semester."
],
"kontak": "https://register.djarumbeasiswaplus.org/",
"tanggal_buka": "2026-04-08",
"tanggal_tutup": "2026-06-10",
"url_sumber": "https://register.djarumbeasiswaplus.org/",
"tags": ["S1", "D4", "Swasta", "Indonesia"]
},
{
"id": "bsw-012",
"nama": "Beasiswa Sobat Bumi Kalimantan 2026",
"nama_en": "Sobat Bumi Kalimantan Scholarship 2026",
"kategori": "Swasta",
"status": "Buka",
"deskripsi_singkat": "Beasiswa dari Pertamina Foundation khusus mahasiswa S1 di wilayah Kalimantan.",
"deskripsi_singkat_en": "Scholarship from Pertamina Foundation specifically for undergraduate students in the Kalimantan region.",
"syarat_umum": [
"Mahasiswa S1 aktif yang berdomisili atau kuliah di Kalimantan.",
"Memiliki kepedulian terhadap lingkungan hidup."
],
"syarat_umum_en": [
"Active undergraduate students domiciled or studying in Kalimantan.",
"Demonstrate care and commitment to the environment."
],
"kontak": "https://pertaminafoundation.org/",
"tanggal_buka": "2026-05-02",
"tanggal_tutup": "2026-07-01",
"url_sumber": "https://pertaminafoundation.org/",
"tags": ["S1", "Swasta", "Indonesia"]
},
{
"id": "bsw-013",
"nama": "Beasiswa Sehat Kemenkes 2026",
"nama_en": "Ministry of Health (Sehat) Scholarship 2026",
"kategori": "Pemerintah",
"status": "Segera Dibuka",
"deskripsi_singkat": "Program bantuan biaya pendidikan dari Kementerian Kesehatan untuk jenjang D3, D4, dan Profesi.",
"deskripsi_singkat_en": "Educational assistance program from the Ministry of Health for D3, D4, and Professional degrees.",
"syarat_umum": [
"Mahasiswa aktif jenjang D3, D4, atau Profesi di bidang kesehatan.",
"Memenuhi kualifikasi dan standar nilai dari Kementerian Kesehatan."
],
"syarat_umum_en": [
"Active D3, D4, or Professional degree students in the health sector.",
"Meet the qualifications and academic standards of the Ministry of Health."
],
"kontak": "https://sibk.kemkes.go.id/",
"tanggal_buka": "2026-06-08",
"tanggal_tutup": "2026-06-30",
"url_sumber": "https://sibk.kemkes.go.id/",
"tags": ["D3", "D4", "Profesi", "Pemerintah", "Indonesia"]
},
{
"id": "bsw-014",
"nama": "Beasiswa SDM Sawit 2026",
"nama_en": "Palm Oil Human Resources Scholarship 2026",
"kategori": "Pemerintah",
"status": "Buka",
"deskripsi_singkat": "Beasiswa pendanaan BPDPKS untuk pengembangan SDM Perkebunan Kelapa Sawit (D1-D4, S1).",
"deskripsi_singkat_en": "BPDPKS funding scholarship for the development of Palm Oil Plantation Human Resources (D1-D4, S1).",
"syarat_umum": [
"Terbuka untuk lulusan SMA/SMK/sederajat yang melanjutkan ke jenjang D1-D4 atau S1.",
"Bersedia ditempatkan atau mengabdi di sektor perkebunan kelapa sawit."
],
"syarat_umum_en": [
"Open to high school/vocational school graduates continuing to D1-D4 or S1 levels.",
"Willing to be placed or serve in the palm oil plantation sector."
],
"kontak": "https://sdmperkebunan.bpdp.or.id/",
"tanggal_buka": "2026-06-03",
"tanggal_tutup": "2026-06-20",
"url_sumber": "https://sdmperkebunan.bpdp.or.id/",
"tags": ["S1", "D1", "D3", "D4", "Pemerintah", "Indonesia"]
},
{
"id": "bsw-015",
"nama": "Beasiswa PPBP/PPTI BCA 2027",
"nama_en": "BCA PPBP/PPTI Scholarship 2027",
"kategori": "Swasta",
"status": "Buka",
"deskripsi_singkat": "Program Pendidikan Bisnis dan Perbankan (PPBP) serta Teknik Informatika (PPTI) dari BCA.",
"deskripsi_singkat_en": "Business and Banking Education Program (PPBP) and Informatics Engineering (PPTI) from BCA.",
"syarat_umum": [
"Lulusan SMA/SMK/sederajat yang berprestasi.",
"Lulus seleksi administrasi dan serangkaian tes dari BCA."
],
"syarat_umum_en": [
"High-achieving high school/vocational school graduates.",
"Pass the administrative selection and a series of tests from BCA."
],
"kontak": "https://bcafinance.co.id/Pendaftaran-Beasiswa-BCA-Finance-Peduli-2026-Dibuka",
"tanggal_buka": "2026-05-01",
"tanggal_tutup": "2026-10-20",
"url_sumber": "https://bcafinance.co.id/Pendaftaran-Beasiswa-BCA-Finance-Peduli-2026-Dibuka",
"tags": ["S1", "Swasta", "Indonesia"]
} }
]
]
>>>>>>> b5bc7bad0c7de6e63a60018da0fe06e923bf094c
+125
View File
@@ -0,0 +1,125 @@
# Panduan Pengelolaan Data Kelompok C4A
Dokumentasi ini dibuat untuk memudahkan pengembang atau pengguna berikutnya dalam mengelola data Staf Pendukung, SOP, dan FAQ pada halaman C4A.
---
## 📋 Aturan Umum Pengelolaan Data
1. **Lokasi Berkas Data**:
Seluruh data disimpan dalam format JSON di direktori `groups/C4A/data/`:
- Data Staf: `groups/C4A/data/data-staff.json`
- Data SOP: `groups/C4A/data/data-sop.json`
- Data FAQ: `groups/C4A/data/data-faq.json`
2. **Dukungan Bilingual (ID/EN)**:
Semua teks konten yang ditampilkan ke pengguna mendukung dua bahasa. Anda perlu menggunakan objek dengan properti `id` (Bahasa Indonesia) dan `en` (Bahasa Inggris) pada setiap field teks. Jika properti `en` tidak diisi atau data masih berupa *string* biasa, sistem akan otomatis menggunakan Bahasa Indonesia sebagai *fallback*.
3. **Ikon FontAwesome (v4)**:
Jika ingin mengubah atau menambahkan logo/ikon pada data SOP, harap gunakan daftar ikon dari **FontAwesome v4** melalui tautan berikut:
👉 **[FontAwesome v4 Icons](https://fontawesome.com/v4/icons/)** (Contoh penggunaan: `fa-dot-circle-o`, `fa-graduation-cap`, `fa-flask`).
4. **Field Opsional**:
Pada data SOP, properti **`staff_responsible`** (Staf Bertanggung Jawab) dan **`additional_info`** (Informasi Tambahan) bersifat **opsional**. Jika informasi tersebut tidak tersedia, kosongkan nilainya atau hapus propertinya dari JSON agar UI secara otomatis menyembunyikan bloknya.
---
## 🛠️ Template Penambahan Data
Silakan salin-tempel (copy-paste) format di bawah ini ke dalam array JSON yang sesuai di folder `data/` untuk menambahkan data baru.
### 1. Template Data Staf (`data-staff.json`)
```json
{
"id": 3,
"name": "Nama Staf Beserta Gelar",
"role": {
"id": "Peran / Jabatan Staf",
"en": "Staff Role / Title"
},
"image": "assets/images/nama-file-foto.png",
"location": {
"id": "Lokasi Ruang Kerja / Kantor Staf",
"en": "Staff Workspace / Office Location"
},
"contact_consent": true,
"phone": "628xxxxxxxxxx",
"schedule": [
{
"day": {
"id": "Hari Kerja (contoh: Senin Jumat)",
"en": "Work Days (e.g., Monday Friday)"
},
"time": "07.3016.00 WIB",
"break": {
"id": "Jam Istirahat (contoh: istirahat 12.0013.00 WIB)",
"en": "Break Time (e.g., break 12.0013.00 WIB)"
}
}
],
"tasks": [
{
"id": "Tugas dan tanggung jawab utama 1",
"en": "Main task and responsibility 1"
},
{
"id": "Tugas dan tanggung jawab utama 2",
"en": "Main task and responsibility 2"
}
]
}
```
*Catatan: Letakkan file gambar/foto staf ke dalam direktori `groups/C4A/assets/images/` dan masukkan nama berkas fotonya pada properti `image` di atas. Untuk nomor telepon (`phone`), Anda dapat menggunakan format `08xxxxxxxxxx` atau `628xxxxxxxxxx` karena skrip JavaScript akan melakukan normalisasi secara otomatis.*
### 2. Template Data SOP (`data-sop.json`)
```json
{
"tab_id": "id-unik-sop-tanpa-spasi",
"tab_number": 9,
"category": "akademik",
"icon": "fa-file-text",
"title": {
"id": "Judul Lengkap SOP",
"en": "Full SOP Title"
},
"description": {
"id": "Deskripsi singkat mengenai SOP ini.",
"en": "Brief description of this SOP."
},
"purpose": {
"id": "Tujuan dibuatnya standar operasional prosedur ini.",
"en": "The purpose of creating this standard operating procedure."
},
"requirements": [
{ "id": "Persyaratan administratif/akademik 1", "en": "Administrative/academic requirement 1" },
{ "id": "Persyaratan administratif/akademik 2", "en": "Administrative/academic requirement 2" }
],
"procedures": [
{ "id": "Langkah alur prosedur ke-1", "en": "Procedure flow step 1" },
{ "id": "Langkah alur prosedur ke-2", "en": "Procedure flow step 2" }
],
"staff_responsible": [
"👤 Staf Administrasi Informatika / Informatics Administrative Staff"
],
"additional_info": {
"id": "Informasi tambahan penting.",
"en": "Important additional information."
}
}
```
*Catatan: Properti `category` bernilai antara `"akademik"` atau `"praktikum"`. Properti `staff_responsible` dan `additional_info` bersifat opsional.*
### 3. Template Data FAQ (`data-faq.json`)
```json
{
"id": 11,
"question": {
"id": "Pertanyaan yang sering diajukan?",
"en": "Frequently asked question?"
},
"answer": {
"id": "Jawaban dari pertanyaan tersebut. Boleh menggunakan tag HTML seperti <strong>tebal</strong>.",
"en": "Answer to the question. HTML tags like <strong>bold</strong> can be used."
}
}
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

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

After

Width:  |  Height:  |  Size: 644 KiB

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

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

+745 -10
View File
@@ -4,6 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Group 01 &ndash; Jellyfish.Corp | Informatika UNTAN</title> <title>Group 01 &ndash; Jellyfish.Corp | Informatika UNTAN</title>
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="../../assets/global/output.css"> <link rel="stylesheet" href="../../assets/global/output.css">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
@@ -1140,17 +1141,751 @@
</div> </div>
<main id="main-content" class="pt-[88px] lg:pt-[160px] min-h-screen bg-white"> <main id="main-content" class="pt-[88px] lg:pt-[160px] min-h-screen bg-white">
<div class="container mx-auto px-4 lg:px-8 py-12"> <div class="container">
<div class="mb-8 pb-8 border-b border-slate-200">
<span class="bg-untan-yellow text-slate-900 text-[10px] font-bold uppercase tracking-widest px-2 py-0.5 inline-block mb-3">Tugas Halaman Statis</span>
<h1 class="text-3xl lg:text-4xl font-bold text-slate-900 mb-2">Judul Halaman Group 01</h1>
<p class="text-slate-500 text-sm">Kelompok 01 &mdash; Jellyfish.Corp &nbsp;&middot;&nbsp; Informatika UNTAN 2026</p>
</div>
<div id="assignment-body" class="prose prose-slate max-w-none"> <div id="assignment-body" class="prose prose-slate max-w-none">
<!-- ================= START: BAGIAN YANG DIEDIT KELOMPOK ================= --> <!-- ================= START: BAGIAN YANG DIEDIT KELOMPOK ================= -->
<p>Tulis konten halaman kelompok di sini.</p> <!-- ===================== A. HERO ===================== -->
<h3>Subjudul</h3> <section class="jf-hero-landing">
<p>Gunakan paragraf, list, tabel, gambar, dan elemen konten lainnya.</p> <div class="jf-hero-landing-bg"></div>
<div class="container jf-hero-landing-inner">
<div class="jf-hero-landing-content content">
<h1 class="jf-hero-headline" data-i18n="grp_jellyfish_hero_title">Sekilas Informatika<br>
<h1 class="jf-hero-headline" data-i18n="grp_jellyfish_hero_subtitle" style="color: #feb401">Universitas Tanjungpura</h1>
<p class="jf-hero-sub" data-i18n="grp_jellyfish_hero_desc">Mencetak talenta digital unggulan dari Kalimantan Barat untuk Indonesia dan
dunia. Bergabunglah bersama kami dalam perjalanan membangun masa depan teknologi.</p>
<div class="jf-hero-ctas">
<a href="#kontak" class="jf-hero-cta-primary" data-i18n="grp_jellyfish_btn_register">Daftar Sekarang</a>
<a href="#tentang" class="jf-hero-cta-secondary" data-i18n="grp_jellyfish_btn_learn">Pelajari Lebih Lanjut</a>
</div>
<div class="jf-hero-stats">
<div class="jf-hs-item">
<span class="jf-hs-num">2004</span>
<span class="jf-hs-label" data-i18n="grp_jellyfish_stat_founded">Tahun Berdirinya</span>
</div>
<div class="jf-hs-div"></div>
<div class="jf-hs-item">
<span class="jf-hs-num">512</span>
<span class="jf-hs-label" data-i18n="grp_jellyfish_stat_students">Mahasiswa Aktif</span>
</div>
<div class="jf-hs-div"></div>
</div>
</div>
</div>
</section>
<!-- ===== MAIN CONTENT ===== -->
<main id="assignment-body">
<!-- ===================== B. TENTANG PRODI ===================== -->
<section class="jf-section jf-section-white" id="tentang">
<div class="container ">
<div class="jf-about-layout content">
<div class="jf-about-left">
<span class="jf-badge" data-i18n="grp_jellyfish_about_badge">Tentang Prodi</span>
<h2 class="jf-section-title" data-i18n="grp_jellyfish_about_title">Mengenal Program Studi Informatika UNTAN</h2>
<p class="jf-about-intro" data-i18n="grp_jellyfish_about_intro">Program Studi Informatika Universitas Tanjungpura hadir sejak <strong>2004</strong> 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.</p>
<div class="jf-visi-misi-cards">
<div class="jf-vm-card jf-vm-visi">
<div class="jf-vm-label" data-i18n="grp_jellyfish_vision_label">🎯 Visi</div>
<p data-i18n="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.</p>
</div>
</div>
<div class="jf-misi-section">
<div class="jf-misi-label" data-i18n="grp_jellyfish_mission_label">📋 Misi</div>
<div class="jf-misi-list">
<div class="jf-misi-item">
<div class="jf-misi-num">1</div>
<div>
<strong data-i18n="grp_jellyfish_mission_1_title">Pendidikan OBE Unggul</strong>
<p data-i18n="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.</p>
</div>
</div>
<div class="jf-misi-item">
<div class="jf-misi-num">2</div>
<div>
<strong data-i18n="grp_jellyfish_mission_2_title">Penelitian untuk SDGs</strong>
<p data-i18n="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.</p>
</div>
</div>
<div class="jf-misi-item">
<div class="jf-misi-num">3</div>
<div>
<strong data-i18n="grp_jellyfish_mission_3_title">Pengabdian Berbasis Kearifan Lokal</strong>
<p data-i18n="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.</p>
</div>
</div>
<div class="jf-misi-item">
<div class="jf-misi-num">4</div>
<div>
<strong data-i18n="grp_jellyfish_mission_4_title">Kemitraan Strategis</strong>
<p data-i18n="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.</p>
</div>
</div>
<div class="jf-misi-item">
<div class="jf-misi-num">5</div>
<div>
<strong data-i18n="grp_jellyfish_mission_5_title">Budaya Akademik Beretika</strong>
<p data-i18n="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.</p>
</div>
</div>
</div>
</div>
</div>
<div class="jf-about-right">
<div class="jf-timeline-card">
<div class="jf-timeline-title" data-i18n="grp_jellyfish_timeline_title">Tonggak Sejarah</div>
<div class="jf-timeline-list">
<div class="jf-tl-item">
<div class="jf-tl-year">2004</div>
<div class="jf-tl-dot"></div>
<div class="jf-tl-content">
<strong data-i18n="grp_jellyfish_timeline_2004_title">Pendirian Program Studi</strong>
<span data-i18n="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.</span>
</div>
</div>
<div class="jf-tl-item">
<div class="jf-tl-year">2005</div>
<div class="jf-tl-dot"></div>
<div class="jf-tl-content">
<strong data-i18n="grp_jellyfish_timeline_2005_title">SNMPTN Pertama</strong>
<span data-i18n="grp_jellyfish_timeline_2005_desc">Penerimaan mahasiswa baru mulai diintegrasikan secara nasional melalui jalur Seleksi Nasional Masuk Perguruan Tinggi Negeri (SNMPTN).</span>
</div>
</div>
<div class="jf-tl-item">
<div class="jf-tl-year">2006</div>
<div class="jf-tl-dot"></div>
<div class="jf-tl-content">
<strong data-i18n="grp_jellyfish_timeline_2006_title">Akreditasi C</strong>
<span data-i18n="grp_jellyfish_timeline_2006_desc">Program studi memperoleh peringkat Akreditasi C dari BAN-PT sebagai langkah awal penjaminan mutu akademik secara sistematis.</span>
</div>
</div>
<div class="jf-tl-item">
<div class="jf-tl-year">2008</div>
<div class="jf-tl-dot"></div>
<div class="jf-tl-content">
<strong data-i18n="grp_jellyfish_timeline_2008_title">Kelas Malam & SPOTA</strong>
<span data-i18n="grp_jellyfish_timeline_2008_desc">Pembukaan program kelas sore/malam serta pengembangan SPOTA (Sistem Pendukung Outline Tugas Akhir) sebagai aplikasi inovatif perdana program studi.</span>
</div>
</div>
<div class="jf-tl-item">
<div class="jf-tl-year">2016</div>
<div class="jf-tl-dot"></div>
<div class="jf-tl-content">
<strong data-i18n="grp_jellyfish_timeline_2016_title">Akreditasi B</strong>
<span data-i18n="grp_jellyfish_timeline_2016_desc">Peningkatan standar mutu akademik yang ditandai dengan pencapaian peringkat Akreditasi B dari BAN-PT.</span>
</div>
</div>
<div class="jf-tl-item">
<div class="jf-tl-year">2017</div>
<div class="jf-tl-dot"></div>
<div class="jf-tl-content">
<strong data-i18n="grp_jellyfish_timeline_2017_title">Nama Resmi Berubah</strong>
<span data-i18n="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.).</span>
</div>
</div>
<div class="jf-tl-item">
<div class="jf-tl-year">2019</div>
<div class="jf-tl-dot"></div>
<div class="jf-tl-content">
<strong data-i18n="grp_jellyfish_timeline_2019_title">Angkatan PPAPK Terakhir</strong>
<span data-i18n="grp_jellyfish_timeline_2019_desc">Penyelenggaraan penerimaan mahasiswa jalur non-reguler (PPAPK) angkatan terakhir sebelum resmi dinonaktifkan.</span>
</div>
</div>
<div class="jf-tl-item">
<div class="jf-tl-year">2021</div>
<div class="jf-tl-dot"></div>
<div class="jf-tl-content">
<strong data-i18n="grp_jellyfish_timeline_2021_title">EdoXid Lahir</strong>
<span data-i18n="grp_jellyfish_timeline_2021_desc">Peluncuran EdoXid, sistem informasi manajemen sidang tugas akhir berbasis digital guna memfasilitasi kebutuhan akademik selama masa pandemi.</span>
</div>
</div>
<div class="jf-tl-item">
<div class="jf-tl-year">2023</div>
<div class="jf-tl-dot"></div>
<div class="jf-tl-content">
<strong data-i18n="grp_jellyfish_timeline_2023_title">Guru Besar Pertama</strong>
<span data-i18n="grp_jellyfish_timeline_2023_desc">Pengukuhan Guru Besar pertama Program Studi Informatika UNTAN, yaitu Prof. Dr. Herry Sujaini, S.T., M.T.</span>
</div>
</div>
<div class="jf-tl-item">
<div class="jf-tl-year">2025</div>
<div class="jf-tl-dot"></div>
<div class="jf-tl-content">
<strong data-i18n="grp_jellyfish_timeline_2025_title">Kaprodi Ke-5</strong>
<span data-i18n="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.</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- ===================== C. ANGKA ===================== -->
<section class="jf-section jf-section-light">
<div class="container content">
<div class="jf-section-header jf-center">
<span class="jf-badge" data-i18n="grp_jellyfish_stats_badge">Data &amp; Statistik</span>
<h2 class="jf-section-title" data-i18n="grp_jellyfish_stats_title">Informatika UNTAN dalam Angka</h2>
<p class="jf-section-sub">
<center data-i18n="grp_jellyfish_stats_subtitle">Bukan sekadar angka, ini adalah cerita nyata dari perjalanan panjang Informatika UNTAN.</center>
</p>
</div>
<div class="jf-stats-grid">
<div class="jf-stat-card jf-stat-blue">
<div class="jf-stat-num">518</div>
<div class="jf-stat-label" data-i18n="grp_jellyfish_mhs_aktif">Mahasiswa Aktif</div>
<div class="jf-stat-sub" data-i18n="grp_jellyfish_tahun_akademik">Tahun Akademik 2024/2025</div>
</div>
<div class="jf-stat-card jf-stat-yellow">
<div class="jf-stat-num">213</div>
<div class="jf-stat-label" data-i18n="grp_jellyfish_total_alumni">Total Alumni</div>
<div class="jf-stat-sub" data-i18n="grp_jellyfish_data_total_alumni">Data sejak tahun 2022</div>
</div>
<div class="jf-stat-card jf-stat-green">
<div class="jf-stat-num" data-i18n="grp_jellyfish_bulan_tunggu"> 6 Bulan</div>
<div class="jf-stat-label" data-i18n="grp_jellyfish_waktu_tunggu_kerja">Waktu Tunggu Kerja</div>
<div class="jf-stat-sub" data-i18n="grp_jellyfish_rata_lulusan">Rata-rata lulusan terserap</div>
</div>
</div>
<div class="jf-stats-row2">
<div class="jf-stat-row-card">
<div class="jf-stat-row-label" data-i18n="grp_jellyfish_dosen_tetap">Dosen Tetap</div>
<div class="jf-stat-row-num" style="color:#005eb8;">33</div>
<p style="font-size:0.82rem;color:#475569;margin-top:0.4rem;" data-i18n="grp_jellyfish_desc_dosen">Berpengalaman dan bersertifikasi
di bidangnya masing-masing.</p>
</div>
<div class="jf-stat-row-card">
<div class="jf-stat-row-label" data-i18n="grp_jellyfish_jurnal_ilmiah">Jurnal Ilmiah</div>
<div class="jf-stat-row-num" style="color:#feb401;" data-i18n="grp_jellyfish_jurnal">400+ Jurnal</div>
<div style="margin-top:0.4rem; font-size:0.82rem; color:#475569;">
<div style="margin-bottom:0.2rem;">📄 JEPIN &nbsp;·&nbsp; JUSTIN</div>
<div>📄 JURISTY &nbsp;·&nbsp; JUARA</div>
</div>
</div>
<div class="jf-stat-row-card">
<div class="jf-stat-row-label" data-i18n="grp_jellyfish_guru_besar">Guru Besar</div>
<div class="jf-stat-row-num" style="color:#10b981;" data-i18n="grp_jellyfish_jlh_guru_besar">1 Guru Besar</div>
<p style="font-size:0.82rem;color:#475569;margin-top:0.4rem;" data-i18n="grp_jellyfish_nama_guru_besar">Prof. Dr. Herry Sujaini, S.T., M.T. — Guru Besar pertama
Informatika UNTAN (sejak 2023).</p>
</div>
</div>
<div class="jf-rasio-wrapper" style="display:flex; justify-content:center; margin-top:1rem;">
<div class="jf-stat-row-card" style="width:calc(33.333% - 1rem);">
<div class="jf-stat-row-label" data-i18n="grp_jellyfish_rasio_dosen">Rasio Dosen : Mahasiswa</div>
<div class="jf-stat-row-num" style="color:#7c3aed;font-size:2rem;">1 : 11</div>
<div style="font-size:0.75rem;color:#94a3b8;" data-i18n="grp_jellyfish_rasio_standar">Memenuhi standar BAN-PT</div>
</div>
</div>
</div>
</section>
<!-- ===================== D. PENCAPAIAN ===================== -->
<section class="jf-section jf-section-white">
<div class="container content">
<div class="jf-section-header jf-center">
<span class="jf-badge" data-i18n="grp_jellyfish_achievement_badge">Prestasi</span>
<h2 class="jf-section-title" data-i18n="grp_jellyfish_achievement_title">Pencapaian Membanggakan</h2>
<p class="jf-section-sub">
<center data-i18n="grp_jellyfish_achievement_subtitle">Dari panggung nasional hingga kolaborasi internasional — Informatika UNTAN hadir,
bersaing, dan meninggalkan jejak.</center>
</p>
</div>
<div class="jf-filter-tabs">
<button class="jf-tab-btn jf-tab-active" data-filter="all" data-i18n="grp_jellyfish_btn_filter_semua">Semua</button>
<button class="jf-tab-btn" data-filter="kompetisi" data-i18n="grp_jellyfish_btn_filter_kompetisi">Kompetisi</button>
<button class="jf-tab-btn" data-filter="publikasi" data-i18n="grp_jellyfish_btn_filter_publikasi">Publikasi &amp; Pengabdian</button>
<button class="jf-tab-btn" data-filter="aplikasi" data-i18n="grp_jellyfish_btn_filter_aplikasi">Aplikasi Unggulan</button>
</div>
<div class="jf-prestasi-grid" id="prestasi-grid">
<div class="jf-prestasi-card" data-cat="kompetisi">
<span class="jf-prest-badge jf-prest-nasional" data-i18n="badge_competition">Kompetisi</span>
<h4 data-i18n="grp_jellyfish_prest_compfest_title">Finalis 10 Besar Competitive Programming Contest (COMPFEST) 17</h4>
<p data-i18n="grp_jellyfish_prest_compfest_desc">Berhasil meraih posisi 10 besar nasional pada COMPFEST 17, kompetisi pemrograman yang diselenggarakan oleh Universitas Indonesia (UI).</p>
<div class="jf-prest-meta"><span data-i18n="label_year_2025">📅 2025 </span><span data-i18n="label_finalist">Finalis</span></div>
</div>
<div class="jf-prestasi-card" data-cat="kompetisi">
<span class="jf-prest-badge jf-prest-nasional" data-i18n="badge_competition">Kompetisi</span>
<h4 data-i18n="grp_jellyfish_prest_gemastik18_title">Tim Regional Terbaik GEMASTIK XVIII bidang Competitive Programming</h4>
<p data-i18n="grp_jellyfish_prest_gemastik18_desc">Berhasil menjadi Tim Regional Terbaik pada GEMASTIK XVIII pada bidang Competitive Programming</p>
<div class="jf-prest-meta"><span>📅 2025</span><span data-i18n="label_team_best">Tim Regional Terbaik</span></div>
</div>
<div class="jf-prestasi-card" data-cat="kompetisi">
<span class="jf-prest-badge jf-prest-nasional" data-i18n="badge_competition">Kompetisi</span>
<h4 data-i18n="grp_jellyfish_prest_icpc_title">International Collegiate Programming Contest predikat Honor</h4>
<p data-i18n="grp_jellyfish_prest_icpc_desc">Tim competitive programming Informatika UNTAN berhasil lolos ke babak regional ICPC Asia Jakarta 2025.</p>
<div class="jf-prest-meta"><span>📅 2025</span><span data-i18n="label_honor">Honor</span></div>
</div>
<div class="jf-prestasi-card" data-cat="kompetisi">
<span class="jf-prest-badge jf-prest-nasional" data-i18n="badge_competition">Kompetisi</span>
<h4 data-i18n="grp_jellyfish_prest_gemastik17_title">Tim Regional Terbaik GEMASTIK XVII bidang Competitive Programming</h4>
<p data-i18n="grp_jellyfish_prest_gemastik17_desc">Berhasil menjadi Tim Regional Terbaik pada GEMASTIK XVII pada bidang Competitive Programming</p>
<div class="jf-prest-meta"><span>📅 2024</span><span data-i18n="label_team_best">Tim Regional Terbaik</span></div>
</div>
<div class="jf-prestasi-card" data-cat="kompetisi">
<span class="jf-prest-badge jf-prest-nasional" data-i18n="badge_competition">Kompetisi</span>
<h4 data-i18n="grp_jellyfish_prest_mtq_title">LOMBA MTQ TINGKAT PROVINSI KALIMANTAN BARAT PESERTA CABANG TAHFIDZ HADITS GOLONGAN 100 HADIST DENGAN SANAD PUTRI</h4>
<p data-i18n="grp_jellyfish_prest_mtq_desc">Berpastisipasi pada MTQ tingkat provinsi Kalimantan Barat cabang Tahfidz Hadits 100 Hadits Bersanad Putri</p>
<div class="jf-prest-meta"><span>📅 2024</span><span data-i18n="label_participant">PESERTA</span></div>
</div>
<div class="jf-prestasi-card" data-cat="publikasi">
<span class="jf-prest-badge jf-prest-publikasi" data-i18n="badge_publication">Publikasi</span>
<h4 data-i18n="grp_jellyfish_prest_crm_title">Customer Relationship Management (CRM) System pada Peningkatan Layanan Pengguna E-Learning</h4>
<p data-i18n="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.</p>
<div class="jf-prest-meta"><span>📅 2025</span></div>
</div>
<div class="jf-prestasi-card" data-cat="publikasi">
<span class="jf-prest-badge jf-prest-publikasi" data-i18n="badge_publication">Publikasi</span>
<h4 data-i18n="grp_jellyfish_prest_dbscan_title">REKOMENDASI AREA PEMASARAN BAGI PEDAGANG SAYUR KELILING MENGGUNAKAN METODE DBSCAN DAN VISUALISASI HEATMAP</h4>
<p data-i18n="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.</p>
<div class="jf-prest-meta"><span>📅 2024</span></div>
</div>
<div class="jf-prestasi-card" data-cat="publikasi">
<span class="jf-prest-badge jf-prest-publikasi" data-i18n="badge_publication">Publikasi</span>
<h4 data-i18n="grp_jellyfish_prest_kmeans_title">Data mining untuk pengelompokan saham pada sektor energi dengan metode k-means</h4>
<p data-i18n="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.</p>
<div class="jf-prest-meta"><span>📅 2023</span></div>
</div>
<div class="jf-prestasi-card" data-cat="publikasi">
<span class="jf-prest-badge jf-prest-penghargaan" data-i18n="badge_community">Pengabdian Masyarakat</span>
<h4 data-i18n="grp_jellyfish_prest_kodingmu_title">KODINGMU PLATFORM BELAJAR COMPUTATIONAL THINKING BAGI SISWA SMAN 1 MEMPAWAH HILIR</h4>
<p data-i18n="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.</p>
<div class="jf-prest-meta"><span>📅 2025</span></div>
</div>
<div class="jf-prestasi-card" data-cat="aplikasi">
<span class="jf-prest-badge" style="background:#e0f2fe;color:#0369a1;" data-i18n="badge_innovation">Inovasi Aplikasi</span>
<h4 data-i18n="grp_jellyfish_prest_spota_title">SPOTA (2008)</h4>
<p data-i18n="grp_jellyfish_prest_spota_desc">Sistem Pendukung Outline Tugas Akhir — lahir dari ide dosen, dibangun mahasiswa, masih digunakan hingga kini. Inovasi yang melampaui zamannya.</p>
<div class="jf-prest-meta"><span>📅 2008</span><span data-i18n="label_still_active">💡 Masih aktif</span></div>
</div>
<div class="jf-prestasi-card" data-cat="aplikasi">
<span class="jf-prest-badge" style="background:#e0f2fe;color:#0369a1;" data-i18n="badge_innovation">Inovasi Aplikasi</span>
<h4 data-i18n="grp_jellyfish_prest_edoxid_title">EdoXid (2021)</h4>
<p data-i18n="grp_jellyfish_prest_edoxid_desc">Sistem pengajuan sidang digital — hadir menjawab pandemi, kini naik kelas ke skala Fakultas MIPA Universitas Tanjungpura.</p>
<div class="jf-prest-meta"><span>📅 2021</span><span data-i18n="label_faculty_scale">🚀 Skala Fakultas</span></div>
</div>
</div>
</div>
</section>
<!-- ===================== E. STUDY CLUB ===================== -->
<section class="jf-section jf-section-light">
<div class="container content">
<div class="jf-section-header jf-center">
<span class="jf-badge" data-i18n="grp_jellyfish_studyclub_badge">Komunitas</span>
<h2 class="jf-section-title" data-i18n="grp_jellyfish_studyclub_title">Study Club</h2>
<p class="jf-section-sub">
<center data-i18n="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.</center>
</p>
</div>
<div class="jf-sc-grid">
<div class="jf-sc-card">
<div class="jf-sc-icon" style="background:#eff6ff;">💻</div>
<div class="jf-sc-name">ARISE</div>
<div class="jf-sc-alias">&nbsp; Competitive Programming</div>
<p data-i18n="grp_jellyfish_arise">Program kerja ini berfokus pada peminatan <em>competitive programming</em>. Mahasiswa dilatih
untuk mengasah kemampuan pemecahan masalah dan algoritma melalui kompetisi pemrograman
bergengsi tingkat nasional maupun internasional.</p>
</div>
<div class="jf-sc-card">
<div class="jf-sc-icon" style="background:#f0fdf4;">🛠️</div>
<div class="jf-sc-name">Software Development</div>
<div class="jf-sc-alias">&nbsp; Software Development </div>
<p data-i18n="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.</p>
</div>
<div class="jf-sc-card">
<div class="jf-sc-icon" style="background:#fefce8;">📊</div>
<div class="jf-sc-name">MINERVA</div>
<div class="jf-sc-alias">&nbsp; Data Mining</div>
<p data-i18n="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.</p>
</div>
</div>
<div class="jf-sc-cta">
<div class="jf-sc-cta-left">
<strong data-i18n="grp_jellyfish_join_studyclub">Bergabung dengan Study Club</strong>
<span data-i18n="grp_jellyfish_studyclub">Study club ini terbuka bagi seluruh mahasiswa aktif Prodi Informatika UNTAN. Pilih
peminatan yang sesuai dengan minat dan tujuan karirmu.</span>
</div>
<a href="mailto:info@informatika.untan.ac.id" class="jf-cta-button" data-i18n="grp_jellyfish_study_info">Info Lebih Lanjut →</a>
</div>
</div>
</section>
<!-- ===================== F. KARIR ===================== -->
<section class="jf-section jf-section-white">
<div class="container content">
<div class="jf-karir-layout">
<div class="jf-karir-left">
<span class="jf-badge" data-i18n="grp_jellyfish_career_badge">Karir</span>
<h2 class="jf-section-title" data-i18n="grp_jellyfish_career_title">Masa Depan Lulusan Informatika</h2>
<p data-i18n="grp_jellyfish_career_desc">Lulusan Prodi Informatika UNTAN diserap secara luas oleh berbagai sektor — mulai dari instansi pemerintah, korporasi swasta, lembaga pendidikan, hingga wirausaha mandiri. Kompetensi digital yang kuat membuka peluang karir di mana saja.</p>
<div class="jf-karir-other" style="margin-top:1.5rem;">
<strong data-i18n="grp_jellyfish_bidang_karir">Bidang Karir</strong>
<div class="jf-chip-wrap" style="margin-top:0.5rem;">
<span class="jf-chip">Web Developer</span>
<span class="jf-chip">Data Engineer</span>
<span class="jf-chip">Mobile Developer</span>
<span class="jf-chip">UI / UX Designer</span>
<span class="jf-chip">Networking / DevOps</span>
<span class="jf-chip">Cybersecurity</span>
<span class="jf-chip">Machine Learning / AI</span>
<span class="jf-chip">Database Engineer</span>
<span class="jf-chip" data-i18n="grp_jellyfish_akademisi">Akademisi / Dosen</span>
</div>
</div>
</div>
<div class="jf-karir-right">
<div class="jf-karir-card jf-karir-navy">
<div class="jf-kc-title" data-i18n="grp_jellyfish_pemerintah">Pemerintah &amp; Instansi Publik</div>
<div class="jf-kc-chips">
<span>Diskominfo</span>
<span>Bappeda</span>
<span>BKPSDM</span>
<span>Bapenda</span>
<span>KPU</span>
<span>BRIN</span>
</div>
</div>
<div class="jf-karir-card jf-karir-purple">
<div class="jf-kc-title" data-i18n="grp_jellyfish_industri">Perusahaan Swasta &amp; Korporasi</div>
<div class="jf-kc-chips">
<span>PT Telkom Indonesia</span>
<span>Telkomsel</span>
<span>PLN Icon Plus</span>
<span>Bank BCA</span>
<span>Grab</span>
<span>Astra Daihatsu</span>
</div>
</div>
<div class="jf-karir-card jf-karir-green">
<div class="jf-kc-title" data-i18n="grp_jellyfish_perguruan_tinggi">Pendidikan, Organisasi &amp; Lembaga Sosial</div>
<div class="jf-kc-chips">
<span>UNTAN</span>
<span>Politeknik Negeri Sambas</span>
<span>STIKES Sambas</span>
<span>SMA / SMP / SD</span>
<span>Organisasi Keagamaan</span>
</div>
</div>
<div class="jf-karir-card jf-karir-orange">
<div class="jf-kc-title" data-i18n="grp_jellyfish_wirausaha">Wirausaha, UMKM &amp; Freelance</div>
<div class="jf-kc-chips">
<span>Freelancer</span>
<span>Web Developer</span>
<span>IT Consultant</span>
<span>Startup Founder</span>
<span>Creative Studio</span>
<span>UMKM Digital</span>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- ===================== G. ALUMNI ===================== -->
<section class="jf-section jf-section-light">
<div class="container content">
<div class="jf-section-header jf-center">
<span class="jf-badge" data-i18n="grp_jellyfish_alumni_badge">Alumni</span>
<h2 class="jf-section-title" data-i18n="grp_jellyfish_alumni_title">Kisah Sukses Alumni</h2>
<p class="jf-section-sub">
<center data-i18n="grp_jellyfish_alumni_subtitle">Mereka adalah bukti nyata bahwa Informatika UNTAN adalah batu loncatan menuju karir gemilang — dari Pontianak, ke panggung dunia.</center>
</p>
</div>
<div class="jf-alumni-grid">
<div class="jf-alumni-card">
<div class="jf-alumni-header">
<div class="jf-alumni-avatar" style="background:linear-gradient(135deg,#005eb8,#00b5e2);">IF
</div>
<div>
<div class="jf-alumni-name">Izhan Fakhruzi</div>
<div class="jf-alumni-batch" data-i18n="grp_jellyfish_if_akt1">Informatika, Angkatan 2005</div>
</div>
</div>
<div class="jf-alumni-photo-placeholder">
<div class="jf-alumni-photo-inner">
<img src="images\Izhan.jpg" alt="">
</div>
</div>
<div class="jf-alumni-roles">
<span data-i18n="grp_jellyfish_kerja1">Dosen UM Pontianak</span>
<span>PhD Candidate Granada</span>
</div>
<p data-i18n="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.</p>
<div class="jf-alumni-divider"></div>
</div>
<div class="jf-alumni-card">
<div class="jf-alumni-header">
<div class="jf-alumni-avatar" style="background:linear-gradient(135deg,#7c3aed,#a855f7);">CD
</div>
<div>
<div class="jf-alumni-name">Charles Darwis Hariman</div>
<div class="jf-alumni-batch" data-i18n="grp_jellyfish_if_akt2">Informatika, Angkatan 2018</div>
</div>
</div>
<div class="jf-alumni-photo-placeholder">
<div class="jf-alumni-photo-inner">
<img src="images\Charles.jpg" alt="">
</div>
</div>
<div class="jf-alumni-roles">
<span>Software Developer</span>
<span>FinTech Travixy Frankfurt</span>
</div>
<p data-i18n="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.</p>
<div class="jf-alumni-divider"></div>
</div>
<div class="jf-alumni-card">
<div class="jf-alumni-header">
<div class="jf-alumni-avatar" style="background:linear-gradient(135deg,#059669,#10b981);">DP
</div>
<div>
<div class="jf-alumni-name">Dona Pramana Putra</div>
<div class="jf-alumni-batch" data-i18n="grp_jellyfish_if_akt3">Informatika, Angkatan 2006</div>
</div>
</div>
<div class="jf-alumni-photo-placeholder">
<div class="jf-alumni-photo-inner">
<img src="images\Dona.jpg" alt="">
</div>
</div>
<div class="jf-alumni-roles">
<span>Product Team</span>
<span>AI Rudder</span>
</div>
<p data-i18n="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.</p>
<div class="jf-alumni-divider"></div>
</div>
</div>
</div>
</section>
<!-- ===================== H. INFRASTRUKTUR ===================== -->
<section class="jf-section jf-section-dark">
<div class="container content">
<div class="jf-section-header jf-center">
<span class="jf-badge" data-i18n="grp_jellyfish_infra_badge">Infrastruktur</span>
<h2 class="jf-section-title" style="color:white;" data-i18n="grp_jellyfish_infra_title">Infrastruktur dan Fasilitas</h2>
<p class="jf-section-sub" style="color:white;">
<center data-i18n="grp_jellyfish_infra_subtitle" style="color:rgba(255, 255, 255, 0.7);">Fasilitas lengkap dan modern untuk mendukung proses belajar, penelitian, dan inovasi mahasiswa.</center>
</p>
<div class="jf-infra-line"></div>
</div>
<div class="jf-infra-grid">
<div class="jf-infra-card" style="background:linear-gradient(135deg,#0ea5e9,#0284c7);">
<div class="jf-infra-icon">🖥️</div>
<div class="jf-infra-name">8 Laboratorium</div>
<p data-i18n="grp_jellyfish_labs">Lab Pemrograman, Jaringan, Multimedia, IoT, AI, Basis Data, Mobile Dev, dan Keamanan Siber —
ber-AC, High-Speed PC.</p>
<div class="jf-infra-stat" data-i18n="grp_jellyfish_labs2"><span>8</span> Lab Aktif &amp; Berteknologi</div>
</div>
<div class="jf-infra-card" style="background:linear-gradient(135deg,#7c3aed,#6d28d9);">
<div class="jf-infra-icon">☁️</div>
<div class="jf-infra-name">Data Center</div>
<p data-i18n="grp_jellyfish_data_center">Server on-premise dengan infrastruktur cloud lokal berkapasitas tinggi untuk penelitian dan
deployment aplikasi.</p>
<div class="jf-infra-stat"><span>99.9%</span> Uptime · Redundant</div>
</div>
<div class="jf-infra-card" style="background:linear-gradient(135deg,#059669,#047857);">
<div class="jf-infra-icon">🚀</div>
<div class="jf-infra-name">IFUNTANHUB</div>
<p data-i18n="grp_jellyfish_ifuntanhub">Platform PaaS eksklusif mahasiswa untuk deploy aplikasi, hosting proyek, akses GPU computing,
dan kolaborasi tim.</p>
<div class="jf-infra-stat" data-i18n="grp_jellyfish_free"><span>Gratis</span> · GPU Access</div>
</div>
<div class="jf-infra-card" style="background:linear-gradient(135deg,#d97706,#b45309);">
<div class="jf-infra-icon">📚</div>
<div class="jf-infra-name">E-Resources</div>
<p data-i18n="grp_jellyfish_resources">Akses jurnal internasional Scopus, IEEE Xplore, ACM Digital Library, Springer, dan database
ilmiah lainnya.</p>
<div class="jf-infra-stat"><span>IEEE · Scopus · ACM</span></div>
</div>
</div>
</div>
</section>
<!-- ===================== I. MITRA ===================== -->
<section class="jf-section jf-section-white">
<div class="container content">
<div class="jf-section-header jf-center">
<span class="jf-badge" data-i18n="grp_jellyfish_partner_badge">Kerjasama</span>
<h2 class="jf-section-title" data-i18n="grp_jellyfish_partner_title">Mitra Strategis Informatika UNTAN</h2>
<p class="jf-section-sub">
<center data-i18n="grp_jellyfish_partner_subtitle">Dibangun di atas kepercayaan dan kolaborasi dengan perusahaan teknologi, instansi
pemerintah, dan lembaga pendidikan terkemuka.</center>
</p>
</div>
<div class="jf-mitra-group-title" data-i18n="grp_jellyfish_industri">Industri &amp; Swasta</div>
<div class="jf-mitra-grid" style="grid-template-columns:repeat(3,1fr);">
<div class="jf-mitra-card">
<div class="jf-mitra-logo"></div>
<div class="jf-mitra-name">PT. PLN Nusantara</div>
<div class="jf-mitra-desc" data-i18n="grp_jellyfish_mitra_pln">Pembuatan aplikasi monitoring BBM untuk PLN Nusantara Power UP Kapuas (2025).</div>
</div>
</div>
<div class="jf-mitra-group-title" data-i18n="grp_jellyfish_pemerintah">Instansi Pemerintah</div>
<div class="jf-mitra-grid jf-mitra-grid-gov" style="grid-template-columns:repeat(3,1fr)">
<div class="jf-mitra-card">
<div class="jf-mitra-name">Diskominfo Kalbar</div>
<div class="jf-mitra-desc" data-i18n="grp_jellyfish_mitra_diskominfo_kalbar">Kerja sama dengan Diskominfo Kalbar untuk kerja praktek dan magang 2024-sekarang.</div>
</div>
<div class="jf-mitra-card">
<div class="jf-mitra-name">POLDA Kalbar</div>
<div class="jf-mitra-desc" data-i18n="grp_jellyfish_mitra_polda">Penyelenggaraan CAT (Computer Assesment Test).</div>
</div>
<div class="jf-mitra-card">
<div class="jf-mitra-name">Bappeda Sambas</div>
<div class="jf-mitra-desc" data-i18n="grp_jellyfish_mitra_bappeda_sambas">Pemutakhiran Geoportal Kabupaten Sambas pada infrastruktur Sambas GO.ID (2024).</div>
</div>
<div class="jf-mitra-card">
<div class="jf-mitra-name">Bappeda Sintang</div>
<div class="jf-mitra-desc" data-i18n="grp_jellyfish_mitra_bappeda_sintang">Penyusunan Rencana Induk Sistem Informasi Geospasial kabupaten Sintang 2019, dan Kajian Analisa Dampak Lingkungan TPA dan IPLT.</div>
</div>
<div class="jf-mitra-card">
<div class="jf-mitra-name">Bappeda Bengkayang</div>
<div class="jf-mitra-desc" data-i18n="grp_jellyfish_mitra_bappeda_bengkayang">Pembangunan Sistem Informasi Perencanaan Pembangunan Berbasis Data Geospasial dan Pendampingan Penyelenggaraan Informasi Geospasial Bagi Pemerintahan Daerah/Simpul Jaringan 2022.</div>
</div>
<div class="jf-mitra-card">
<div class="jf-mitra-name">BKPSDM</div>
<div class="jf-mitra-desc" data-i18n="grp_jellyfish_mitra_bkpsdm">Pengembangan dan pemeliharaan aplikasi E-Kinerja untuk manajemen kinerja aparatur sipil negara (2024).</div>
</div>
<div class="jf-mitra-card">
<div class="jf-mitra-name">BPBD Sintang</div>
<div class="jf-mitra-desc" data-i18n="grp_jellyfish_mitra_bpbd_sintang">Pembuatan peta potensi daerah rawan bencana banjir Kabupaten Sintang sebagai referensi mitigasi (2022).</div>
</div>
<div class="jf-mitra-card">
<div class="jf-mitra-name">BKD Kota Pontianak</div>
<div class="jf-mitra-desc" data-i18n="grp_jellyfish_mitra_bkd_pontianak">Pengadaan Peta GIS PBB-P2 Tahap II untuk pengelolaan data pajak bumi dan bangunan Kota Pontianak (2022).</div>
</div>
<div class="jf-mitra-card">
<div class="jf-mitra-name">Disperkim Singkawang</div>
<div class="jf-mitra-desc" data-i18n="grp_jellyfish_mitra_disperkim_singkawang">Review baseline dan pemetaan kawasan permukiman kumuh Kota Singkawang (2020).</div>
</div>
<div class="jf-mitra-card">
<div class="jf-mitra-name">Diskominfo Singkawang</div>
<div class="jf-mitra-desc" data-i18n="grp_jellyfish_mitra_diskominfo_singkawang">Kerja sama dengan Diskominfo Singkawang untuk kerja praktek dan magang 2024-sekarang.</div>
</div>
<div class="jf-mitra-card">
<div class="jf-mitra-name">Diskominfo Pontianak</div>
<div class="jf-mitra-desc" data-i18n="grp_jellyfish_mitra_diskominfo_pontianak">Kerja sama dengan Diskominfo Pontianak untuk kerja praktek dan magang 2024-sekarang.</div>
</div>
<div class="jf-mitra-card">
<div class="jf-mitra-name">Badan Penelitian dan Pengembangan</div>
<div class="jf-mitra-desc" data-i18n="grp_jellyfish_mitra_balitbang">Pengembangan Dan Pemanfaatan Informasi Geospasial untuk Kelitbangan di Provinsi Kalbar.</div>
</div>
</div>
<div class="jf-mitra-group-title" data-i18n="grp_jellyfish_perguruan_tinggi">Perguruan Tinggi &amp; Lembaga Riset</div>
<div class="jf-mitra-grid jf-mitra-grid-gov" style="grid-template-columns:repeat(3,1fr);">
<div class="jf-mitra-card">
<div class="jf-mitra-name">UNIMAS Malaysia</div>
<div class="jf-mitra-desc" data-i18n="grp_jellyfish_mitra_unimas">Kerja sama pelaksanaan Study Excursion 2019-2025.</div>
</div>
<div class="jf-mitra-card">
<div class="jf-mitra-name">UUM Malaysia</div>
<div class="jf-mitra-desc" data-i18n="grp_jellyfish_mitra_uum">Kerja sama menyelenggarakan konferensi internasional (ICODSE) - 2019.</div>
</div>
<div class="jf-mitra-card">
<div class="jf-mitra-name">UCS Sarawak</div>
<div class="jf-mitra-desc" data-i18n="grp_jellyfish_mitra_ucs">Kerja sama menyelenggarakan konferensi internasional (ICODSE) - 2019.</div>
</div>
<div class="jf-mitra-card">
<div class="jf-mitra-name">UTEM Malaysia</div>
<div class="jf-mitra-desc" data-i18n="grp_jellyfish_mitra_utem">Kerjasama penyelenggaraan konferensi nasional.</div>
</div>
<div class="jf-mitra-card">
<div class="jf-mitra-name">IPB Indonesia</div>
<div class="jf-mitra-desc" data-i18n="grp_jellyfish_mitra_ipb">Kerja sama menyelenggarakan konferensi internasional (ICODSE) - 2019.</div>
</div>
<div class="jf-mitra-card">
<div class="jf-mitra-name">Politeknik Negeri Pontianak</div>
<div class="jf-mitra-desc" data-i18n="grp_jellyfish_mitra_polnep">Kerja sama dengan Politeknik Negeri Pontianak untuk kerja praktek dan magang 2024-sekarang.</div>
</div>
<div class="jf-mitra-card">
<div class="jf-mitra-name">Poltekkes Pontianak</div>
<div class="jf-mitra-desc" data-i18n="grp_jellyfish_mitra_poltekkes">Rancangan kerja sama program Informatika dengan Politeknik Kesehatan Kemenkes Pontianak (2025).</div>
</div>
</div>
</div>
</section>
<!-- ===================== K. CTA / KONTAK ===================== -->
<section class="jf-section jf-cta-section" id="kontak">
<div class="container jf-cta-inner content">
<div class="jf-cta-left">
<span class="jf-badge" data-i18n="grp_jellyfish_cta_badge">Bergabung Bersama Kami</span>
<h2 class="jf-cta-title" data-i18n="grp_jellyfish_cta_title">Mulai Perjalanan Digital Anda <span class="jf-cta-highlight" data-i18n="grp_jellyfish_cta_highlight">Bersama Kami</span></h2>
<p data-i18n="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.</p>
<div class="jf-cta-btns">
<a href="#" class="jf-btn-yellow" data-i18n="grp_jellyfish_btn_registration">Daftar Mahasiswa Baru</a>
<a href="https://wa.me/6285171655161" class="jf-btn-green" data-i18n="grp_jellyfish_btn_whatsapp">💬 Chat WhatsApp</a>
</div>
</div>
<div class="jf-cta-right">
<div class="jf-kontak-card">
<div class="jf-kontak-title" data-i18n="grp_jellyfish_info_kontak">Informasi Kontak</div>
<div class="jf-kontak-list">
<div class="jf-kontak-item">
<div class="jf-kontak-icon">📍</div>
<div class="jf-kontak-info">
<div class="jf-kontak-label" data-i18n="grp_jellyfish_alamat">Alamat</div>
<div class="jf-kontak-val">Jl. Prof. Dr. H. Hadari Nawawi, Bansir Laut, Kec.
Pontianak Tenggara, Kota Pontianak, Kalimantan Barat 78124</div>
</div>
</div>
<div class="jf-kontak-item">
<div class="jf-kontak-icon">✉️</div>
<div class="jf-kontak-info">
<div class="jf-kontak-label">Email</div>
<div class="jf-kontak-val">info@informatika.untan.ac.id</div>
</div>
</div>
<div class="jf-kontak-item">
<div class="jf-kontak-icon">💬</div>
<div class="jf-kontak-info">
<div class="jf-kontak-label">WhatsApp</div>
<div class="jf-kontak-val">+62 851-7165-5161<br><small data-i18n="grp_jellyfish_tanggal">Senin Jumat, 08.00 16.00
WIB</small></div>
</div>
</div>
<div class="jf-kontak-item">
<div class="jf-kontak-icon">📞</div>
<div class="jf-kontak-info">
<div class="jf-kontak-label" data-i18n="grp_jellyfish_telepon">Telepon / Fax</div>
<div class="jf-kontak-val">+62(561) 739637</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</main>
<!-- ================= END: BAGIAN YANG DIEDIT KELOMPOK ================= --> <!-- ================= END: BAGIAN YANG DIEDIT KELOMPOK ================= -->
</div> </div>
</div> </div>
@@ -1332,7 +2067,7 @@
} }
})(); })();
</script> </script>
<script src="script.js"></script>
<script src="../../assets/global/localization.js"></script> <script src="../../assets/global/localization.js"></script>
</body> </body>
</html> </html>
+92
View File
@@ -0,0 +1,92 @@
// ===== SCROLL REVEAL — jf-* cards di dalam #assignment-body =====
function initScrollReveal() {
const body = document.getElementById('assignment-body');
if (!body) return;
const targets = body.querySelectorAll(
'.jf-stat-card, .jf-stat-row-card, .jf-prestasi-card, ' +
'.jf-sc-card, .jf-karir-card, .jf-alumni-card, ' +
'.jf-infra-card, .jf-mitra-card, .jf-tl-item, .jf-kontak-item'
);
targets.forEach(el => {
el.style.opacity = '0';
el.style.transform = 'translateY(24px)';
el.style.transition = 'opacity 0.55s ease, transform 0.55s ease';
});
const obs = new IntersectionObserver((entries) => {
entries.forEach((entry, i) => {
if (!entry.isIntersecting) return;
setTimeout(() => {
entry.target.style.opacity = '1';
entry.target.style.transform = 'translateY(0)';
}, i * 70);
obs.unobserve(entry.target);
});
}, { threshold: 0.08 });
targets.forEach(el => obs.observe(el));
}
// ===== PRESTASI FILTER — tab filter di dalam #assignment-body =====
function initPrestasiFilter() {
const body = document.getElementById('assignment-body');
if (!body) return;
const tabs = body.querySelectorAll('.jf-tab-btn');
const cards = body.querySelectorAll('#prestasi-grid .jf-prestasi-card');
if (!tabs.length) return;
tabs.forEach(tab => {
tab.addEventListener('click', () => {
tabs.forEach(t => t.classList.remove('jf-tab-active'));
tab.classList.add('jf-tab-active');
const filter = tab.dataset.filter;
cards.forEach(card => {
const show = filter === 'all' || card.dataset.cat === filter;
card.style.display = show ? '' : 'none';
if (show) card.style.animation = 'jfFadeIn 0.35s ease forwards';
});
});
});
}
// ===== ANIMATED COUNTER — .jf-stat-num di dalam #assignment-body =====
function initCounters() {
const body = document.getElementById('assignment-body');
if (!body) return;
const obs = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (!entry.isIntersecting) return;
const el = entry.target;
const raw = el.textContent.trim();
const num = parseFloat(raw.replace(/[^0-9.]/g, ''));
if (isNaN(num) || num === 0) return;
const prefix = raw.match(/^[^0-9]*/)[0];
const suffix = raw.match(/[^0-9.]*$/)[0];
let start = 0;
const step = 16;
const increment = num / (1400 / step);
const timer = setInterval(() => {
start = Math.min(start + increment, num);
const display = Number.isInteger(num)
? Math.floor(start).toLocaleString('id-ID')
: start.toFixed(1);
el.textContent = prefix + display + suffix;
if (start >= num) clearInterval(timer);
}, step);
obs.unobserve(el);
});
}, { threshold: 0.5 });
body.querySelectorAll('.jf-stat-num').forEach(el => obs.observe(el));
}
// ===== INIT =====
initScrollReveal();
initPrestasiFilter();
initCounters();
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 934 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 992 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 669 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 924 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 788 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 889 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 853 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 737 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

+1267 -39
View File
File diff suppressed because it is too large Load Diff
+48 -2
View File
@@ -1,4 +1,4 @@
:root { :root {
--brand-navy: #003150; --brand-navy: #003150;
--brand-yellow: #feb401; --brand-yellow: #feb401;
--text-main: #1e293b; --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; } .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; } .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; } .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 { 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-logo { height: 28px; width: auto; }
.footer-ornament { width: 100%; display: block; opacity: .9; } .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;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 988 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 803 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

+1780 -70
View File
File diff suppressed because it is too large Load Diff
+2036 -38
View File
File diff suppressed because it is too large Load Diff
+36 -62
View File
@@ -4,7 +4,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sorotan dan Prestasi Karya Mahasiswa | Informatika UNTAN</title> <title>Riset & Kelompok Bidang Keahlian | Informatika UNTAN</title>
<!-- Tailwind CSS via CDN --> <!-- Tailwind CSS via CDN -->
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
<!-- Font Awesome untuk Icon --> <!-- Font Awesome untuk Icon -->
@@ -76,12 +76,7 @@
window.APP_CONFIG = window.APP_CONFIG || { window.APP_CONFIG = window.APP_CONFIG || {
debugLogs: false, debugLogs: false,
dashboardRuntimeFetch: 'always', dashboardRuntimeFetch: 'always',
dashboardTargetUrl: 'https://dashboard.informatika.untan.ac.id/index.php?penelitian', dashboardApiUrl: 'https://proxykbk.netlify.app/.netlify/functions/dashboard',
dashboardProxyTemplates: [
'https://api.allorigins.win/get?url={{url}}&ts={{ts}}',
'https://api.codetabs.com/v1/proxy?quest={{url}}',
'https://thingproxy.freeboard.io/fetch/{{rawUrl}}'
]
}; };
</script> </script>
<!-- RODA_DASHBOARD_DATA --> <!-- RODA_DASHBOARD_DATA -->
@@ -1633,6 +1628,22 @@
updateStatPub(newData.length); updateStatPub(newData.length);
} }
async function fetchProxyRows(proxyUrl) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 15000);
try {
const response = await fetch(proxyUrl, { signal: controller.signal });
if (!response.ok) throw new Error(`HTTP_ERROR_${response.status}`);
const responseData = await response.text();
const html = extractDashboardHtml(responseData);
return parseDashboardRows(html);
} finally {
clearTimeout(timeoutId);
}
}
async function fetchDashboardData() { async function fetchDashboardData() {
const grid = document.getElementById('riset-grid'); const grid = document.getElementById('riset-grid');
if (grid) { if (grid) {
@@ -1645,70 +1656,33 @@
} }
let lastError = null; let lastError = null;
if (isDashboardRuntimeFetchAllowed()) {
const dashboardApiUrl = window.APP_CONFIG?.dashboardApiUrl?.trim();
if (dashboardApiUrl) {
try {
const rows = await fetchProxyRows(dashboardApiUrl);
applyDashboardRows(rows);
return;
} catch (error) {
lastError = error;
}
}
}
// Fallback ke data yang di-embed saat build jika runtime fetch gagal atau dinonaktifkan
const embeddedHtml = readEmbeddedDashboardHtml(); const embeddedHtml = readEmbeddedDashboardHtml();
if (embeddedHtml) { if (embeddedHtml) {
try { try {
applyDashboardRows(parseDashboardRows(embeddedHtml)); applyDashboardRows(parseDashboardRows(embeddedHtml));
return; return;
} catch (error) { } catch (error) {
lastError = error; lastError = lastError || error;
} }
} }
if (!isDashboardRuntimeFetchAllowed()) { renderRisetFeedback('Pemuatan Data Gagal', getDashboardErrorMessage(lastError || new Error('DASHBOARD_RUNTIME_FETCH_DISABLED')), 'Coba Lagi', 'fetchDashboardData()');
renderRisetFeedback('Pemuatan Data Gagal', getDashboardErrorMessage(lastError || new Error('DASHBOARD_RUNTIME_FETCH_DISABLED')), 'Coba Lagi', 'fetchDashboardData()');
return;
}
const targetUrl = window.APP_CONFIG?.dashboardTargetUrl?.trim();
const proxyTemplates = Array.isArray(window.APP_CONFIG?.dashboardProxyTemplates)
? window.APP_CONFIG.dashboardProxyTemplates.filter(Boolean)
: [];
// Endpoint server.js hanya ada saat development lokal.
const proxyServers = shouldUseLocalDashboardProxy() ? [() => '/api/dashboard'] : [];
// Deployment static memakai proxy publik karena tidak menjalankan server.js.
if (targetUrl) {
proxyTemplates.forEach((template) => {
proxyServers.push((url) => template
.replaceAll('{{url}}', encodeURIComponent(url))
.replaceAll('{{rawUrl}}', url)
.replaceAll('{{ts}}', String(Date.now())));
});
}
if (!proxyServers.length) {
renderRisetFeedback('Pemuatan Data Gagal', getDashboardErrorMessage(new Error('DASHBOARD_PROXY_NOT_CONFIGURED')), 'Coba Lagi', 'fetchDashboardData()');
return;
}
for (const getProxyUrl of proxyServers) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 15000);
try {
const proxyUrl = getProxyUrl(targetUrl);
const response = await fetch(proxyUrl, { signal: controller.signal });
clearTimeout(timeoutId);
if (!response.ok) throw new Error(`HTTP_ERROR_${response.status}`);
const responseData = await response.text();
const html = extractDashboardHtml(responseData);
const rows = parseDashboardRows(html);
applyDashboardRows(rows);
return; // Berhasil! Keluar dari loop
} catch (error) {
clearTimeout(timeoutId);
lastError = error;
// Lanjut ke proxy berikutnya...
}
}
// Jika semua proxy gagal
renderRisetFeedback('Pemuatan Data Gagal', getDashboardErrorMessage(lastError), 'Coba Lagi', 'fetchDashboardData()');
} }
-13
View File
@@ -1,13 +0,0 @@
{
"name": "roda",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "roda",
"version": "1.0.0",
"license": "ISC"
}
}
}
+2 -13
View File
@@ -1,14 +1,3 @@
{ {
"name": "roda", "type": "commonjs"
"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"
}
+38 -85
View File
@@ -6,64 +6,21 @@ const { URL } = require('url');
const ROOT_DIR = __dirname; const ROOT_DIR = __dirname;
const PARENT_DIR = path.join(__dirname, '..', '..'); const PARENT_DIR = path.join(__dirname, '..', '..');
const INDEX_PATH = path.join(ROOT_DIR, 'index.html'); const INDEX_PATH = path.join(ROOT_DIR, 'index.html');
const ENV_PATH = path.join(ROOT_DIR, '.env'); const HOST = process.env.HOST || '127.0.0.1';
const HOST = '127.0.0.1';
const PORT = Number(process.env.PORT || 3000); const PORT = Number(process.env.PORT || 3000);
const DASHBOARD_TARGET_URL = 'https://dashboard.informatika.untan.ac.id/index.php?penelitian';
const RODA_PATH_PREFIX = '/groups/RODA';
function logServerError(scope, error, extra = {}) { function logServerError(scope, error, extra = {}) {
console.error(`[${scope}]`, error && error.stack ? error.stack : 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() { 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 { return {
dashboardTargetUrl: env.DASHBOARD_TARGET_URL || '', debugLogs: false,
dashboardProxyTemplates dashboardRuntimeFetch: 'always',
dashboardApiUrl: 'api/dashboard',
dashboardTargetUrl: DASHBOARD_TARGET_URL,
}; };
} }
@@ -71,10 +28,6 @@ const DASHBOARD_CACHE_TTL_MS = 5 * 60 * 1000;
let dashboardCache = { html: null, timestamp: 0 }; let dashboardCache = { html: null, timestamp: 0 };
async function fetchDashboardHtml() { async function fetchDashboardHtml() {
const targetUrl = getAppConfig().dashboardTargetUrl;
if (!targetUrl) {
throw new Error('DASHBOARD_TARGET_URL kosong');
}
const now = Date.now(); const now = Date.now();
if (dashboardCache.html && (now - dashboardCache.timestamp) < DASHBOARD_CACHE_TTL_MS) { if (dashboardCache.html && (now - dashboardCache.timestamp) < DASHBOARD_CACHE_TTL_MS) {
@@ -84,7 +37,7 @@ async function fetchDashboardHtml() {
const controller = new AbortController(); const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 12000); const timeoutId = setTimeout(() => controller.abort(), 12000);
try { try {
const response = await fetch(targetUrl, { const response = await fetch(DASHBOARD_TARGET_URL, {
signal: controller.signal, signal: controller.signal,
headers: { headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36' 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36'
@@ -106,39 +59,14 @@ async function fetchDashboardHtml() {
} }
} }
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) { function injectConfig(html) {
const configScript = `<script>window.APP_CONFIG = ${JSON.stringify(getAppConfig())};</script>`; const configScript = `<script>window.APP_CONFIG = ${JSON.stringify(getAppConfig())};</script>`;
const defaultConfigScript = '<script>\n window.APP_CONFIG = window.APP_CONFIG || {\n dashboardTargetUrl: \'\',\n dashboardProxyTemplates: []\n };\n </script>';
if (html.includes(defaultConfigScript)) {
return html.replace(defaultConfigScript, configScript);
}
return html.replace('</head>', ` ${configScript}\n</head>`); return html.replace('</head>', ` ${configScript}\n</head>`);
} }
function sendHtml(res) { function sendHtml(res) {
try { try {
const html = fs.readFileSync(INDEX_PATH, 'utf8'); 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); const withConfig = injectConfig(html);
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(withConfig); res.end(withConfig);
@@ -195,28 +123,53 @@ function sendNotFound(res) {
res.end('Not found'); res.end('Not found');
} }
function getRodaStaticPathname(pathname) {
if (pathname === RODA_PATH_PREFIX || pathname === `${RODA_PATH_PREFIX}/` || pathname === `${RODA_PATH_PREFIX}/index.html`) {
return '/index.html';
}
if (pathname.startsWith(`${RODA_PATH_PREFIX}/`)) {
return pathname.slice(RODA_PATH_PREFIX.length);
}
return pathname;
}
const server = http.createServer((req, res) => { const server = http.createServer((req, res) => {
try { try {
const requestUrl = new URL(req.url, `http://${req.headers.host}`); const requestUrl = new URL(req.url, `http://${req.headers.host}`);
const pathname = requestUrl.pathname; const pathname = requestUrl.pathname;
if (pathname === '/' || pathname === '/index.html') { if (pathname === '/' || pathname === '/index.html' || pathname === RODA_PATH_PREFIX || pathname === `${RODA_PATH_PREFIX}/` || pathname === `${RODA_PATH_PREFIX}/index.html`) {
sendHtml(res); sendHtml(res);
return; return;
} }
if (pathname === '/api/dashboard') { if (pathname === '/api/dashboard' || pathname === `${RODA_PATH_PREFIX}/api/dashboard`) {
if (req.method === 'OPTIONS') {
res.writeHead(204, {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET',
});
res.end();
return;
}
fetchDashboardHtml() fetchDashboardHtml()
.then(({ html, cached }) => { .then(({ html, cached }) => {
res.writeHead(200, { res.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8', 'Content-Type': 'text/html; charset=utf-8',
'X-Cache': cached 'Access-Control-Allow-Origin': '*',
'X-Cache': cached,
}); });
res.end(html); res.end(html);
}) })
.catch((error) => { .catch((error) => {
logServerError('api-dashboard', error); logServerError('api-dashboard', error);
res.writeHead(502, { 'Content-Type': 'text/plain; charset=utf-8' }); res.writeHead(502, {
'Content-Type': 'text/plain; charset=utf-8',
'Access-Control-Allow-Origin': '*',
});
res.end('DASHBOARD_FETCH_FAILED'); res.end('DASHBOARD_FETCH_FAILED');
}); });
return; return;
@@ -237,7 +190,7 @@ const server = http.createServer((req, res) => {
} }
// Serve static files from ROOT_DIR // Serve static files from ROOT_DIR
const filepath = path.join(ROOT_DIR, pathname); const filepath = path.join(ROOT_DIR, getRodaStaticPathname(pathname));
const normalizedPath = path.normalize(filepath); const normalizedPath = path.normalize(filepath);
// Security check: ensure the requested file is within ROOT_DIR // Security check: ensure the requested file is within ROOT_DIR
+3 -1
View File
@@ -5,7 +5,9 @@
"type": "module", "type": "module",
"description": "Student-safe static page assignment kit for Informatika UNTAN", "description": "Student-safe static page assignment kit for Informatika UNTAN",
"scripts": { "scripts": {
"dev": "vite", "dev": "node scripts/dev.js",
"dev:vite": "vite",
"dev:roda": "node groups/RODA/server.js",
"build": "vite build", "build": "vite build",
"test:tapops": "node --test groups/Tapops/tests/*.test.mjs", "test:tapops": "node --test groups/Tapops/tests/*.test.mjs",
"preview": "vite preview" "preview": "vite preview"
+94
View File
@@ -0,0 +1,94 @@
import { spawn } from 'node:child_process';
export function createDevProcessSpecs({
nodeCommand = process.execPath,
env = process.env,
} = {}) {
return {
roda: {
command: nodeCommand,
args: ['groups/RODA/server.js'],
options: {
env: {
...env,
HOST: env.RODA_HOST || '127.0.0.1',
PORT: env.RODA_PORT || '3000',
},
stdio: ['ignore', 'ignore', 'pipe'],
},
},
vite: {
command: nodeCommand,
args: ['node_modules/vite/bin/vite.js'],
options: {
env: { ...env },
stdio: 'inherit',
},
},
};
}
export function startDevServers({
spawnProcess = spawn,
specs = createDevProcessSpecs(),
} = {}) {
const roda = spawnProcess(specs.roda.command, specs.roda.args, specs.roda.options);
const vite = spawnProcess(specs.vite.command, specs.vite.args, specs.vite.options);
let shuttingDown = false;
let rodaErrorOutput = '';
roda.stderr?.on('data', (chunk) => {
const message = String(chunk);
rodaErrorOutput += message;
if (!message.includes('EADDRINUSE')) {
process.stderr.write(`[roda] ${message}`);
}
});
function stopChildren() {
shuttingDown = true;
if (!roda.killed) {
roda.kill();
}
if (!vite.killed) {
vite.kill();
}
}
vite.on('exit', (code, signal) => {
shuttingDown = true;
if (!roda.killed) {
roda.kill();
}
if (signal) {
process.exitCode = 1;
return;
}
process.exitCode = code ?? 0;
});
roda.on('exit', (code, signal) => {
if (shuttingDown) {
return;
}
if (rodaErrorOutput.includes('EADDRINUSE')) {
process.stderr.write('[roda] Port 3000 is already in use; continuing with the existing RODA server.\n');
return;
}
const reason = signal ? `signal ${signal}` : `exit code ${code}`;
process.stderr.write(`[roda] server stopped unexpectedly (${reason})\n`);
if (!vite.killed) {
vite.kill();
}
process.exitCode = code || 1;
});
process.once('SIGINT', stopChildren);
process.once('SIGTERM', stopChildren);
return { roda, vite };
}
+3
View File
@@ -0,0 +1,3 @@
import { startDevServers } from './dev-runner.js';
startDevServers();
+34 -11
View File
@@ -6,6 +6,13 @@ import { defineConfig } from 'vite';
const root = path.dirname(fileURLToPath(import.meta.url)); const root = path.dirname(fileURLToPath(import.meta.url));
const excludedDirs = new Set(['.git', 'dist', 'node_modules']); const excludedDirs = new Set(['.git', 'dist', 'node_modules']);
const rodaDashboardUrl = 'https://dashboard.informatika.untan.ac.id/index.php?penelitian'; const rodaDashboardUrl = 'https://dashboard.informatika.untan.ac.id/index.php?penelitian';
const rodaDashboardCacheTtlMs = 5 * 60 * 1000;
const dashboardRequestHeaders = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36',
};
const rodaHost = process.env.RODA_HOST || '127.0.0.1';
const rodaPort = process.env.RODA_PORT || '3000';
const rodaTarget = `http://${rodaHost}:${rodaPort}`;
let rodaDashboardHtmlCache; let rodaDashboardHtmlCache;
@@ -50,33 +57,37 @@ function copyStaticAssets() {
}; };
} }
async function fetchRodaDashboardHtml() { async function requestRodaDashboardHtml() {
if (rodaDashboardHtmlCache !== undefined) {
return rodaDashboardHtmlCache;
}
const controller = new AbortController(); const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 12000); const timeoutId = setTimeout(() => controller.abort(), 12000);
try { try {
const response = await fetch(rodaDashboardUrl, { const response = await fetch(rodaDashboardUrl, {
signal: controller.signal, signal: controller.signal,
headers: { headers: dashboardRequestHeaders,
'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) { if (!response.ok) {
throw new Error(`HTTP ${response.status}`); throw new Error(`HTTP ${response.status}`);
} }
rodaDashboardHtmlCache = await response.text(); return response.text();
} finally {
clearTimeout(timeoutId);
}
}
async function fetchRodaDashboardHtml() {
if (rodaDashboardHtmlCache !== undefined) {
return rodaDashboardHtmlCache;
}
try {
rodaDashboardHtmlCache = await requestRodaDashboardHtml();
} catch (error) { } catch (error) {
const message = error instanceof Error ? error.message : String(error); const message = error instanceof Error ? error.message : String(error);
console.warn(`[roda-dashboard] Build-time dashboard fetch skipped: ${message}`); console.warn(`[roda-dashboard] Build-time dashboard fetch skipped: ${message}`);
rodaDashboardHtmlCache = ''; rodaDashboardHtmlCache = '';
} finally {
clearTimeout(timeoutId);
} }
return rodaDashboardHtmlCache; return rodaDashboardHtmlCache;
@@ -117,6 +128,18 @@ function embedRodaDashboardData() {
export default defineConfig({ export default defineConfig({
plugins: [embedRodaDashboardData(), copyStaticAssets()], plugins: [embedRodaDashboardData(), copyStaticAssets()],
server: {
proxy: {
'/groups/RODA/api/dashboard': {
target: rodaTarget,
changeOrigin: true,
},
'/api/dashboard': {
target: rodaTarget,
changeOrigin: true,
},
},
},
build: { build: {
rollupOptions: { rollupOptions: {
input: collectHtmlInputs(root), input: collectHtmlInputs(root),