Compare commits
160 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 95197fab1f | |||
| 3c28313d43 | |||
| f6999381e4 | |||
| 8efef62933 | |||
| d98ddc201a | |||
| 1690dcae3e | |||
| 994fec5456 | |||
| da38e3eb11 | |||
| 9b9241e923 | |||
| df7041340a | |||
| b19c014ca6 | |||
| 0f65b47757 | |||
| 48e5d9a4a9 | |||
| 350f2be836 | |||
| 78eebd2099 | |||
| d346b7e415 | |||
| 080683c34e | |||
| 28cbd24338 | |||
| 1acefb4516 | |||
| 1e1f2a33e5 | |||
| 9af3a1a731 | |||
| 7028fc4f30 | |||
| 067e19a952 | |||
| 2166057a8b | |||
| 4fb6fe8e84 | |||
| f343a1c7c6 | |||
| 051a9a5669 | |||
| 68e7c3df2a | |||
| d3d1fce164 | |||
| 91b1ad28e4 | |||
| 103fdabc48 | |||
| 9d408b1a06 | |||
| 3b85dff0b9 | |||
| 35fe2d814a | |||
| e7a01dd4a1 | |||
| cd792dd5f4 | |||
| 9f22d4f11b | |||
| a8e5a93929 | |||
| 761095f7d3 | |||
| d3ed1e54af | |||
| 8e42878c0a | |||
| ed3f2ad9e8 | |||
| baf8843d55 | |||
| a09ddac2c6 | |||
| bc173ff4ec | |||
| e2e36f1b92 | |||
| 6080ed2252 | |||
| 22f957e0f6 | |||
| c9634c619f | |||
| 4e38e7524b | |||
| 9c870cf28c | |||
| b844ca8b23 | |||
| f410b390e8 | |||
| 471ed6cd18 | |||
| 874c185dc9 | |||
| fe5c645cbd | |||
| ec0288fd8a | |||
| a8fca707b9 | |||
| 4705710d13 | |||
| 00fa66b699 | |||
| 7cf671d826 | |||
| f42afb5b81 | |||
| cff21cbf92 | |||
| 4ed5c20d80 | |||
| 859e538761 | |||
| 5080517d4d | |||
| bd75009c8b | |||
| f45f14b60b | |||
| 2fb6e7642d | |||
| d34cc1c6e5 | |||
| 68cd20864e | |||
| b3a34dd608 | |||
| 99de2a3167 | |||
| b65a581ed4 | |||
| 986477ab58 | |||
| 563af0ec56 | |||
| f8500db1dd | |||
| 4375c846fa | |||
| a942826266 | |||
| 7f2df4b08e | |||
| 30091e68cc | |||
| c772aa2d83 | |||
| 153de4dc92 | |||
| fa46a434c2 | |||
| e792afec16 | |||
| e5226c81dd | |||
| 2f31c8997b | |||
| 3076c922e3 | |||
| 3e87d77b04 | |||
| f75c04c052 | |||
| 6a6f316e5f | |||
| 3b52480c81 | |||
| eef091582d | |||
| 73964fe874 | |||
| 766a930542 | |||
| dfc84bcbe9 | |||
| c8cb265394 | |||
| e1dbbbb096 | |||
| 35f1dd3865 | |||
| 49cd290d5a | |||
| 4d828814da | |||
| 112447c6bc | |||
| 027bd25359 | |||
| 141eeeb629 | |||
| 54b00194a8 | |||
| b42edb3356 | |||
| 7996ee0186 | |||
| bedac5851e | |||
| 91136a58a1 | |||
| 034b131c12 | |||
| 3310c152c3 | |||
| 4dd1bc0782 | |||
| 478d48c237 | |||
| 429d6aa4d0 | |||
| 56c34eddc3 | |||
| 5c5c6aa42c | |||
| 3b749e4c99 | |||
| 1d5b807fc7 | |||
| 97a7cd1369 | |||
| 793a9e1dbc | |||
| 243541d1e4 | |||
| 36eebac312 | |||
| 11b8e518ac | |||
| 6f1e7d744b | |||
| 1270ce60fd | |||
| 48ff6250ab | |||
| a40a67875c | |||
| d1a80838a5 | |||
| e07d70093b | |||
| c64abd8083 | |||
| 768dbed9ff | |||
| 6cb0ffc571 | |||
| 46a7921bf0 | |||
| fbdcbe6472 | |||
| 529693cd5f | |||
| 263ffd1924 | |||
| b0918c1bb9 | |||
| 46d9b1677d | |||
| 9c1de91a82 | |||
| 260c896230 | |||
| 5bb704ac88 | |||
| 7bc9588a4a | |||
| 123ce1be04 | |||
| edab5db3d7 | |||
| 00722bcfef | |||
| 3dd522de3d | |||
| f27b2a901a | |||
| 3c54047474 | |||
| c9ae01f9ae | |||
| 0173fbb679 | |||
| c9d96eabe4 | |||
| df4878a8af | |||
| 8516c7bce3 | |||
| 4f76c220e3 | |||
| f798fa563d | |||
| 3516c5a3de | |||
| 7df1862dcc | |||
| b0b2c4fda3 | |||
| ad6f7a13c6 | |||
| 8e9137f725 |
@@ -1,3 +1,9 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
groups/**/.env
|
||||||
|
groups/**/.env.local
|
||||||
|
README.md
|
||||||
|
i18n.js
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="id" class="scroll-smooth">
|
<html lang="id" class="scroll-smooth">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
@@ -1177,7 +1177,7 @@
|
|||||||
style="background:rgba(254,180,1,0.08);filter:blur(80px);margin-right:-5rem;margin-top:-5rem;">
|
style="background:rgba(254,180,1,0.08);filter:blur(80px);margin-right:-5rem;margin-top:-5rem;">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container mx-auto px-4" style="max-width:1100px;">
|
<div class="container mx-auto px-4 lg:px-8">
|
||||||
<div style="display:grid;grid-template-columns:1fr;gap:1.5rem;align-items:center;"
|
<div style="display:grid;grid-template-columns:1fr;gap:1.5rem;align-items:center;"
|
||||||
class="lg-hero-grid">
|
class="lg-hero-grid">
|
||||||
<!-- Left: text -->
|
<!-- Left: text -->
|
||||||
@@ -1185,9 +1185,9 @@
|
|||||||
<span class="acaab-fade-in"
|
<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;"
|
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 & PRESTASI</span>
|
data-i18n="grp_acaab_hero_badge">MAHASISWA & PRESTASI</span>
|
||||||
<h1 class="acaab-fade-in"
|
<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;"
|
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</h1>
|
data-i18n="grp_acaab_hero_title">Sorotan dan Prestasi Karya Mahasiswa</h2>
|
||||||
<p class="acaab-fade-in"
|
<p class="acaab-fade-in"
|
||||||
style="color:rgba(255,255,255,0.7);font-size:0.9375rem;line-height:1.7;margin:0;"
|
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
|
data-i18n="grp_acaab_hero_desc">Temukan pencapaian luar biasa mahasiswa Informatika
|
||||||
@@ -1201,6 +1201,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div style="position:relative;">
|
<div style="position:relative;">
|
||||||
<img src="./hero.png" alt="Mahasiswa Informatika UNTAN"
|
<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;"
|
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'">
|
onerror="this.style.display='none'">
|
||||||
<!-- Gradient fades over image -->
|
<!-- Gradient fades over image -->
|
||||||
@@ -1234,67 +1236,65 @@
|
|||||||
<div
|
<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;">
|
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;">
|
||||||
|
|
||||||
<!-- Row 1: Dua chip header navy -->
|
<div style="display:flex;flex-wrap:wrap;gap:1.5rem;">
|
||||||
<div style="display:flex;gap:0.75rem;margin-bottom:1.25rem;">
|
<!-- Prestasi Block -->
|
||||||
<!-- TOTAL PRESTASI chip — lebar 4x -->
|
<div style="flex:4;min-width:240px;">
|
||||||
<div
|
<div style="background:#003150;border-radius:0.5rem;padding:0.5rem 0.75rem;text-align:center;margin-bottom:1.25rem;">
|
||||||
style="flex:4;background:#003150;border-radius:0.5rem;padding:0.5rem 0.75rem;text-align:center;">
|
<span style="color:white;font-size:0.875rem;font-weight:800;letter-spacing:0.12em;text-transform:uppercase;"
|
||||||
<span
|
data-i18n="grp_acaab_stats_total_ach">Total Prestasi</span>
|
||||||
style="color:white;font-size:0.65rem;font-weight:800;letter-spacing:0.12em;text-transform:uppercase;"
|
</div>
|
||||||
data-i18n="grp_acaab_stats_total_ach">Total Prestasi</span>
|
<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;">
|
||||||
|
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>
|
</div>
|
||||||
<!-- TOTAL PORTOFOLIO chip — lebar 1x -->
|
|
||||||
<div
|
|
||||||
style="flex:1;background:#003150;border-radius:0.5rem;padding:0.5rem 0.75rem;text-align:center;">
|
|
||||||
<span
|
|
||||||
style="color:white;font-size:0.65rem;font-weight:800;letter-spacing:0.12em;text-transform:uppercase;"
|
|
||||||
data-i18n="grp_acaab_stats_total_proj">Total Portofolio</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Row 2: 5 angka sejajar (4 prestasi + 1 proyek) -->
|
<!-- Proyek Block -->
|
||||||
<div style="display:flex;gap:0.25rem;">
|
<div style="flex:1;min-width:120px;">
|
||||||
<!-- Prestasi Mahasiswa (total) -->
|
<div style="background:#003150;border-radius:0.5rem;padding:0.5rem 0.75rem;text-align:center;margin-bottom:1.25rem;">
|
||||||
<div style="flex:1;text-align:center;padding:0 0.25rem;">
|
<span style="color:white;font-size:0.875rem;font-weight:800;letter-spacing:0.12em;text-transform:uppercase;"
|
||||||
<div id="stat-total-ach"
|
data-i18n="grp_acaab_stats_total_proj">Total Portofolio</span>
|
||||||
style="font-size:clamp(2rem,4.5vw,4rem);font-weight:900;color:#003150;line-height:1;font-family:'Roboto Condensed',sans-serif;">
|
</div>
|
||||||
0</div>
|
<div style="display:flex;justify-content:center;">
|
||||||
<div
|
<!-- Proyek Mahasiswa (total) -->
|
||||||
style="font-size:0.55rem;font-weight:800;text-transform:uppercase;letter-spacing:0.08em;color:#003150;margin-top:0.5rem;line-height:1.3;">
|
<div style="text-align:center;padding:0 0.15rem;">
|
||||||
Prestasi<br>Mahasiswa</div>
|
<div id="stat-total-proj"
|
||||||
</div>
|
style="font-size:clamp(2rem,4.5vw,4rem);font-weight:900;color:#003150;line-height:1;font-family:'Roboto Condensed',sans-serif;">
|
||||||
<!-- Internasional -->
|
0</div>
|
||||||
<div style="flex:1;text-align:center;padding:0 0.25rem;">
|
<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;">
|
||||||
<div id="stat-intl"
|
Proyek<br>Mahasiswa</div>
|
||||||
style="font-size:clamp(2rem,4.5vw,4rem);font-weight:900;color:#003150;line-height:1;font-family:'Roboto Condensed',sans-serif;">
|
</div>
|
||||||
0</div>
|
</div>
|
||||||
<div style="font-size:0.55rem;font-weight:800;text-transform:uppercase;letter-spacing:0.08em;color:#003150;margin-top:0.5rem;"
|
|
||||||
data-i18n="grp_acaab_stats_intl">Internasional</div>
|
|
||||||
</div>
|
|
||||||
<!-- Nasional -->
|
|
||||||
<div style="flex:1;text-align:center;padding:0 0.25rem;">
|
|
||||||
<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:0.55rem;font-weight:800;text-transform:uppercase;letter-spacing:0.08em;color:#003150;margin-top:0.5rem;"
|
|
||||||
data-i18n="grp_acaab_stats_national">Nasional</div>
|
|
||||||
</div>
|
|
||||||
<!-- Regional -->
|
|
||||||
<div style="flex:1;text-align:center;padding:0 0.25rem;">
|
|
||||||
<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:0.55rem;font-weight:800;text-transform:uppercase;letter-spacing:0.08em;color:#003150;margin-top:0.5rem;"
|
|
||||||
data-i18n="grp_acaab_stats_regional">Regional</div>
|
|
||||||
</div>
|
|
||||||
<!-- Proyek Mahasiswa (total) -->
|
|
||||||
<div style="flex:1;text-align:center;padding:0 0.25rem;">
|
|
||||||
<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:0.55rem;font-weight:800;text-transform:uppercase;letter-spacing:0.08em;color:#003150;margin-top:0.5rem;line-height:1.3;">
|
|
||||||
Proyek<br>Mahasiswa</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1341,11 +1341,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- Row 3: Dropdowns -->
|
<!-- Row 3: Dropdowns -->
|
||||||
<div style="display:flex;flex-wrap:wrap;gap:0.625rem;">
|
<div style="display:flex;flex-wrap:wrap;gap:0.625rem;">
|
||||||
<select id="acaab-ach-level"
|
<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>
|
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"
|
<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>
|
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"
|
<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>
|
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>
|
||||||
</div>
|
</div>
|
||||||
@@ -1355,7 +1355,7 @@
|
|||||||
|
|
||||||
<!-- Cards grid -->
|
<!-- Cards grid -->
|
||||||
<div id="acaab-ach-grid"
|
<div id="acaab-ach-grid"
|
||||||
style="display:grid;grid-template-columns:repeat(auto-fill,minmax(270px,1fr));gap:1.5rem;">
|
style="display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:2rem;justify-content:center;">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Pagination buttons -->
|
<!-- Pagination buttons -->
|
||||||
@@ -1371,27 +1371,16 @@
|
|||||||
|
|
||||||
<!-- ── PROJECT CAROUSEL ───────────────────────────────────────────────── -->
|
<!-- ── PROJECT CAROUSEL ───────────────────────────────────────────────── -->
|
||||||
<section id="acaab-proj-section" class="not-prose acaab-full-bleed" style="padding:2.5rem 0 3rem;background:#f8fafc;">
|
<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" style="max-width:1100px;">
|
<div class="container mx-auto px-4 lg:px-8">
|
||||||
<!-- Section header row: title + scroll buttons (seperti di React source) -->
|
<!-- Section header row: title + scroll buttons (seperti di React source) -->
|
||||||
<div
|
<div
|
||||||
style="display:flex;align-items:flex-start;justify-content:space-between;gap:1rem;margin-bottom:1.5rem;">
|
style="display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:1rem;margin-bottom:1.5rem;">
|
||||||
<div>
|
<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;"
|
<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>
|
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;"
|
<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>
|
data-i18n="grp_acaab_proj_desc">Jelajahi inovasi digital dan aplikasi nyata yang dikembangkan oleh mahasiswa Informatika UNTAN.</p>
|
||||||
</div>
|
</div>
|
||||||
<!-- Scroll control buttons (atas, kanan) -->
|
|
||||||
<div style="display:flex;align-items:center;gap:0.5rem;flex-shrink:0;margin-top:0.25rem;">
|
|
||||||
<button id="acaab-proj-prev" aria-label="Geser kiri"
|
|
||||||
style="width:2.5rem;height:2.5rem;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:0.875rem;"></i>
|
|
||||||
</button>
|
|
||||||
<button id="acaab-proj-next" aria-label="Geser kanan"
|
|
||||||
style="width:2.5rem;height:2.5rem;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:0.875rem;"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Filter panel -->
|
<!-- Filter panel -->
|
||||||
@@ -1418,9 +1407,9 @@
|
|||||||
placeholder="Cari karya..." data-i18n-placeholder="grp_acaab_proj_search">
|
placeholder="Cari karya..." data-i18n-placeholder="grp_acaab_proj_search">
|
||||||
</div>
|
</div>
|
||||||
<div style="display:flex;flex-wrap:wrap;gap:0.625rem;">
|
<div style="display:flex;flex-wrap:wrap;gap:0.625rem;">
|
||||||
<select id="acaab-proj-cat"
|
<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>
|
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"
|
<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>
|
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>
|
||||||
</div>
|
</div>
|
||||||
@@ -1433,6 +1422,18 @@
|
|||||||
<div id="acaab-proj-track" class="acaab-carousel-track acaab-hide-scrollbar"
|
<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>
|
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 -->
|
<!-- Load more -->
|
||||||
<div style="text-align:center;margin-top:1.5rem;">
|
<div style="text-align:center;margin-top:1.5rem;">
|
||||||
<button id="acaab-proj-more"
|
<button id="acaab-proj-more"
|
||||||
@@ -1444,8 +1445,8 @@
|
|||||||
|
|
||||||
<!-- ── CTA ────────────────────────────────────────────────────────────── -->
|
<!-- ── CTA ────────────────────────────────────────────────────────────── -->
|
||||||
<section class="not-prose acaab-full-bleed bg-untan-navy"
|
<section class="not-prose acaab-full-bleed bg-untan-navy"
|
||||||
style="padding:3.5rem 0;">
|
style="padding:3.5rem 0; margin-bottom: 3rem;">
|
||||||
<div class="container mx-auto px-4" style="max-width:1100px;">
|
<div class="container mx-auto px-4 lg:px-8">
|
||||||
<!-- Inner card: kuning transparan dengan border, rounded-2xl -->
|
<!-- Inner card: kuning transparan dengan border, rounded-2xl -->
|
||||||
<div
|
<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;">
|
style="background:rgba(254,180,1,0.08);border:1px solid rgba(254,180,1,0.2);border-radius:1rem;padding:2.5rem 2rem;">
|
||||||
@@ -1660,8 +1661,9 @@
|
|||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="./main.js"></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>
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
/**
|
||||||
|
* 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_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_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);
|
||||||
|
}());
|
||||||
@@ -181,7 +181,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
if (achResult.status === 'fulfilled') {
|
if (achResult.status === 'fulfilled') {
|
||||||
achievements = achResult.value;
|
achievements = achResult.value.sort((a, b) => b.year - a.year);
|
||||||
} else {
|
} else {
|
||||||
console.error('[ACAAB] achievements error:', achResult.reason);
|
console.error('[ACAAB] achievements error:', achResult.reason);
|
||||||
showError('acaab-ach-grid', t('grp_acaab_ach_error'));
|
showError('acaab-ach-grid', t('grp_acaab_ach_error'));
|
||||||
@@ -359,9 +359,11 @@ function renderAchievements() {
|
|||||||
if (moreBtn) moreBtn.style.display = achVisible < filtered.length ? 'inline-block' : 'none';
|
if (moreBtn) moreBtn.style.display = achVisible < filtered.length ? 'inline-block' : 'none';
|
||||||
if (lessBtn) lessBtn.style.display = achVisible > ACH_INIT ? 'inline-block' : 'none';
|
if (lessBtn) lessBtn.style.display = achVisible > ACH_INIT ? 'inline-block' : 'none';
|
||||||
|
|
||||||
grid.querySelectorAll('.acaab-ach-readmore').forEach(btn => {
|
grid.querySelectorAll('.acaab-ach-card').forEach(card => {
|
||||||
btn.addEventListener('click', () => {
|
card.addEventListener('click', (e) => {
|
||||||
const ach = achievements.find(a => a.id === btn.dataset.id);
|
// 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);
|
if (ach) openAchModal(ach);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -369,7 +371,7 @@ function renderAchievements() {
|
|||||||
|
|
||||||
function achCardHtml(a, idx) {
|
function achCardHtml(a, idx) {
|
||||||
return `
|
return `
|
||||||
<article class="acaab-ach-card acaab-fade-in" style="animation-delay:${idx * 0.05}s;">
|
<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>
|
<div class="acaab-ach-top-bar"></div>
|
||||||
<!-- Image h-56 (224px) -->
|
<!-- Image h-56 (224px) -->
|
||||||
<div style="position:relative;height:224px;overflow:hidden;background:#f1f5f9;">
|
<div style="position:relative;height:224px;overflow:hidden;background:#f1f5f9;">
|
||||||
@@ -494,11 +496,11 @@ function openAchModal(a) {
|
|||||||
</div>` : '';
|
</div>` : '';
|
||||||
|
|
||||||
renderModal(`
|
renderModal(`
|
||||||
<!-- Image header h-56 -->
|
<!-- Image header -->
|
||||||
<div style="position:relative;height:224px;overflow:hidden;border-radius:0.75rem 0.75rem 0 0;background:#e2e8f0;flex-shrink:0;">
|
<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%;height:100%;object-fit:cover;"
|
<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/003150/FDB813?text=Informatika+UNTAN'">
|
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.2) 50%,transparent 100%);pointer-events:none;"></div>
|
<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 -->
|
<!-- Close button: white rounded-full -->
|
||||||
<button id="acaab-modal-close" aria-label="Tutup"
|
<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);">
|
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);">
|
||||||
@@ -568,13 +570,18 @@ function buildProjFilterOptions() {
|
|||||||
|
|
||||||
function filterProjects() {
|
function filterProjects() {
|
||||||
const q = projFilter.search.toLowerCase();
|
const q = projFilter.search.toLowerCase();
|
||||||
return projects.filter(p => {
|
const result = projects.filter(p => {
|
||||||
const matchSearch = !q || [p.title, p.studentName, p.description, ...p.categories]
|
const matchSearch = !q || [p.title, p.studentName, p.description, ...p.categories]
|
||||||
.some(s => (s || '').toLowerCase().includes(q));
|
.some(s => (s || '').toLowerCase().includes(q));
|
||||||
const matchCat = !projFilter.cat || p.categories.includes(projFilter.cat);
|
const matchCat = !projFilter.cat || p.categories.includes(projFilter.cat);
|
||||||
const matchYear = !projFilter.year || String(p.year) === projFilter.year;
|
const matchYear = !projFilter.year || String(p.year) === projFilter.year;
|
||||||
return matchSearch && matchCat && matchYear;
|
return matchSearch && matchCat && matchYear;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Sort descending by year
|
||||||
|
result.sort((a, b) => (b.year || 0) - (a.year || 0));
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderProjects() {
|
function renderProjects() {
|
||||||
@@ -598,9 +605,17 @@ function renderProjects() {
|
|||||||
track.innerHTML = visible.map((p, i) => projCardHtml(p, i)).join('');
|
track.innerHTML = visible.map((p, i) => projCardHtml(p, i)).join('');
|
||||||
if (moreBtn) moreBtn.style.display = projVisible < filtered.length ? 'inline-block' : 'none';
|
if (moreBtn) moreBtn.style.display = projVisible < filtered.length ? 'inline-block' : 'none';
|
||||||
|
|
||||||
track.querySelectorAll('.acaab-proj-readmore').forEach(btn => {
|
// Responsif: Sembunyikan scroll button & center jika cuma 1 data
|
||||||
btn.addEventListener('click', () => {
|
const prevBtn = document.getElementById('acaab-proj-prev');
|
||||||
const proj = projects.find(p => p.id === btn.dataset.id);
|
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);
|
if (proj) openProjModal(proj);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -613,7 +628,7 @@ function projCardHtml(p, idx) {
|
|||||||
</span>`).join('');
|
</span>`).join('');
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="acaab-proj-card acaab-proj-card-wrap acaab-fade-in" style="animation-delay:${idx * 0.05}s;">
|
<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>
|
<div class="acaab-proj-left-bar"></div>
|
||||||
<!-- Image h-52 (208px) dengan title overlay -->
|
<!-- Image h-52 (208px) dengan title overlay -->
|
||||||
<div style="height:208px;overflow:hidden;position:relative;">
|
<div style="height:208px;overflow:hidden;position:relative;">
|
||||||
@@ -720,11 +735,11 @@ function openProjModal(p) {
|
|||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
renderModal(`
|
renderModal(`
|
||||||
<!-- Image header h-56 -->
|
<!-- Image header -->
|
||||||
<div style="position:relative;height:224px;overflow:hidden;border-radius:0.75rem 0.75rem 0 0;background:#e2e8f0;flex-shrink:0;">
|
<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%;height:100%;object-fit:cover;"
|
<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/003150/FDB813?text=Informatika+UNTAN'">
|
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.2) 50%,transparent 100%);pointer-events:none;"></div>
|
<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 -->
|
<!-- Close button -->
|
||||||
<button id="acaab-modal-close" aria-label="Tutup"
|
<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);">
|
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);">
|
||||||
|
|||||||
@@ -201,6 +201,22 @@ body.acaab-modal-open {
|
|||||||
|
|
||||||
/* --- Reduced motion --- */
|
/* --- Reduced motion --- */
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.acaab-fade-in, .acaab-skeleton, .acaab-card-img,
|
|
||||||
.acaab-ach-card, .acaab-proj-card-wrap { animation: none !important; transition: none !important; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,273 @@
|
|||||||
|
# PROJECT CONTEXT
|
||||||
|
## Website Statis Halaman Beasiswa — Informatika UNTAN
|
||||||
|
|
||||||
|
> File ini adalah konteks utama proyek untuk AI assistant (Antigravity).
|
||||||
|
> Baca file ini setiap kali kamu membantu pengerjaan proyek ini.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Apa yang Dibangun?
|
||||||
|
|
||||||
|
**Single static landing page** berbasis HTML/CSS/JS murni (native web) untuk menampilkan informasi beasiswa bagi mahasiswa aktif, alumni, dan masyarakat umum Program Studi Informatika, Universitas Tanjungpura (UNTAN), Pontianak.
|
||||||
|
|
||||||
|
Website ini bukan aplikasi web dinamis. Tidak ada backend, tidak ada database, tidak ada login. Update konten dilakukan dengan mengedit file JSON dan push ke GitHub → auto-deploy via Puify ke server prodi.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏗️ Tech Stack
|
||||||
|
|
||||||
|
| Layer | Teknologi |
|
||||||
|
|---|---|
|
||||||
|
| Markup | HTML5 |
|
||||||
|
| Styling | CSS3 + Tailwind CSS |
|
||||||
|
| Interaktivitas | Vanilla JavaScript (ES6+) |
|
||||||
|
| Font | Google Fonts (Inter / Poppins) |
|
||||||
|
| Ikon | Font Awesome |
|
||||||
|
| Animasi | AOS.js (Animate on Scroll) |
|
||||||
|
| Carousel | Swiper.js |
|
||||||
|
| Hosting | Dedicated Server Prodi UNTAN via Puify |
|
||||||
|
| Version Control | Git + GitHub |
|
||||||
|
|
||||||
|
**Tidak boleh menggunakan:** Framework berat (React, Vue, Angular), backend (Node.js, PHP, Python), database (MySQL, MongoDB).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📐 Desain
|
||||||
|
|
||||||
|
- **Figma Project:** https://www.figma.com/design/rG6JYG19CWziaPMhbJ2NPL/PPL-FIGMA?node-id=0-1
|
||||||
|
- **Prototype:** https://www.figma.com/proto/rG6JYG19CWziaPMhbJ2NPL/PPL-FIGMA?node-id=120-1247
|
||||||
|
|
||||||
|
### Warna Utama
|
||||||
|
|
||||||
|
| Variabel | Hex | Penggunaan |
|
||||||
|
|---|---|---|
|
||||||
|
| Primary (Biru) | `#2B4590` | Navbar, heading, tombol, border aktif |
|
||||||
|
| Secondary (Kuning) | `#F5A623` | CTA, aksen, badge |
|
||||||
|
| Background | `#FFFFFF` | Latar halaman |
|
||||||
|
| Text | `#1A1A1A` | Body text |
|
||||||
|
| Accent | `#6B7280` | Caption, label, border |
|
||||||
|
| Success | `#10B981` | Badge "Buka" |
|
||||||
|
| Danger | `#EF4444` | Badge "Ditutup" |
|
||||||
|
| Overlay | `#00000080` | Background popup |
|
||||||
|
|
||||||
|
### Tipografi
|
||||||
|
- **Font:** Inter (utama) / Poppins (heading)
|
||||||
|
- H1: 48–64px bold | H2: 32–40px semibold | H3: 24–28px semibold
|
||||||
|
- Body: 16–18px regular | Caption: 12–14px
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📄 Struktur Halaman (Top → Bottom)
|
||||||
|
|
||||||
|
```
|
||||||
|
[NAVBAR - Sticky]
|
||||||
|
Logo UNTAN | Menu: Beranda, Beasiswa, Testimoni, Kontak | Toggle ID/EN
|
||||||
|
|
||||||
|
[#hero - Hero Section]
|
||||||
|
Judul: "Beasiswa Studi & Penelitian"
|
||||||
|
Sub: Teknik Informatika Universitas Tanjungpura
|
||||||
|
Deskripsi singkat tentang beasiswa
|
||||||
|
CTA Button (kuning): "Lihat Daftar Beasiswa →"
|
||||||
|
Foto ilustrasi mahasiswa (kanan)
|
||||||
|
|
||||||
|
[#statistics - Statistik Counter]
|
||||||
|
3 angka dengan animasi scroll:
|
||||||
|
- Total Mahasiswa Penerima Beasiswa Semester Ini
|
||||||
|
- Total Penerima Beasiswa Pembiayaan Pemerintah
|
||||||
|
- Total Penerima Beasiswa Pembiayaan Swasta
|
||||||
|
|
||||||
|
[#directory - Direktori Beasiswa]
|
||||||
|
- Tab filter: Semua | Sedang Buka | Ditutup | Pemerintah | Swasta
|
||||||
|
- Search bar (real-time)
|
||||||
|
- Grid kartu beasiswa:
|
||||||
|
* Desktop: 3 kolom
|
||||||
|
* Tablet: 2 kolom
|
||||||
|
* Mobile: 1 kolom
|
||||||
|
- Tiap kartu: thumbnail, badge status, nama, deskripsi singkat, tanggal, tombol "Selengkapnya →"
|
||||||
|
- Klik "Selengkapnya →" → buka POPUP MODAL
|
||||||
|
|
||||||
|
[POPUP MODAL - Detail Beasiswa]
|
||||||
|
- Nama + thumbnail/logo beasiswa
|
||||||
|
- Badge status (Buka / Ditutup)
|
||||||
|
- Syarat umum (ringkasan/bullet)
|
||||||
|
- Kontak yang bisa dihubungi
|
||||||
|
- Tombol "Info Lebih Lanjut" → buka URL sumber resmi (tab baru)
|
||||||
|
- Tutup: tombol ×, klik luar area, tekan Escape
|
||||||
|
|
||||||
|
[#testimonial - Testimoni Penerima Beasiswa]
|
||||||
|
- Carousel / grid kartu
|
||||||
|
- Tiap kartu: foto, nama, nama beasiswa yang diterima, kutipan singkat
|
||||||
|
- Navigasi carousel jika > 3 testimoni
|
||||||
|
|
||||||
|
[#cta - Call to Action]
|
||||||
|
- Judul: "Ingin Mengetahui Info Lebih Lanjut?"
|
||||||
|
- Dua tombol:
|
||||||
|
* "Daftar Beasiswa" (scroll ke #directory)
|
||||||
|
* "Hubungi Kami" (WhatsApp admin)
|
||||||
|
|
||||||
|
[#footer - Footer]
|
||||||
|
- Logo Informatika UNTAN
|
||||||
|
- Alamat prodi, email, telepon, WhatsApp
|
||||||
|
- Ikon media sosial
|
||||||
|
- Ilustrasi khas Pontianak (dekorasi bawah)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 Struktur File Proyek
|
||||||
|
|
||||||
|
```
|
||||||
|
project-root/
|
||||||
|
├── index.html
|
||||||
|
├── assets/
|
||||||
|
│ ├── css/
|
||||||
|
│ │ └── style.css
|
||||||
|
│ ├── js/
|
||||||
|
│ │ ├── main.js # Init AOS, smooth scroll, sticky navbar
|
||||||
|
│ │ ├── filter.js # Filter tab + real-time search
|
||||||
|
│ │ ├── popup.js # Modal detail beasiswa
|
||||||
|
│ │ ├── counter.js # Animasi counter statistik
|
||||||
|
│ │ ├── carousel.js # Carousel testimoni (Swiper.js)
|
||||||
|
│ │ └── i18n.js # Toggle bahasa ID/EN
|
||||||
|
│ └── images/
|
||||||
|
│ ├── hero/
|
||||||
|
│ ├── beasiswa/ # Logo/thumbnail beasiswa
|
||||||
|
│ └── testimoni/ # Foto penerima
|
||||||
|
├── data/
|
||||||
|
│ ├── beasiswa.json
|
||||||
|
│ └── testimoni.json
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🗃️ Format Data
|
||||||
|
|
||||||
|
### `data/beasiswa.json`
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "bsw-001",
|
||||||
|
"nama": "Beasiswa Adaro Foundation 2026",
|
||||||
|
"kategori": "Swasta",
|
||||||
|
"status": "Ditutup",
|
||||||
|
"thumbnail": "assets/images/beasiswa/adaro.jpg",
|
||||||
|
"deskripsi_singkat": "Beasiswa untuk mahasiswa S1 & D4 dengan IPK minimal 3.00...",
|
||||||
|
"syarat_umum": [
|
||||||
|
"Mahasiswa aktif S1/D4",
|
||||||
|
"IPK minimal 3.00",
|
||||||
|
"Berasal dari keluarga kurang mampu"
|
||||||
|
],
|
||||||
|
"kontak": "Admin Prodi / Kemahasiswaan UNTAN",
|
||||||
|
"tanggal_buka": "2026-01-01",
|
||||||
|
"tanggal_tutup": "2026-04-09",
|
||||||
|
"url_sumber": "https://...",
|
||||||
|
"tags": ["S1", "Swasta"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `data/testimoni.json`
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "tsm-001",
|
||||||
|
"nama": "Aulia Rahman",
|
||||||
|
"foto": "assets/images/testimoni/aulia.jpg",
|
||||||
|
"nama_beasiswa": "Beasiswa Prestasi",
|
||||||
|
"kutipan": "Beasiswa ini benar-benar membuka jalan bagi saya untuk fokus sepenuhnya pada pendidikan..."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ Fitur Interaktif yang Harus Diimplementasikan
|
||||||
|
|
||||||
|
| Fitur | Status | File |
|
||||||
|
|---|---|---|
|
||||||
|
| Sticky Navbar (transparent → solid saat scroll) | ⏳ | `main.js` |
|
||||||
|
| Hamburger Menu | ⏳ | `main.js` |
|
||||||
|
| Smooth Scroll ke seksi | ⏳ | `main.js` |
|
||||||
|
| Active State Navbar (IntersectionObserver) | ⏳ | `main.js` |
|
||||||
|
| Animasi AOS (elemen muncul saat scroll) | ⏳ | `main.js` |
|
||||||
|
| Counter Animasi Statistik | ⏳ | `counter.js` |
|
||||||
|
| Render kartu beasiswa dari JSON | ⏳ | `filter.js` |
|
||||||
|
| Filter tab (Semua/Buka/Ditutup/Pemerintah/Swasta) | ⏳ | `filter.js` |
|
||||||
|
| Real-time search | ⏳ | `filter.js` |
|
||||||
|
| Popup Modal detail beasiswa | ⏳ | `popup.js` |
|
||||||
|
| Tutup popup (×, klik luar, Escape) | ⏳ | `popup.js` |
|
||||||
|
| Carousel testimoni (Swiper.js) | ⏳ | `carousel.js` |
|
||||||
|
| Toggle bahasa ID/EN | ⏳ | `i18n.js` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📱 Responsive Breakpoints
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Mobile first */
|
||||||
|
/* Default: mobile < 768px */
|
||||||
|
/* Tablet: */ @media (min-width: 768px) { ... }
|
||||||
|
/* Laptop: */ @media (min-width: 1024px) { ... }
|
||||||
|
/* Desktop: */ @media (min-width: 1280px) { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔒 Aturan Keamanan Kode
|
||||||
|
|
||||||
|
- Semua link eksternal: `target="_blank" rel="noopener noreferrer"`
|
||||||
|
- Tidak ada hardcoded data sensitif di source code
|
||||||
|
- Input search di-sanitasi sebelum digunakan untuk filter DOM
|
||||||
|
- Tidak menggunakan `innerHTML` dengan input pengguna langsung (cegah XSS)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 👥 Tim & Peran
|
||||||
|
|
||||||
|
| Nama | Peran |
|
||||||
|
|---|---|
|
||||||
|
| Djapianus Thebrianto | Project Manager |
|
||||||
|
| Derangga Aray Abimanyu | Backend Developer (data, scraping, JSON) |
|
||||||
|
| Destra Aulia Faza Ananda Putra | Frontend Developer (HTML, CSS, JS) |
|
||||||
|
| Dian Satrio Prayogo | UI/UX Designer (Figma) |
|
||||||
|
| Jonathan Geral Nababan | Quality Assurance (testing) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Hal Penting untuk AI Assistant
|
||||||
|
|
||||||
|
1. **Ini bukan React/Vue/Angular.** Semua harus ditulis dalam HTML/CSS/Tailwind/Vanilla JS.
|
||||||
|
2. **Data beasiswa dibaca dari `data/beasiswa.json`** — bukan hardcoded di HTML.
|
||||||
|
3. **Warna wajib sesuai palet resmi** — jangan gunakan warna lain sembarangan.
|
||||||
|
4. **Mobile-first** — tulis CSS dari ukuran kecil dulu, baru ke besar.
|
||||||
|
5. **Popup harus bisa ditutup 3 cara:** tombol ×, klik overlay, tekan Escape.
|
||||||
|
6. **Filter dan search bisa aktif bersamaan** (kumulatif, bukan saling menggantikan).
|
||||||
|
7. **Navbar berubah warna saat scroll** — transparent di atas hero, solid biru setelah scroll turun.
|
||||||
|
8. **Semua animasi pakai AOS.js** — inisialisasi di `main.js` dengan `AOS.init()`.
|
||||||
|
9. **Carousel testimoni pakai Swiper.js** — touch-friendly untuk mobile.
|
||||||
|
10. **Tidak ada `alert()`, `console.log()` di production code.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Cara Kerja Deployment
|
||||||
|
|
||||||
|
```
|
||||||
|
Developer push ke GitHub
|
||||||
|
↓
|
||||||
|
Puify detects push
|
||||||
|
↓
|
||||||
|
Auto-deploy ke server
|
||||||
|
↓
|
||||||
|
Website live (Dev)
|
||||||
|
↓
|
||||||
|
Approved → Production
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📎 Dokumen Terkait
|
||||||
|
|
||||||
|
- `SRS.md` — Spesifikasi kebutuhan lengkap (use case, NFR, batasan)
|
||||||
|
- `SDD.md` — Desain teknis lengkap (arsitektur, komponen, schema data)
|
||||||
|
- Figma: https://www.figma.com/design/rG6JYG19CWziaPMhbJ2NPL/PPL-FIGMA?node-id=0-1
|
||||||
@@ -0,0 +1,329 @@
|
|||||||
|
# Software Design Document (SDD)
|
||||||
|
## Website Statis Halaman Beasiswa — One-Page HTML
|
||||||
|
### Program Studi Informatika — Universitas Tanjungpura
|
||||||
|
|
||||||
|
| Field | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Nama Proyek | Pengembangan Website Statis Halaman Beasiswa |
|
||||||
|
| Kelompok | Ape Nama Team E |
|
||||||
|
| Versi | 1.0 |
|
||||||
|
| Fase SDLC | Design (aktif) |
|
||||||
|
| Dosen | Izuardo Zulkarnain, S.Pd., M.T |
|
||||||
|
| Semester | Genap 2025/2026 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Pendahuluan
|
||||||
|
|
||||||
|
### 1.1 Tujuan Dokumen
|
||||||
|
SDD ini mendeskripsikan perancangan teknis website beasiswa Informatika UNTAN sebagai one-page HTML native. Dokumen ini menjadi panduan tim dalam mengimplementasikan:
|
||||||
|
- Fitur popup detail beasiswa
|
||||||
|
- Integrasi data hasil scraping & survei admin
|
||||||
|
- Otomatisasi deployment via Puify (push-to-deploy)
|
||||||
|
|
||||||
|
### 1.2 Stack Teknologi
|
||||||
|
|
||||||
|
| Layer | Teknologi | Keterangan |
|
||||||
|
|---|---|---|
|
||||||
|
| Markup | HTML5 | Struktur konten |
|
||||||
|
| Styling | CSS3 / Tailwind CSS | Layout responsif |
|
||||||
|
| Interaktivitas | JavaScript (Vanilla / Alpine.js) | Animasi, scroll, toggle |
|
||||||
|
| Font & Ikon | Google Fonts / Font Awesome | Tipografi & ikon |
|
||||||
|
| Hosting | Puify (SaaS) | Auto-deploy dari repo ke server |
|
||||||
|
| Server | Dedicated Server Prodi | Dev & Production |
|
||||||
|
| Version Control | Git + GitHub | Kolaborasi & push-to-deploy |
|
||||||
|
|
||||||
|
### 1.3 Posisi dalam SDLC
|
||||||
|
|
||||||
|
| Fase | Status |
|
||||||
|
|---|---|
|
||||||
|
| Requirement | ✅ Selesai |
|
||||||
|
| Design | 🔄 Aktif |
|
||||||
|
| Implementation | ⏳ Belum |
|
||||||
|
| Testing | ⏳ Belum |
|
||||||
|
| Deployment | ⏳ Belum |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Arsitektur Halaman (Site Architecture)
|
||||||
|
|
||||||
|
### 2.1 Konsep One-Page Website
|
||||||
|
Single-page statis — semua konten dalam satu file HTML utama. Navigasi menggunakan **smooth scroll**. Detail beasiswa (syarat umum) ditampilkan via **popup modal** agar halaman tetap bersih.
|
||||||
|
|
||||||
|
### 2.2 Struktur Seksi (Vertikal, Atas → Bawah)
|
||||||
|
|
||||||
|
| No | ID | Nama Seksi | Konten Utama |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | `#hero` | Hero / Landing | Judul "Beasiswa Studi & Penelitian", deskripsi, CTA "Lihat Daftar Beasiswa", foto ilustrasi mahasiswa |
|
||||||
|
| 2 | `#statistics` | Statistik Counter | 3 angka animasi: Total Penerima Semester Ini, Total Pemerintah, Total Swasta |
|
||||||
|
| 3 | `#directory` | Direktori Beasiswa | Grid kartu (3 kolom desktop, 2 tablet, 1 mobile), tab filter, real-time search, popup detail |
|
||||||
|
| 4 | `#testimonial` | Testimoni | Carousel/grid testimoni: foto, nama, beasiswa, kutipan singkat |
|
||||||
|
| 5 | `#cta` | Call-to-Action | Banner ajakan + tombol WhatsApp admin + Instagram kemahasiswaan |
|
||||||
|
| 6 | `#footer` | Footer | Logo prodi, alamat, email, telepon, WA, ikon sosmed, dekorasi ilustrasi Pontianak |
|
||||||
|
|
||||||
|
### 2.3 Navigasi
|
||||||
|
**Sticky navbar** yang selalu terlihat saat scroll.
|
||||||
|
|
||||||
|
| Perangkat | Perilaku Navbar |
|
||||||
|
|---|---|
|
||||||
|
| Desktop/Laptop | Horizontal, semua item terlihat |
|
||||||
|
| Tablet | Horizontal, item muat; sisanya dropdown |
|
||||||
|
| Mobile | Hamburger menu (≡) — tap untuk expand vertikal |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Desain Visual & Identitas
|
||||||
|
|
||||||
|
### 3.1 Color Palette
|
||||||
|
|
||||||
|
| Peran | Nama | Hex | Digunakan Untuk |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Primary | Biru Informatika | `#2B4590` | Navbar, heading, tombol primary, border aktif |
|
||||||
|
| Secondary | Kuning/Oranye | `#F5A623` | CTA button, aksen, badge |
|
||||||
|
| Background | White | `#FFFFFF` | Latar halaman utama |
|
||||||
|
| Text | Dark Gray | `#1A1A1A` | Body text, paragraf |
|
||||||
|
| Accent | Abu-abu | `#6B7280` | Caption, label, border |
|
||||||
|
| Success | Hijau | `#10B981` | Badge status "Buka" |
|
||||||
|
| Danger | Merah | `#EF4444` | Badge status "Ditutup" |
|
||||||
|
| Overlay | Hitam + Transparansi | `#00000080` | Background popup modal |
|
||||||
|
|
||||||
|
### 3.2 Tipografi
|
||||||
|
|
||||||
|
| Elemen | Font Family | Ukuran | Weight |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Heading H1 | Poppins / Inter | 48–64px | 700 (Bold) |
|
||||||
|
| Heading H2 | Poppins / Inter | 32–40px | 600 (SemiBold) |
|
||||||
|
| Heading H3 | Poppins / Inter | 24–28px | 600 (SemiBold) |
|
||||||
|
| Body Text | Inter / Poppins | 16–18px | 400 (Regular) |
|
||||||
|
| Caption/Label | Inter / Poppins | 12–14px | 400–500 |
|
||||||
|
| Navbar | Inter | 14–16px | 500 (Medium) |
|
||||||
|
| Button | Inter | 14–16px | 600 (SemiBold) |
|
||||||
|
|
||||||
|
### 3.3 Komponen UI
|
||||||
|
|
||||||
|
| Komponen | Spesifikasi |
|
||||||
|
|---|---|
|
||||||
|
| Navbar | Sticky; transparent di atas hero → solid biru `#2B4590` saat scroll; toggle ID/EN di kanan |
|
||||||
|
| Hero Section | Full-height, overlay gradient gelap, judul besar, deskripsi, CTA kuning kontras |
|
||||||
|
| Card Beasiswa | Border subtle, shadow ringan; hover: scale sedikit + shadow lebih dalam; thumbnail 100% lebar |
|
||||||
|
| Button Primary | Solid biru, teks putih |
|
||||||
|
| Button Secondary | Outline biru |
|
||||||
|
| Button Ghost | Tanpa border |
|
||||||
|
| Badge Status | Rounded pill; Hijau "Buka", Merah "Ditutup" |
|
||||||
|
| Tab Filter | Underline active state, smooth transition, teks hitam, garis bawah aktif biru |
|
||||||
|
| Popup Modal | Center screen, dark overlay, border-radius 12px, close button (×) kanan atas |
|
||||||
|
| Footer | Background `#1A1A1A`, teks putih, 3–4 kolom desktop |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Desain Mockup (UI/UX)
|
||||||
|
|
||||||
|
### 4.1 Alat Desain
|
||||||
|
**Figma** — kolaborasi realtime, wireframe + hi-fidelity prototype.
|
||||||
|
|
||||||
|
- **Figma Project:** https://www.figma.com/design/rG6JYG19CWziaPMhbJ2NPL/PPL-FIGMA?node-id=0-1
|
||||||
|
- **Prototype Interaktif:** https://www.figma.com/proto/rG6JYG19CWziaPMhbJ2NPL/PPL-FIGMA?node-id=120-1247
|
||||||
|
|
||||||
|
### 4.2 Tahapan Desain
|
||||||
|
1. **Wireframe (Lo-Fi)** — sketsa hitam-putih, fokus layout & posisi elemen
|
||||||
|
2. **Mockup (Hi-Fi)** — desain lengkap dengan warna, font, dan aset visual
|
||||||
|
3. **Prototype** — mockup yang bisa diklik untuk simulasi navigasi antar seksi
|
||||||
|
4. **User Testing** — validasi desain bersama calon pengguna
|
||||||
|
|
||||||
|
### 4.3 Breakpoint & Resolusi
|
||||||
|
|
||||||
|
| Perangkat | Resolusi Mockup |
|
||||||
|
|---|---|
|
||||||
|
| Desktop Monitor | 1920 × 1080 |
|
||||||
|
| Laptop | 1366 × 768 |
|
||||||
|
| Tablet Portrait | 768 × 1024 |
|
||||||
|
| Tablet Landscape | 1024 × 768 |
|
||||||
|
| Mobile / HP | 390 × 844 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Desain Responsif
|
||||||
|
|
||||||
|
### 5.1 Strategi: Mobile-First
|
||||||
|
Desain dimulai dari layar terkecil → diperluas ke layar lebih besar via CSS media query. Memastikan performa optimal di koneksi lambat.
|
||||||
|
|
||||||
|
### 5.2 Breakpoint
|
||||||
|
|
||||||
|
| Perangkat | Breakpoint | Resolusi Umum |
|
||||||
|
|---|---|---|
|
||||||
|
| Desktop Monitor | `>= 1280px` | 1920×1080, 2560×1440 |
|
||||||
|
| Laptop | `>= 1024px` | 1366×768, 1440×900 |
|
||||||
|
| Tablet | `>= 768px` | 768×1024, 820×1180 |
|
||||||
|
| Mobile | `< 768px` | 360×800, 390×844, 414×896 |
|
||||||
|
|
||||||
|
### 5.3 Perubahan Layout per Breakpoint
|
||||||
|
|
||||||
|
| Elemen | Mobile (<768px) | Tablet (768–1279px) | Desktop (≥1280px) |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Navbar | Hamburger menu | Hamburger / partial menu | Horizontal full menu |
|
||||||
|
| Hero Section | Teks atas, gambar bawah | Teks kiri, gambar kanan 50/50 | Teks kiri, gambar kanan lebar |
|
||||||
|
| Card Grid | 1 kolom | 2 kolom | 3–4 kolom |
|
||||||
|
| Teks Body | 16px | 16–17px | 17–18px |
|
||||||
|
| Padding Section | 24px 16px | 40px 32px | 80px 64px |
|
||||||
|
| Footer | Stack vertikal | 2 kolom | 3–4 kolom |
|
||||||
|
|
||||||
|
### 5.4 Hasil Uji Responsivitas
|
||||||
|
|
||||||
|
| No | Perangkat/Browser | Ukuran | Status | Catatan |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 1 | Chrome Desktop | 1920×1080 | ✅ Lulus | Layout proporsional, spacing rapi |
|
||||||
|
| 2 | Chrome Mobile Sim. | 390×844 | ✅ Lulus | Card grid 1 kolom, teks 16px nyaman |
|
||||||
|
| 3 | Firefox Desktop | 1440×900 | ✅ Lulus | Konsisten dengan Chrome Desktop |
|
||||||
|
| 4 | iPhone (Safari) | 390×844 | ✅ Lulus | Hero stack sempurna, padding aman |
|
||||||
|
| 5 | Android Chrome | 360×800 | ✅ Lulus | Touch target ideal, gambar proporsional |
|
||||||
|
| 6 | iPad (Safari) | 768×1024 | ✅ Lulus | Hero 50/50, spasi sentuh lega |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Use Case Diagram
|
||||||
|
|
||||||
|
### 6.1 Aktor Sistem
|
||||||
|
|
||||||
|
| Aktor | Deskripsi |
|
||||||
|
|---|---|
|
||||||
|
| Pengunjung Umum | Mahasiswa aktif, alumni, calon mahasiswa, dosen, masyarakat — tanpa autentikasi |
|
||||||
|
| Admin Prodi | Pengelola konten: memperbarui data beasiswa, testimoni, statistik via push-to-repo |
|
||||||
|
|
||||||
|
### 6.2 Daftar Use Case & Skenario
|
||||||
|
|
||||||
|
Lihat detail lengkap di SRS.md. Ringkasan:
|
||||||
|
|
||||||
|
| Kode | Nama | Aktor |
|
||||||
|
|---|---|---|
|
||||||
|
| UC-01 | Melihat Daftar Beasiswa | Pengunjung Umum |
|
||||||
|
| UC-02 | Memfilter Beasiswa Berdasarkan Kategori | Pengunjung Umum |
|
||||||
|
| UC-03 | Mencari Beasiswa | Pengunjung Umum |
|
||||||
|
| UC-04 | Melihat Detail Beasiswa (Popup) | Pengunjung Umum |
|
||||||
|
| UC-05 | Mengakses Sumber Resmi Beasiswa | Pengunjung Umum |
|
||||||
|
| UC-06 | Menghubungi Admin via CTA | Pengunjung Umum |
|
||||||
|
| UC-07 | Melihat Testimoni Penerima Beasiswa | Pengunjung Umum |
|
||||||
|
| UC-08 | Mengganti Bahasa Tampilan (ID/EN) | Pengunjung Umum |
|
||||||
|
| UC-09 | Memperbarui Data Beasiswa | Admin Prodi |
|
||||||
|
|
||||||
|
**Relasi antar UC:** UC-04 `<<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.
|
||||||
@@ -0,0 +1,337 @@
|
|||||||
|
# Software Requirements Specification (SRS)
|
||||||
|
## Pengembangan Website Statis Halaman Beasiswa
|
||||||
|
### Program Studi Informatika — Universitas Tanjungpura
|
||||||
|
|
||||||
|
| Field | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Nama Proyek | Pengembangan Website Statis Halaman Beasiswa |
|
||||||
|
| Kelompok | Ape Nama Team E |
|
||||||
|
| Versi | 1.1 |
|
||||||
|
| Tanggal | 01/05/2026 |
|
||||||
|
| Dosen | Izuardo Zulkarnain, S.Pd., M.T |
|
||||||
|
| Mata Kuliah | Proyek Perangkat Lunak — Semester Genap 2025/2026 |
|
||||||
|
|
||||||
|
**Anggota Tim:**
|
||||||
|
- Djapianus Thebrianto — Project Manager
|
||||||
|
- Derangga Aray Abimanyu — Backend Developer
|
||||||
|
- Destra Aulia Faza Ananda Putra — Frontend Developer
|
||||||
|
- Dian Satrio Prayogo — UI/UX Designer
|
||||||
|
- Jonathan Geral Nababan — Quality Assurance
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Pendahuluan
|
||||||
|
|
||||||
|
### 1.1 Tujuan Dokumen
|
||||||
|
|
||||||
|
SRS ini memberikan spesifikasi teknis dan fungsional untuk pengembangan Website Statis Beasiswa Informatika Universitas Tanjungpura. Dokumen ini memastikan:
|
||||||
|
|
||||||
|
- Pengembang membangun sistem dengan kode sederhana yang mudah di-maintain melalui mekanisme push-to-repo ke dedicated server prodi.
|
||||||
|
- Penyajian data beasiswa (nama & syarat umum) dilakukan melalui fitur **Pop-up** dengan akses langsung ke admin via WhatsApp dan Instagram kemahasiswaan UNTAN.
|
||||||
|
- Proyek mengalihkan alur data dari portal universitas/survei admin ke static page dengan **availability 24 jam**.
|
||||||
|
|
||||||
|
**Ditujukan kepada:** Tim Ape Nama Team E, Product Owner (dosen), dan Administrator Prodi.
|
||||||
|
|
||||||
|
### 1.2 Ruang Lingkup
|
||||||
|
|
||||||
|
Website statis single-page yang menjadi pusat informasi beasiswa, riset, dan pengembangan karir — terintegrasi dengan infrastruktur server Prodi Informatika UNTAN.
|
||||||
|
|
||||||
|
**Fitur yang dicakup:**
|
||||||
|
- Direktori beasiswa (mahasiswa aktif, alumni, S2 luar negeri, magang)
|
||||||
|
- Detail beasiswa via popup (nama + syarat umum)
|
||||||
|
- Data beasiswa dari web scraping sumber resmi
|
||||||
|
- Seksi testimoni penerima beasiswa (foto, profil, kutipan)
|
||||||
|
- Toggle bahasa Indonesia / Inggris (bilingual)
|
||||||
|
- CTA ke WhatsApp admin & Instagram kemahasiswaan UNTAN
|
||||||
|
|
||||||
|
**Yang TIDAK dicakup:**
|
||||||
|
- Formulir pendaftaran (diarahkan ke URL asli penyedia)
|
||||||
|
- Halaman admin berbasis web (update via push-to-repo)
|
||||||
|
- Database — data bersifat statis (JSON/JS di repositori)
|
||||||
|
|
||||||
|
### 1.3 Definisi & Akronim
|
||||||
|
|
||||||
|
| Istilah | Definisi |
|
||||||
|
|---|---|
|
||||||
|
| Native Website | Web dengan HTML, CSS, JS murni tanpa framework kompleks |
|
||||||
|
| Scraping | Pengambilan data otomatis dari website eksternal |
|
||||||
|
| Dedicated Server | Server milik Prodi Informatika UNTAN (Dev & Production) |
|
||||||
|
| Pop-up / Modal | Jendela overlay di atas halaman tanpa reload |
|
||||||
|
| CTA | Call-to-Action — tombol ajakan ke WhatsApp/Instagram |
|
||||||
|
| Availability | Kemampuan sistem beroperasi 24 jam |
|
||||||
|
| Puify | Platform SaaS otomatisasi deployment dari repo ke server |
|
||||||
|
|
||||||
|
### 1.4 Referensi
|
||||||
|
- IEEE Std 830-1998 — Software Requirements Specifications
|
||||||
|
- UU No. 27 Tahun 2022 tentang Perlindungan Data Pribadi (UU PDP)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Deskripsi Umum Sistem
|
||||||
|
|
||||||
|
### 2.1 Perspektif Produk
|
||||||
|
|
||||||
|
Produk baru berdiri sendiri sebagai **single static landing page** — bukan pengganti sistem lama. Website ini hadir sebagai transformasi digital dari penyebaran info beasiswa yang sebelumnya manual (portal, surat kabar mahasiswa, grup medsos) ke satu platform terpusat.
|
||||||
|
|
||||||
|
**Integrasi eksternal (satu arah):**
|
||||||
|
- Website resmi penyedia beasiswa (data via scraping)
|
||||||
|
- API Website Informatika UNTAN (konten artikel/berita)
|
||||||
|
- WhatsApp & Instagram Kemahasiswaan UNTAN (CTA)
|
||||||
|
|
||||||
|
Tidak ada antarmuka admin berbasis web. Semua update konten via **push ke GitHub → auto-deploy ke server** melalui Puify.
|
||||||
|
|
||||||
|
### 2.2 Fungsi Utama Sistem
|
||||||
|
|
||||||
|
| Kode | Fungsi | Deskripsi |
|
||||||
|
|---|---|---|
|
||||||
|
| F01 | Direktori Beasiswa | Grid kartu beasiswa aktif, S2, magang, penelitian dari berbagai sumber resmi |
|
||||||
|
| F02 | Filter & Pencarian | Saring berdasarkan kategori (Semua/Buka/Ditutup/Pemerintah/Swasta) + real-time search |
|
||||||
|
| F03 | Detail via Pop-up | Modal berisi nama, syarat umum, kontak, tautan sumber resmi |
|
||||||
|
| F04 | Testimoni | Foto, nama, nama beasiswa, kutipan dari penerima beasiswa |
|
||||||
|
| F05 | CTA Hubungi Admin | Tombol cepat ke WhatsApp admin & Instagram kemahasiswaan |
|
||||||
|
| F06 | Bilingual ID/EN | Toggle bahasa Indonesia ↔ Inggris tanpa reload |
|
||||||
|
| F07 | Update via Repository | Admin edit file data + push → halaman publik auto-update |
|
||||||
|
|
||||||
|
### 2.3 Kelas Pengguna
|
||||||
|
|
||||||
|
| Kelas | Karakteristik | Hak Akses |
|
||||||
|
|---|---|---|
|
||||||
|
| Pengunjung Umum | Mahasiswa aktif, alumni, masyarakat — tanpa login | Lihat konten, filter, search, buka popup, akses link eksternal |
|
||||||
|
| Admin Prodi | Staf/mahasiswa dengan kemampuan Git dasar | Edit file data di GitHub → push → deploy otomatis |
|
||||||
|
|
||||||
|
### 2.4 Lingkungan Operasional
|
||||||
|
|
||||||
|
| Komponen | Spesifikasi |
|
||||||
|
|---|---|
|
||||||
|
| Platform | Browser modern: Chrome ≥v100, Firefox ≥v100, Edge ≥v100, Safari ≥v15 |
|
||||||
|
| Server/Hosting | Dedicated server Prodi Informatika UNTAN (Dev & Production) via Puify |
|
||||||
|
| Database | Tidak ada — data di file statis JSON/JS di repositori |
|
||||||
|
| Bahasa Pemrograman | HTML5, CSS3 + Tailwind CSS, JavaScript ES6+ |
|
||||||
|
| Version Control | Git / GitHub |
|
||||||
|
| Koneksi | Min 1 Mbps (dasar), 10 Mbps (optimal) |
|
||||||
|
|
||||||
|
### 2.5 Asumsi & Ketergantungan
|
||||||
|
|
||||||
|
**Asumsi:**
|
||||||
|
- Pengunjung menggunakan browser modern dengan JS ES6+ dan CSS3 aktif
|
||||||
|
- Data beasiswa sudah diverifikasi admin sebelum dipublikasikan
|
||||||
|
- Perangkat pengunjung mendukung akses WhatsApp (app atau web)
|
||||||
|
|
||||||
|
**Ketergantungan:**
|
||||||
|
- Dedicated server Prodi UNTAN harus aktif untuk availability 24 jam
|
||||||
|
- Update data bergantung pada keaktifan admin melakukan push secara berkala
|
||||||
|
- Fitur berita bergantung pada stabilitas API Website Informatika UNTAN
|
||||||
|
- CTA bergantung pada keaktifan nomor WhatsApp & akun Instagram kemahasiswaan
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Kebutuhan Fungsional
|
||||||
|
|
||||||
|
### 3.1 Daftar Use Case
|
||||||
|
|
||||||
|
| UC ID | Nama Use Case | Aktor | Prioritas (MoSCoW) |
|
||||||
|
|---|---|---|---|
|
||||||
|
| UC-01 | Melihat Daftar Beasiswa | Pengunjung Umum | Must Have |
|
||||||
|
| UC-02 | Memfilter Beasiswa Berdasarkan Kategori | Pengunjung Umum | Must Have |
|
||||||
|
| UC-03 | Mencari Beasiswa | Pengunjung Umum | Must Have |
|
||||||
|
| UC-04 | Melihat Detail Beasiswa (Popup) | Pengunjung Umum | Must Have |
|
||||||
|
| UC-05 | Mengakses Sumber Informasi Beasiswa | Pengunjung Umum | Must Have |
|
||||||
|
| UC-06 | Menghubungi Admin via CTA | Pengunjung Umum | Must Have |
|
||||||
|
| UC-07 | Melihat Testimoni Penerima Beasiswa | Pengunjung Umum | Should Have |
|
||||||
|
| UC-08 | Mengganti Bahasa Tampilan (ID/EN) | Pengunjung Umum | Should Have |
|
||||||
|
| UC-09 | Memperbarui Data Beasiswa | Admin Prodi | Must Have |
|
||||||
|
|
||||||
|
### 3.2 Detail Use Case
|
||||||
|
|
||||||
|
#### UC-01: Melihat Daftar Beasiswa
|
||||||
|
| Field | Detail |
|
||||||
|
|---|---|
|
||||||
|
| Aktor | Pengunjung Umum |
|
||||||
|
| Precondition | Halaman berhasil dimuat di browser |
|
||||||
|
| Trigger | Pengunjung membuka URL atau scroll ke seksi Daftar Beasiswa |
|
||||||
|
| Main Flow | 1. Pengunjung buka URL halaman<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)
|
||||||
|
After Width: | Height: | Size: 7.4 MiB |
|
After Width: | Height: | Size: 848 KiB |
|
After Width: | Height: | Size: 755 KiB |
@@ -0,0 +1,81 @@
|
|||||||
|
// =====================================================
|
||||||
|
// API.js — Mekanisme Fetch Data Beasiswa (terpusat)
|
||||||
|
// -----------------------------------------------------
|
||||||
|
// Alur: coba API remote (Directus publik) → jika gagal,
|
||||||
|
// fallback ke berkas lokal `data/beasiswa.json`.
|
||||||
|
//
|
||||||
|
// Data remote dinormalisasi ke skema UI yang sama dengan
|
||||||
|
// data lokal agar filter.js dan popup.js tidak perlu berubah.
|
||||||
|
// =====================================================
|
||||||
|
|
||||||
|
(function (global) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const CONFIG = {
|
||||||
|
baseUrl: 'https://api.ifuntanhub.dev',
|
||||||
|
collection: '/items/beasiswa',
|
||||||
|
localSource: 'data/beasiswa.json',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Normalise status remote → nilai yang dipakai UI
|
||||||
|
function normalizeStatus(s) {
|
||||||
|
if (!s) return 'Ditutup';
|
||||||
|
const lower = s.toLowerCase();
|
||||||
|
if (lower === 'dibuka' || lower === 'buka') return 'Buka';
|
||||||
|
if (lower === 'ditutup') return 'Ditutup';
|
||||||
|
return s; // "Segera Dibuka" atau nilai lain: tampilkan apa adanya
|
||||||
|
}
|
||||||
|
|
||||||
|
// Petakan satu item Directus ke skema yang diharapkan filter.js / popup.js
|
||||||
|
function normalizeItem(item) {
|
||||||
|
const parts = [item.jenjang, item.negara, item.tipe_pendanaan].filter(Boolean);
|
||||||
|
return {
|
||||||
|
id: String(item.id),
|
||||||
|
nama: item.nama || '',
|
||||||
|
status: normalizeStatus(item.status),
|
||||||
|
kategori: item.tipe_pendanaan || '',
|
||||||
|
thumbnail: '',
|
||||||
|
deskripsi_singkat: parts.join(' · ') || 'Info beasiswa tersedia di sumber resmi.',
|
||||||
|
tanggal_buka: item.tanggal_buka || '',
|
||||||
|
tanggal_tutup: item.tenggat_waktu || '',
|
||||||
|
url_sumber: item.url || '',
|
||||||
|
syarat_umum: [],
|
||||||
|
kontak: '',
|
||||||
|
tags: parts,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchRemote() {
|
||||||
|
const res = await fetch(CONFIG.baseUrl + CONFIG.collection);
|
||||||
|
if (!res.ok) throw new Error(`API merespons HTTP ${res.status}`);
|
||||||
|
const json = await res.json();
|
||||||
|
return (json.data || []).map(normalizeItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchLocal() {
|
||||||
|
const res = await fetch(CONFIG.localSource);
|
||||||
|
if (!res.ok) throw new Error(`Gagal memuat data lokal (HTTP ${res.status})`);
|
||||||
|
const json = await res.json();
|
||||||
|
return Array.isArray(json) ? json : json.data || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getBeasiswa() {
|
||||||
|
try {
|
||||||
|
// Prioritas: data lokal (lengkap: url_sumber, syarat_umum, kontak tersedia)
|
||||||
|
// Remote API digunakan hanya sebagai fallback jika data lokal tidak ditemukan.
|
||||||
|
const data = await fetchLocal();
|
||||||
|
return { data, source: 'local' };
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('[BeasiswaAPI] Data lokal gagal, fallback ke API remote:', err.message);
|
||||||
|
try {
|
||||||
|
const data = await fetchRemote();
|
||||||
|
return { data, source: 'remote' };
|
||||||
|
} catch (remoteErr) {
|
||||||
|
console.error('[BeasiswaAPI] Semua sumber gagal:', remoteErr.message);
|
||||||
|
throw remoteErr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
global.BeasiswaAPI = { config: CONFIG, getBeasiswa, fetchRemote, fetchLocal };
|
||||||
|
})(window);
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
// =====================================================
|
||||||
|
// carousel.js — Swiper.js Carousel Testimoni
|
||||||
|
// Touch-friendly, auto-play, responsive breakpoints
|
||||||
|
// + DUKUNGAN MULTI-BAHASA (ID/EN) Terintegrasi
|
||||||
|
// =====================================================
|
||||||
|
|
||||||
|
let testimoniSwiper = null;
|
||||||
|
let allTestimoniData = []; // Simpan data di memori agar tidak perlu loading ulang saat ganti bahasa
|
||||||
|
|
||||||
|
// 1. Variabel penampung bahasa yang terhubung otomatis dengan tombol ID/EN
|
||||||
|
let activeCarouselLang = localStorage.getItem('if_untan_lang') || document.documentElement.lang || 'id';
|
||||||
|
|
||||||
|
// 2. Tangkap event perubahan bahasa secara langsung dari localization.js
|
||||||
|
document.addEventListener('languageChanged', (e) => {
|
||||||
|
// Update bahasa
|
||||||
|
if (e.detail && e.detail.lang) {
|
||||||
|
activeCarouselLang = e.detail.lang;
|
||||||
|
} else if (typeof window.getLang === 'function') {
|
||||||
|
activeCarouselLang = window.getLang();
|
||||||
|
} else {
|
||||||
|
activeCarouselLang = document.documentElement.lang || 'id';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jika data sudah ada, langsung cetak ulang testimoninya
|
||||||
|
if (allTestimoniData && allTestimoniData.length > 0) {
|
||||||
|
renderTestimoniSlides(allTestimoniData);
|
||||||
|
initSwiper(allTestimoniData.length); // Swiper harus di-restart agar animasinya tidak rusak
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Render slide testimoni ke dalam swiper-wrapper */
|
||||||
|
function renderTestimoniSlides(data) {
|
||||||
|
const wrapper = document.getElementById('testimonial-wrapper');
|
||||||
|
if (!wrapper) return;
|
||||||
|
|
||||||
|
const lang = activeCarouselLang; // Gunakan bahasa terbaru
|
||||||
|
|
||||||
|
wrapper.innerHTML = data.map(t => {
|
||||||
|
// Logika Pintar: Cek mode bahasa. Jika 'en' dan teks terjemahannya ada, pakai bahasa Inggris.
|
||||||
|
// Jika tidak, tetap tampilkan teks bahasa Indonesia (sebagai cadangan).
|
||||||
|
const beasiswaName = (lang === 'en' && t.nama_beasiswa_en) ? t.nama_beasiswa_en : t.nama_beasiswa;
|
||||||
|
const quote = (lang === 'en' && t.kutipan_en) ? t.kutipan_en : t.kutipan;
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="swiper-slide" style="height:auto;">
|
||||||
|
<div class="testimonial-card">
|
||||||
|
<img
|
||||||
|
src="${t.foto}"
|
||||||
|
alt="Foto ${t.nama}"
|
||||||
|
class="testimonial-avatar"
|
||||||
|
loading="lazy"
|
||||||
|
onerror="
|
||||||
|
this.style.display='none';
|
||||||
|
this.nextElementSibling.style.display='flex';
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="testimonial-avatar"
|
||||||
|
style="
|
||||||
|
display:none;
|
||||||
|
background:linear-gradient(135deg,#003150,#4a6bc5);
|
||||||
|
align-items:center;
|
||||||
|
justify-content:center;
|
||||||
|
color:white;
|
||||||
|
font-size:22px;
|
||||||
|
font-weight:700;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
${t.nama.charAt(0).toUpperCase()}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="testimonial-name">${t.nama}</div>
|
||||||
|
<div class="testimonial-beasiswa">${beasiswaName}</div>
|
||||||
|
<p class="testimonial-quote">"${quote}"</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Inisialisasi Swiper setelah slide di-render */
|
||||||
|
function initSwiper(count) {
|
||||||
|
// Bersihkan swiper lama jika ada (penting agar tidak menumpuk saat ganti bahasa)
|
||||||
|
if (testimoniSwiper) {
|
||||||
|
testimoniSwiper.destroy(true, true);
|
||||||
|
testimoniSwiper = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
testimoniSwiper = new Swiper('.testimonial-swiper', {
|
||||||
|
slidesPerView: 1,
|
||||||
|
spaceBetween: 24,
|
||||||
|
loop: count > 3,
|
||||||
|
centerInsufficientSlides: true,
|
||||||
|
autoplay: {
|
||||||
|
delay: 4500,
|
||||||
|
disableOnInteraction: false,
|
||||||
|
pauseOnMouseEnter: true,
|
||||||
|
},
|
||||||
|
pagination: {
|
||||||
|
el: '.testimonial-pagination',
|
||||||
|
clickable: true,
|
||||||
|
},
|
||||||
|
grabCursor: true,
|
||||||
|
breakpoints: {
|
||||||
|
640: { slidesPerView: 2 },
|
||||||
|
1024: { slidesPerView: 3 },
|
||||||
|
},
|
||||||
|
// Dukungan Aksesibilitas Dinamis (VoiceOver / Screen Reader)
|
||||||
|
a11y: {
|
||||||
|
prevSlideMessage: activeCarouselLang === 'en' ? 'Previous slide' : 'Slide sebelumnya',
|
||||||
|
nextSlideMessage: activeCarouselLang === 'en' ? 'Next slide' : 'Slide berikutnya',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Fetch data testimoni dan render */
|
||||||
|
async function initCarousel() {
|
||||||
|
const section = document.getElementById('testimonial');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('data/testimoni.json');
|
||||||
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
allTestimoniData = data; // Simpan ke variabel global
|
||||||
|
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
section?.classList.add('hidden');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTestimoniSlides(data);
|
||||||
|
requestAnimationFrame(() => initSwiper(data.length));
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
section?.classList.add('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', initCarousel);
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
// =====================================================
|
||||||
|
// counter.js — Animasi Counter Statistik
|
||||||
|
// Menggunakan IntersectionObserver + requestAnimationFrame
|
||||||
|
// =====================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animasi angka dari 0 ke nilai target
|
||||||
|
* @param {HTMLElement} el - Elemen yang menampilkan angka
|
||||||
|
* @param {number} target - Angka target
|
||||||
|
* @param {number} duration - Durasi animasi dalam ms
|
||||||
|
*/
|
||||||
|
function animateCounter(el, target, duration = 1800) {
|
||||||
|
const startTime = performance.now();
|
||||||
|
|
||||||
|
function update(currentTime) {
|
||||||
|
const elapsed = currentTime - startTime;
|
||||||
|
const progress = Math.min(elapsed / duration, 1);
|
||||||
|
|
||||||
|
// Ease out cubic — percepat di awal, lambat di akhir
|
||||||
|
const eased = 1 - Math.pow(1 - progress, 3);
|
||||||
|
const current = Math.round(eased * target);
|
||||||
|
|
||||||
|
el.textContent = current;
|
||||||
|
|
||||||
|
if (progress < 1) {
|
||||||
|
requestAnimationFrame(update);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(update);
|
||||||
|
}
|
||||||
|
|
||||||
|
function initCounters() {
|
||||||
|
const counters = document.querySelectorAll('[data-counter]');
|
||||||
|
if (!counters.length) return;
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (entry.isIntersecting && !entry.target.dataset.animated) {
|
||||||
|
entry.target.dataset.animated = 'true';
|
||||||
|
const target = parseInt(entry.target.dataset.counter, 10) || 0;
|
||||||
|
animateCounter(entry.target, target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ threshold: 0.6 }
|
||||||
|
);
|
||||||
|
|
||||||
|
counters.forEach(el => observer.observe(el));
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', initCounters);
|
||||||
@@ -0,0 +1,288 @@
|
|||||||
|
// =====================================================
|
||||||
|
// filter.js — Fetch JSON, Render Kartu, Filter Tab, Pencarian, Paginasi
|
||||||
|
// Filter dan search berjalan KUMULATIF
|
||||||
|
// + DUKUNGAN MULTI-BAHASA (ID/EN) Terintegrasi
|
||||||
|
// =====================================================
|
||||||
|
|
||||||
|
const ITEMS_PER_PAGE = 6;
|
||||||
|
|
||||||
|
let allData = [];
|
||||||
|
let activeFilter = 'semua';
|
||||||
|
let searchKeyword = '';
|
||||||
|
let currentPage = 1;
|
||||||
|
|
||||||
|
// 1. Variabel penampung bahasa yang terhubung otomatis dengan tombol ID/EN
|
||||||
|
let activeLang = localStorage.getItem('if_untan_lang') || document.documentElement.lang || 'id';
|
||||||
|
|
||||||
|
// 2. Tangkap event perubahan bahasa secara langsung dari localization.js
|
||||||
|
document.addEventListener('languageChanged', (e) => {
|
||||||
|
if (e.detail && e.detail.lang) {
|
||||||
|
activeLang = e.detail.lang; // Perbarui bahasa!
|
||||||
|
} else {
|
||||||
|
activeLang = document.documentElement.lang || 'id';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jika data sudah ada, langsung cetak ulang kartunya
|
||||||
|
if (allData.length > 0) {
|
||||||
|
renderCards();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. Format tanggal cerdas berdasarkan bahasa
|
||||||
|
function formatDate(str) {
|
||||||
|
if (!str) return '—';
|
||||||
|
const d = new Date(str);
|
||||||
|
if (isNaN(d.getTime())) return str;
|
||||||
|
|
||||||
|
return d.toLocaleDateString(activeLang === 'en' ? 'en-US' : 'id-ID', {
|
||||||
|
day: 'numeric', month: 'long', year: 'numeric'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Kamus mini untuk teks dinamis dari JSON (Negara & Kategori)
|
||||||
|
function translateDynamic(text, lang) {
|
||||||
|
if (!text || lang === 'id') return text;
|
||||||
|
|
||||||
|
const dict = {
|
||||||
|
'Amerika Serikat': 'United States',
|
||||||
|
'Swiss': 'Switzerland',
|
||||||
|
'Pemerintah': 'Government',
|
||||||
|
'Swasta': 'Private',
|
||||||
|
};
|
||||||
|
|
||||||
|
return dict[text] || text;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitize(str) {
|
||||||
|
return str.replace(/[<>&"']/g, c =>
|
||||||
|
({ '<': '<', '>': '>', '&': '&', '"': '"', "'": ''' }[c])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCard(bsw) {
|
||||||
|
const lang = activeLang; // Selalu gunakan bahasa aktif terbaru
|
||||||
|
|
||||||
|
// Terjemahan Tombol Status
|
||||||
|
let statusClass = 'status-btn-ditutup';
|
||||||
|
let statusLabel = lang === 'en' ? 'Closed' : 'Ditutup';
|
||||||
|
|
||||||
|
const statusLower = (bsw.status || '').toLowerCase();
|
||||||
|
if (statusLower === 'buka' || statusLower === 'dibuka') {
|
||||||
|
statusClass = 'status-btn-buka';
|
||||||
|
statusLabel = lang === 'en' ? 'Registration Open' : 'Pendaftaran Dibuka';
|
||||||
|
} else if (statusLower.includes('segera')) {
|
||||||
|
statusClass = 'status-btn-segera';
|
||||||
|
statusLabel = lang === 'en' ? 'Opening Soon' : 'Segera Dibuka';
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- KAMUS MINI UNTUK KARTU ---
|
||||||
|
const textMulai = lang === 'en' ? 'Start' : 'Mulai';
|
||||||
|
const textDeadline = lang === 'en' ? 'Deadline' : 'Tenggat';
|
||||||
|
const textSelengkapnya = lang === 'en' ? 'Read More' : 'Selengkapnya';
|
||||||
|
|
||||||
|
// Ambil pills (tags) dan terjemahkan negaranya
|
||||||
|
const tags = Array.isArray(bsw.tags) ? bsw.tags : [];
|
||||||
|
const jenjang = tags[0] || '';
|
||||||
|
const skipPatterns = /^(S1|S2|S3|D3|D4|Swasta|Pemerintah|Fully Funded|Partially Funded|Beasiswa Penuh|Beasiswa Sebagian|Prestasi)$/i;
|
||||||
|
const rawNegara = tags.slice(1).find(t => !skipPatterns.test(t)) || '';
|
||||||
|
const negara = translateDynamic(rawNegara, lang);
|
||||||
|
|
||||||
|
// Terjemahan Subtitle (Kategori Beasiswa)
|
||||||
|
const tipe = bsw.kategori || '';
|
||||||
|
const rawTipe = tipe.replace(/^beasiswa\s+/i, '');
|
||||||
|
let subtitle = '';
|
||||||
|
if (rawTipe) {
|
||||||
|
if (lang === 'en') {
|
||||||
|
const tipeEn = translateDynamic(rawTipe, lang);
|
||||||
|
subtitle = `${tipeEn} Scholarship`;
|
||||||
|
} else {
|
||||||
|
subtitle = `Beasiswa ${rawTipe}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ambil nama dari versi Inggris jika ada
|
||||||
|
const namaBsw = (lang === 'en' && bsw.nama_en) ? bsw.nama_en : bsw.nama;
|
||||||
|
|
||||||
|
const pillsHtml = [jenjang, negara]
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((t, i) => `<span class="card-pill ${i === 0 ? 'card-pill-level' : ''}">${t}</span>`)
|
||||||
|
.join('');
|
||||||
|
|
||||||
|
const article = document.createElement('article');
|
||||||
|
article.className = 'scholarship-card fade-in-up';
|
||||||
|
article.setAttribute('role', 'listitem');
|
||||||
|
article.setAttribute('data-id', bsw.id);
|
||||||
|
|
||||||
|
article.innerHTML = `
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title">${namaBsw}</h3>
|
||||||
|
${subtitle ? `<p class="card-subtitle">${subtitle}</p>` : ''}
|
||||||
|
${pillsHtml ? `<div class="card-pills">${pillsHtml}</div>` : ''}
|
||||||
|
<hr class="card-divider">
|
||||||
|
<div class="card-dates">
|
||||||
|
<div class="card-date-col">
|
||||||
|
<span class="card-date-label">${textMulai}</span>
|
||||||
|
<span class="card-date-value">${formatDate(bsw.tanggal_buka)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-date-col">
|
||||||
|
<span class="card-date-label">${textDeadline}</span>
|
||||||
|
<span class="card-date-value">${formatDate(bsw.tanggal_tutup)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="card-status-btn ${statusClass}" disabled>${statusLabel}</button>
|
||||||
|
<button
|
||||||
|
class="btn-selengkapnya"
|
||||||
|
onclick="openPopup('${bsw.id}')"
|
||||||
|
aria-label="Lihat detail beasiswa ${namaBsw}"
|
||||||
|
>${textSelengkapnya}</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
return article;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFiltered() {
|
||||||
|
const kw = searchKeyword.toLowerCase();
|
||||||
|
return allData.filter(bsw => {
|
||||||
|
|
||||||
|
// Logika Filter Tab S1 & Studi Lanjut
|
||||||
|
let matchFilter = false;
|
||||||
|
const tags = bsw.tags || [];
|
||||||
|
|
||||||
|
if (activeFilter === 'semua') {
|
||||||
|
matchFilter = true;
|
||||||
|
} else if (activeFilter === 's1') {
|
||||||
|
matchFilter = tags.some(t => t.includes('S1'));
|
||||||
|
} else if (activeFilter === 'studi_lanjut') {
|
||||||
|
matchFilter = tags.some(t => t.includes('S2') || t.includes('S3'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logika Pencarian Search Bar
|
||||||
|
const matchSearch = !kw ||
|
||||||
|
bsw.nama.toLowerCase().includes(kw) ||
|
||||||
|
bsw.deskripsi_singkat.toLowerCase().includes(kw) ||
|
||||||
|
tags.some(t => t.toLowerCase().includes(kw));
|
||||||
|
|
||||||
|
return matchFilter && matchSearch;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCards() {
|
||||||
|
const grid = document.getElementById('beasiswa-grid');
|
||||||
|
const pagCont = document.getElementById('pagination');
|
||||||
|
const filtered = getFiltered();
|
||||||
|
const lang = activeLang; // Gunakan bahasa terbaru
|
||||||
|
|
||||||
|
grid.innerHTML = '';
|
||||||
|
|
||||||
|
if (filtered.length === 0) {
|
||||||
|
const emptyTitle = lang === 'en' ? 'No scholarships found' : 'Tidak ada beasiswa ditemukan';
|
||||||
|
const emptyDesc = lang === 'en' ? 'Try adjusting your filters or search keywords' : 'Coba ubah filter atau kata kunci pencarian Anda';
|
||||||
|
|
||||||
|
grid.innerHTML = `
|
||||||
|
<div class="empty-state" style="grid-column:1/-1">
|
||||||
|
<i class="fas fa-search-minus"></i>
|
||||||
|
<p style="font-size:15px;font-weight:700;color:#94a3b8;margin-bottom:6px;">${emptyTitle}</p>
|
||||||
|
<p style="font-size:13px;color:#cbd5e1;">${emptyDesc}</p>
|
||||||
|
</div>`;
|
||||||
|
pagCont.innerHTML = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalPages = Math.ceil(filtered.length / ITEMS_PER_PAGE);
|
||||||
|
if (currentPage > totalPages) currentPage = totalPages;
|
||||||
|
const start = (currentPage - 1) * ITEMS_PER_PAGE;
|
||||||
|
const items = filtered.slice(start, start + ITEMS_PER_PAGE);
|
||||||
|
|
||||||
|
items.forEach((bsw, i) => {
|
||||||
|
const card = createCard(bsw);
|
||||||
|
card.style.animationDelay = `${i * 55}ms`;
|
||||||
|
grid.appendChild(card);
|
||||||
|
});
|
||||||
|
|
||||||
|
renderPagination(totalPages, pagCont);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPagination(total, container) {
|
||||||
|
container.innerHTML = '';
|
||||||
|
if (total <= 1) return;
|
||||||
|
|
||||||
|
const make = (content, page, disabled = false, isActive = false) => {
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.className = `page-btn${isActive ? ' active' : ''}`;
|
||||||
|
btn.innerHTML = content;
|
||||||
|
btn.disabled = disabled;
|
||||||
|
if (!disabled) {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
currentPage = page;
|
||||||
|
renderCards();
|
||||||
|
const dir = document.getElementById('directory');
|
||||||
|
if (dir) {
|
||||||
|
const top = dir.getBoundingClientRect().top + window.scrollY - 120;
|
||||||
|
window.scrollTo({ top, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return btn;
|
||||||
|
};
|
||||||
|
|
||||||
|
container.appendChild(make('<i class="fas fa-chevron-left" style="font-size:11px;"></i>', currentPage - 1, currentPage === 1));
|
||||||
|
for (let i = 1; i <= total; i++) {
|
||||||
|
container.appendChild(make(i, i, false, i === currentPage));
|
||||||
|
}
|
||||||
|
container.appendChild(make('<i class="fas fa-chevron-right" style="font-size:11px;"></i>', currentPage + 1, currentPage === total));
|
||||||
|
}
|
||||||
|
|
||||||
|
function initFilterTabs() {
|
||||||
|
document.querySelectorAll('.filter-tab').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
document.querySelectorAll('.filter-tab').forEach(b => {
|
||||||
|
b.classList.remove('active');
|
||||||
|
b.setAttribute('aria-selected', 'false');
|
||||||
|
});
|
||||||
|
btn.classList.add('active');
|
||||||
|
btn.setAttribute('aria-selected', 'true');
|
||||||
|
activeFilter = btn.dataset.filter;
|
||||||
|
currentPage = 1;
|
||||||
|
renderCards();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function initSearch() {
|
||||||
|
const input = document.getElementById('search-input');
|
||||||
|
if (!input) return;
|
||||||
|
input.addEventListener('input', () => {
|
||||||
|
searchKeyword = sanitize(input.value).trim();
|
||||||
|
currentPage = 1;
|
||||||
|
renderCards();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initDirectory() {
|
||||||
|
const grid = document.getElementById('beasiswa-grid');
|
||||||
|
try {
|
||||||
|
if (!window.BeasiswaAPI) throw new Error('Modul API.js (BeasiswaAPI) belum dimuat');
|
||||||
|
const { data, source } = await window.BeasiswaAPI.getBeasiswa();
|
||||||
|
allData = data;
|
||||||
|
window.beasiswaData = allData;
|
||||||
|
renderCards();
|
||||||
|
} catch (err) {
|
||||||
|
const lang = activeLang;
|
||||||
|
const errTitle = lang === 'en' ? 'Failed to load scholarship data' : 'Gagal memuat data beasiswa';
|
||||||
|
const errDesc = lang === 'en' ? 'Could not connect to the server. Please refresh or contact admin.' : 'Tidak dapat terhubung ke server. Silakan refresh atau hubungi admin.';
|
||||||
|
|
||||||
|
grid.innerHTML = `
|
||||||
|
<div class="empty-state" style="grid-column:1/-1">
|
||||||
|
<i class="fas fa-exclamation-triangle" style="color:#fca5a5;"></i>
|
||||||
|
<p style="font-size:15px;font-weight:700;color:#94a3b8;margin-bottom:6px;">${errTitle}</p>
|
||||||
|
<p style="font-size:13px;color:#cbd5e1;">${errDesc}</p>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
initFilterTabs();
|
||||||
|
initSearch();
|
||||||
|
initDirectory();
|
||||||
|
});
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
// =====================================================
|
||||||
|
// main.js — Inisialisasi Utama
|
||||||
|
// Sticky Navbar, Hamburger Menu, Smooth Scroll, AOS, Active Nav
|
||||||
|
// =====================================================
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
|
||||||
|
// ─── AOS (Animate on Scroll) ────────────────────────
|
||||||
|
AOS.init({
|
||||||
|
duration: 700,
|
||||||
|
easing: 'ease-out-cubic',
|
||||||
|
once: true,
|
||||||
|
offset: 50,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// ─── Sticky Navbar ──────────────────────────────────
|
||||||
|
// Navbar sudah fixed di CSS; tidak perlu mengubah warna karena
|
||||||
|
// warna primary sudah menjadi identitas halaman ini.
|
||||||
|
// Namun kita tambahkan shadow saat scroll untuk depth effect.
|
||||||
|
|
||||||
|
// PERBAIKAN: Menyesuaikan ID dengan yang ada di index.html (nav-wrapper)
|
||||||
|
const navbar = document.getElementById('nav-wrapper') || document.getElementById('navbar');
|
||||||
|
|
||||||
|
const handleScroll = () => {
|
||||||
|
if (!navbar) return; // PERBAIKAN: Mencegah error jika elemen tidak ada di halaman
|
||||||
|
|
||||||
|
if (window.scrollY > 10) {
|
||||||
|
navbar.style.boxShadow = '0 4px 20px rgba(0,0,0,0.25)';
|
||||||
|
} else {
|
||||||
|
navbar.style.boxShadow = 'none';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||||
|
handleScroll(); // initial call
|
||||||
|
|
||||||
|
|
||||||
|
// ─── Ticker Visibility (Muncul setelah Hero & Stats) ─
|
||||||
|
const ticker = document.querySelector('.ticker-wrapper');
|
||||||
|
const statsSection = document.getElementById('statistics');
|
||||||
|
|
||||||
|
const handleTickerVisibility = () => {
|
||||||
|
if (!ticker || !statsSection) return;
|
||||||
|
|
||||||
|
// Ticker muncul setelah melewati batas bawah elemen statistik
|
||||||
|
const statsBottom = statsSection.offsetTop + statsSection.offsetHeight;
|
||||||
|
|
||||||
|
if (window.scrollY > statsBottom) {
|
||||||
|
ticker.classList.add('show-ticker');
|
||||||
|
} else {
|
||||||
|
ticker.classList.remove('show-ticker');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('scroll', handleTickerVisibility, { passive: true });
|
||||||
|
// Call once on load with a slight delay to ensure layout is ready
|
||||||
|
setTimeout(handleTickerVisibility, 100);
|
||||||
|
|
||||||
|
|
||||||
|
// ─── Hamburger Menu (Mobile) ─────────────────────────
|
||||||
|
const mobileBtn = document.getElementById('mobile-menu-button');
|
||||||
|
const mobileMenu = document.getElementById('mobile-menu');
|
||||||
|
const closeMobileBtn = document.getElementById('close-mobile-menu');
|
||||||
|
|
||||||
|
const closeMobileMenu = () => {
|
||||||
|
mobileMenu?.classList.add('-translate-x-full');
|
||||||
|
mobileBtn?.setAttribute('aria-expanded', 'false');
|
||||||
|
};
|
||||||
|
|
||||||
|
// openMobileMenu handled by inline script in HTML; this covers close-on-scroll
|
||||||
|
closeMobileBtn?.addEventListener('click', closeMobileMenu);
|
||||||
|
|
||||||
|
|
||||||
|
// ─── Smooth Scroll untuk anchor links ──────────────
|
||||||
|
// Sesuai SDD: CSS scroll-behavior + JS fallback dengan offset
|
||||||
|
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||||
|
anchor.addEventListener('click', e => {
|
||||||
|
const href = anchor.getAttribute('href');
|
||||||
|
const target = document.querySelector(href);
|
||||||
|
if (!target) return;
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// PERBAIKAN: Mencegah error saat membaca offsetHeight jika navbar null
|
||||||
|
const navHeight = navbar ? navbar.offsetHeight : 110;
|
||||||
|
const top = target.getBoundingClientRect().top + window.scrollY - navHeight - 4;
|
||||||
|
window.scrollTo({ top, behavior: 'smooth' });
|
||||||
|
|
||||||
|
// Tutup mobile menu jika sedang terbuka
|
||||||
|
closeMobileMenu();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// ─── Active Nav Link via IntersectionObserver ──────
|
||||||
|
// Sesuai SDD fitur #4: highlight menu sesuai seksi yang terlihat
|
||||||
|
const sections = document.querySelectorAll('section[id]');
|
||||||
|
const navLinks = document.querySelectorAll('a.nav-link');
|
||||||
|
|
||||||
|
const sectionObserver = new IntersectionObserver(
|
||||||
|
entries => {
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
navLinks.forEach(link => {
|
||||||
|
link.classList.remove('active');
|
||||||
|
if (link.getAttribute('href') === `#${entry.target.id}`) {
|
||||||
|
link.classList.add('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ rootMargin: '-30% 0px -65% 0px' }
|
||||||
|
);
|
||||||
|
|
||||||
|
sections.forEach(s => sectionObserver.observe(s));
|
||||||
|
|
||||||
|
|
||||||
|
// ─── Back to Top visibility ────────────────────────
|
||||||
|
const backToTop = document.getElementById('back-to-top');
|
||||||
|
if (backToTop) {
|
||||||
|
window.addEventListener('scroll', () => {
|
||||||
|
if (window.scrollY > 400) {
|
||||||
|
backToTop.classList.remove('opacity-0', 'pointer-events-none');
|
||||||
|
backToTop.classList.add('opacity-100');
|
||||||
|
} else {
|
||||||
|
backToTop.classList.add('opacity-0', 'pointer-events-none');
|
||||||
|
backToTop.classList.remove('opacity-100');
|
||||||
|
}
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
backToTop.addEventListener('click', () => {
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Contact Us Modal (Hubungi Kami) ────────────────
|
||||||
|
const btnOpenContact = document.getElementById('btn-open-contact');
|
||||||
|
const contactOverlay = document.getElementById('contact-overlay');
|
||||||
|
const contactModal = document.getElementById('contact-modal');
|
||||||
|
const contactClose = document.getElementById('contact-close');
|
||||||
|
const contactBackdrop = document.getElementById('contact-backdrop');
|
||||||
|
|
||||||
|
const openContactModal = () => {
|
||||||
|
if (!contactOverlay) return;
|
||||||
|
contactOverlay.classList.remove('hidden');
|
||||||
|
contactOverlay.classList.add('flex');
|
||||||
|
// Animasi muncul
|
||||||
|
setTimeout(() => {
|
||||||
|
contactOverlay.classList.remove('opacity-0');
|
||||||
|
contactModal.classList.remove('scale-95');
|
||||||
|
contactModal.classList.add('scale-100');
|
||||||
|
}, 10);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeContactModal = () => {
|
||||||
|
if (!contactOverlay) return;
|
||||||
|
contactOverlay.classList.add('opacity-0');
|
||||||
|
contactModal.classList.remove('scale-100');
|
||||||
|
contactModal.classList.add('scale-95');
|
||||||
|
// Tunggu animasi selesai baru di-hide
|
||||||
|
setTimeout(() => {
|
||||||
|
contactOverlay.classList.remove('flex');
|
||||||
|
contactOverlay.classList.add('hidden');
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (btnOpenContact) btnOpenContact.addEventListener('click', openContactModal);
|
||||||
|
if (contactClose) contactClose.addEventListener('click', closeContactModal);
|
||||||
|
if (contactBackdrop) contactBackdrop.addEventListener('click', closeContactModal);
|
||||||
|
|
||||||
|
}); // end DOMContentLoaded
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
// =====================================================
|
||||||
|
// popup.js — Modal Detail Beasiswa
|
||||||
|
// Tutup via: tombol ×, klik overlay, tekan Escape
|
||||||
|
// + DUKUNGAN MULTI-BAHASA (ID/EN)
|
||||||
|
// =====================================================
|
||||||
|
|
||||||
|
const overlay = document.getElementById('popup-overlay');
|
||||||
|
const modal = document.getElementById('popup-modal');
|
||||||
|
const closeBtn = document.getElementById('popup-close');
|
||||||
|
const popupBody = document.getElementById('popup-content');
|
||||||
|
|
||||||
|
let currentOpenId = null; // Menyimpan status beasiswa mana yang sedang dibuka
|
||||||
|
|
||||||
|
// Bertanya langsung ke mesin utama (localization.js)
|
||||||
|
function getPopupLang() {
|
||||||
|
if (typeof window.getLang === 'function') {
|
||||||
|
return window.getLang();
|
||||||
|
}
|
||||||
|
return localStorage.getItem('if_untan_lang') || 'id';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format tanggal dinamis (ID / EN)
|
||||||
|
function fmtDate(str, lang) {
|
||||||
|
if (!str) return '—';
|
||||||
|
const d = new Date(str);
|
||||||
|
if (isNaN(d.getTime())) return str;
|
||||||
|
return d.toLocaleDateString(lang === 'en' ? 'en-US' : 'id-ID', {
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
year: 'numeric'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kamus Teks Statis Modal
|
||||||
|
const popupDict = {
|
||||||
|
id: {
|
||||||
|
req_empty: 'Syarat belum tersedia. Kunjungi sumber resmi.',
|
||||||
|
req_title: 'Syarat Umum',
|
||||||
|
open_date: 'Tanggal Buka',
|
||||||
|
close_date: 'Tanggal Tutup',
|
||||||
|
contact: 'Kontak',
|
||||||
|
more_info: 'Info Lebih Lanjut',
|
||||||
|
no_link: 'Tautan tidak tersedia. Silakan hubungi admin prodi.',
|
||||||
|
status_open: 'Buka',
|
||||||
|
status_closed: 'Ditutup'
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
req_empty: 'Requirements not available. Visit official source.',
|
||||||
|
req_title: 'General Requirements',
|
||||||
|
open_date: 'Open Date',
|
||||||
|
close_date: 'Closing Date',
|
||||||
|
contact: 'Contact',
|
||||||
|
more_info: 'More Information',
|
||||||
|
no_link: 'Link unavailable. Please contact department admin.',
|
||||||
|
status_open: 'Open',
|
||||||
|
status_closed: 'Closed'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Buka popup modal untuk beasiswa dengan ID tertentu
|
||||||
|
*/
|
||||||
|
function openPopup(id) {
|
||||||
|
currentOpenId = id;
|
||||||
|
const data = window.beasiswaData || [];
|
||||||
|
const bsw = data.find(b => b.id === id);
|
||||||
|
if (!bsw) return;
|
||||||
|
|
||||||
|
const lang = getPopupLang();
|
||||||
|
const d = popupDict[lang];
|
||||||
|
|
||||||
|
const isBuka = bsw.status === 'Buka' || bsw.status === 'Dibuka';
|
||||||
|
const badgeClass = isBuka ? 'badge-buka' : 'badge-ditutup';
|
||||||
|
|
||||||
|
// Terjemahkan Status & Kategori
|
||||||
|
const statusText = isBuka ? d.status_open : (lang === 'en' && (bsw.status === 'Ditutup' || bsw.status === 'Tutup') ? d.status_closed : bsw.status);
|
||||||
|
|
||||||
|
// Menerjemahkan Kategori (Misal: "Beasiswa Swasta" -> "Private Scholarship")
|
||||||
|
let kategoriText = bsw.kategori;
|
||||||
|
if (lang === 'en') {
|
||||||
|
if (bsw.kategori_en) {
|
||||||
|
kategoriText = bsw.kategori_en;
|
||||||
|
} else if (bsw.kategori.toLowerCase().includes('swasta')) {
|
||||||
|
kategoriText = 'Private Scholarship';
|
||||||
|
} else if (bsw.kategori.toLowerCase().includes('pemerintah')) {
|
||||||
|
kategoriText = 'Government Scholarship';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Teks Dinamis (Ambil _en dari JSON jika ada, jika tidak pakai ID)
|
||||||
|
const namaText = (lang === 'en' && bsw.nama_en) ? bsw.nama_en : bsw.nama;
|
||||||
|
const deskripsiText = (lang === 'en' && bsw.deskripsi_singkat_en) ? bsw.deskripsi_singkat_en : bsw.deskripsi_singkat;
|
||||||
|
const syaratData = (lang === 'en' && bsw.syarat_umum_en) ? bsw.syarat_umum_en : (bsw.syarat_umum || []);
|
||||||
|
|
||||||
|
// Bangun list syarat umum
|
||||||
|
const syaratItems = syaratData
|
||||||
|
.map(s => `
|
||||||
|
<li>
|
||||||
|
<i class="fas fa-check-circle"></i>
|
||||||
|
<span>${s}</span>
|
||||||
|
</li>`)
|
||||||
|
.join('');
|
||||||
|
|
||||||
|
const syaratHTML = syaratItems
|
||||||
|
? `<ul class="syarat-list">${syaratItems}</ul>`
|
||||||
|
: `<p style="font-size:13px;color:#94a3b8;font-style:italic;">${d.req_empty}</p>`;
|
||||||
|
|
||||||
|
const hasUrl = bsw.url_sumber && bsw.url_sumber !== '#';
|
||||||
|
|
||||||
|
// Render konten popup dengan template Anda yang dipertahankan
|
||||||
|
popupBody.innerHTML = `
|
||||||
|
<div class="popup-body">
|
||||||
|
<div class="popup-kategori-row">
|
||||||
|
<span class="badge-status ${badgeClass}">${statusText}</span>
|
||||||
|
<span class="popup-kategori">${kategoriText}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 id="popup-title" class="popup-title">${namaText}</h2>
|
||||||
|
|
||||||
|
<p class="popup-desc">${deskripsiText}</p>
|
||||||
|
|
||||||
|
<div class="popup-section-title">
|
||||||
|
<i class="fas fa-list-check"></i>
|
||||||
|
${d.req_title}
|
||||||
|
</div>
|
||||||
|
${syaratHTML}
|
||||||
|
|
||||||
|
<div class="popup-periode">
|
||||||
|
<div class="popup-periode-item">
|
||||||
|
<label>${d.open_date}</label>
|
||||||
|
<span>${fmtDate(bsw.tanggal_buka, lang)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="popup-periode-item">
|
||||||
|
<label>${d.close_date}</label>
|
||||||
|
<span style="${!isBuka ? 'color:#EF4444;' : ''}">${fmtDate(bsw.tanggal_tutup, lang)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${bsw.kontak ? `
|
||||||
|
<div class="popup-kontak">
|
||||||
|
<i class="fas fa-phone-alt"></i>
|
||||||
|
<div>
|
||||||
|
<div style="font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;margin-bottom:2px;">${d.contact}</div>
|
||||||
|
${bsw.kontak}
|
||||||
|
</div>
|
||||||
|
</div>` : ''}
|
||||||
|
|
||||||
|
${hasUrl
|
||||||
|
? `<a
|
||||||
|
href="${bsw.url_sumber}"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="popup-cta"
|
||||||
|
aria-label="${d.more_info} ${bsw.nama}"
|
||||||
|
>
|
||||||
|
${d.more_info}
|
||||||
|
<i class="fas fa-external-link-alt" style="font-size:12px;"></i>
|
||||||
|
</a>`
|
||||||
|
: `<button class="popup-cta disabled" disabled aria-disabled="true">
|
||||||
|
${d.more_info}
|
||||||
|
<i class="fas fa-external-link-alt" style="font-size:12px;"></i>
|
||||||
|
</button>
|
||||||
|
<p style="text-align:center;font-size:11px;color:#94a3b8;margin-top:8px;">
|
||||||
|
${d.no_link}
|
||||||
|
</p>`
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Tampilkan overlay
|
||||||
|
overlay.classList.add('show');
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
|
||||||
|
// Set focus ke modal untuk aksesibilitas
|
||||||
|
setTimeout(() => modal.focus(), 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tutup popup modal */
|
||||||
|
function closePopup() {
|
||||||
|
currentOpenId = null;
|
||||||
|
overlay.classList.remove('show');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Event Listeners untuk menutup popup ===
|
||||||
|
closeBtn?.addEventListener('click', closePopup);
|
||||||
|
document.getElementById('popup-backdrop')?.addEventListener('click', closePopup);
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Escape' && overlay.classList.contains('show')) {
|
||||||
|
closePopup();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Jika bahasa diganti SAAT popup sedang terbuka, cetak ulang isinya!
|
||||||
|
document.addEventListener('languageChanged', () => {
|
||||||
|
if (currentOpenId && overlay.classList.contains('show')) {
|
||||||
|
openPopup(currentOpenId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.openPopup = openPopup;
|
||||||
|
window.closePopup = closePopup;
|
||||||
@@ -0,0 +1,285 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "bsw-001",
|
||||||
|
"nama": "Boustany Foundation Scholarship, MBA - Harvard University",
|
||||||
|
"nama_en": "Boustany Foundation Scholarship, MBA - Harvard University",
|
||||||
|
"kategori": "Swasta",
|
||||||
|
"kategori_en": "Private",
|
||||||
|
"status": "Buka",
|
||||||
|
"thumbnail": "https://images.unsplash.com/photo-1541339907198-e08756dedf3f?w=400&q=80&auto=format&fit=crop",
|
||||||
|
"deskripsi_singkat": "Beasiswa bergengsi dari Boustany Foundation untuk program MBA di Harvard University, Amerika Serikat, bagi calon pemimpin bisnis dunia.",
|
||||||
|
"deskripsi_singkat_en": "A prestigious scholarship from the Boustany Foundation for the MBA program at Harvard University, United States, for future global business leaders.",
|
||||||
|
"syarat_umum": [
|
||||||
|
"Diterima atau mendaftar di program MBA Harvard Business School",
|
||||||
|
"Memiliki rekam jejak prestasi akademik dan profesional yang luar biasa",
|
||||||
|
"Menunjukkan potensi kepemimpinan dan kontribusi internasional",
|
||||||
|
"Melengkapi aplikasi beasiswa secara online melalui situs resmi Boustany Foundation"
|
||||||
|
],
|
||||||
|
"syarat_umum_en": [
|
||||||
|
"Accepted or applying to the MBA program at Harvard Business School",
|
||||||
|
"Have an exceptional track record of academic and professional achievements",
|
||||||
|
"Demonstrate leadership potential and international contribution",
|
||||||
|
"Complete the scholarship application online through the official Boustany Foundation website"
|
||||||
|
],
|
||||||
|
"kontak": "Boustany Foundation — boustany-foundation.com",
|
||||||
|
"tanggal_buka": "2026-05-21",
|
||||||
|
"tanggal_tutup": "2027-05-31",
|
||||||
|
"url_sumber": "https://www.schoters.com/id/beasiswa/boustany-foundation-scholarship-mba-harvard-university-1",
|
||||||
|
"tags": ["S2", "Swasta", "Amerika Serikat", "Partially Funded", "MBA"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bsw-002",
|
||||||
|
"nama": "La Trobe University - La Trobe High Achiever Scholarship (S1)",
|
||||||
|
"kategori": "Swasta",
|
||||||
|
"kategori_en": "Private",
|
||||||
|
"status": "Buka",
|
||||||
|
"thumbnail": "https://images.unsplash.com/photo-1523240795612-9a054b0db644?w=400&q=80&auto=format&fit=crop",
|
||||||
|
"deskripsi_singkat": "Beasiswa dari La Trobe University Australia untuk mahasiswa internasional jenjang S1 berprestasi tinggi yang ingin melanjutkan studi di Melbourne.",
|
||||||
|
"deskripsi_singkat_en": "Scholarship from La Trobe University Australia for high-achieving international undergraduate students who wish to pursue their studies in Melbourne.",
|
||||||
|
"syarat_umum": [
|
||||||
|
"Mendaftar sebagai mahasiswa internasional jenjang S1 (Undergraduate) di La Trobe University",
|
||||||
|
"Memiliki nilai akademik yang sangat baik (High Achiever)",
|
||||||
|
"Tidak sedang menerima beasiswa penuh lainnya dari La Trobe University",
|
||||||
|
"Memenuhi persyaratan masuk dan kemampuan bahasa Inggris La Trobe University"
|
||||||
|
],
|
||||||
|
"syarat_umum_en": [
|
||||||
|
"Apply as an international undergraduate student at La Trobe University",
|
||||||
|
"Have an excellent academic record (High Achiever)",
|
||||||
|
"Not currently receiving any other full scholarship from La Trobe University",
|
||||||
|
"Meet La Trobe University's entry and English language requirements"
|
||||||
|
],
|
||||||
|
"kontak": "La Trobe University Admissions — latrobe.edu.au",
|
||||||
|
"tanggal_buka": "2023-09-29",
|
||||||
|
"tanggal_tutup": "2026-12-31",
|
||||||
|
"url_sumber": "https://www.schoters.com/id/beasiswa/la-trobe-university-la-trobe-high-achiever-scholarship-2",
|
||||||
|
"tags": ["S1", "Swasta", "Australia", "Partially Funded"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bsw-003",
|
||||||
|
"nama": "Charles Sturt University - International Student Support Scholarship",
|
||||||
|
"kategori": "Swasta",
|
||||||
|
"kategori_en": "Private",
|
||||||
|
"status": "Buka",
|
||||||
|
"thumbnail": "https://images.unsplash.com/photo-1560472354-b33ff0c44a43?w=400&q=80&auto=format&fit=crop",
|
||||||
|
"deskripsi_singkat": "Beasiswa dukungan dari Charles Sturt University bagi mahasiswa internasional jenjang S1 yang mendaftar di kampus yang berlokasi di New South Wales, Australia.",
|
||||||
|
"deskripsi_singkat_en": "Support scholarship from Charles Sturt University for international undergraduate students enrolling at a campus located in New South Wales, Australia.",
|
||||||
|
"syarat_umum": [
|
||||||
|
"Mendaftar sebagai mahasiswa internasional penuh waktu (full-time) jenjang S1",
|
||||||
|
"Memenuhi persyaratan penerimaan akademik Charles Sturt University",
|
||||||
|
"Memiliki kemampuan Bahasa Inggris yang memadai (IELTS/TOEFL)",
|
||||||
|
"Mendaftar melalui jalur penerimaan internasional resmi"
|
||||||
|
],
|
||||||
|
"syarat_umum_en": [
|
||||||
|
"Apply as a full-time international undergraduate student",
|
||||||
|
"Meet Charles Sturt University's academic admission requirements",
|
||||||
|
"Have adequate English language proficiency (IELTS/TOEFL)",
|
||||||
|
"Apply through the official international admission pathway"
|
||||||
|
],
|
||||||
|
"kontak": "Charles Sturt University International — csu.edu.au",
|
||||||
|
"tanggal_buka": "2026-05-21",
|
||||||
|
"tanggal_tutup": "2026-12-31",
|
||||||
|
"url_sumber": "https://www.schoters.com/id/beasiswa/charles-sturt-university-international-student-support-scholarship-3",
|
||||||
|
"tags": ["S1", "Swasta", "Australia", "Partially Funded"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bsw-004",
|
||||||
|
"nama": "La Trobe University - La Trobe High Achiever Scholarship (S2)",
|
||||||
|
"kategori": "Swasta",
|
||||||
|
"kategori_en": "Private",
|
||||||
|
"status": "Buka",
|
||||||
|
"thumbnail": "https://images.unsplash.com/photo-1529156069898-49953e39b3ac?w=400&q=80&auto=format&fit=crop",
|
||||||
|
"deskripsi_singkat": "Beasiswa dari La Trobe University Australia untuk mahasiswa internasional jenjang S2 (Postgraduate) dengan pencapaian akademik tinggi.",
|
||||||
|
"deskripsi_singkat_en": "Scholarship from La Trobe University Australia for international postgraduate students with high academic achievements.",
|
||||||
|
"syarat_umum": [
|
||||||
|
"Mendaftar sebagai mahasiswa internasional jenjang S2 di La Trobe University",
|
||||||
|
"Memiliki nilai akademik yang sangat baik di jenjang sarjana sebelumnya",
|
||||||
|
"Memenuhi persyaratan bahasa Inggris yang ditetapkan",
|
||||||
|
"Tidak sedang menerima beasiswa lain dari universitas yang sama"
|
||||||
|
],
|
||||||
|
"syarat_umum_en": [
|
||||||
|
"Apply as an international postgraduate student at La Trobe University",
|
||||||
|
"Have excellent academic grades in the previous undergraduate degree",
|
||||||
|
"Meet the specified English language requirements",
|
||||||
|
"Not currently receiving another scholarship from the same university"
|
||||||
|
],
|
||||||
|
"kontak": "La Trobe University Admissions — latrobe.edu.au",
|
||||||
|
"tanggal_buka": "2023-09-29",
|
||||||
|
"tanggal_tutup": "2026-12-31",
|
||||||
|
"url_sumber": "https://www.schoters.com/id/beasiswa/la-trobe-university-la-trobe-high-achiever-scholarship-4",
|
||||||
|
"tags": ["S2", "Swasta", "Australia", "Partially Funded"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bsw-005",
|
||||||
|
"nama": "ETH Zurich - Excellence Scholarship & Opportunity Programme (ESOP)",
|
||||||
|
"kategori": "Pemerintah",
|
||||||
|
"kategori_en": "Government",
|
||||||
|
"status": "Buka",
|
||||||
|
"thumbnail": "https://images.unsplash.com/photo-1497366754035-f200968a6e72?w=400&q=80&auto=format&fit=crop",
|
||||||
|
"deskripsi_singkat": "Beasiswa bergengsi dari ETH Zurich (salah satu universitas teknik terbaik dunia) untuk mahasiswa S2 internasional yang unggul secara akademik.",
|
||||||
|
"deskripsi_singkat_en": "Prestigious scholarship from ETH Zurich (one of the world's best engineering universities) for academically outstanding international master's students.",
|
||||||
|
"syarat_umum": [
|
||||||
|
"Mendaftar program Master (S2) di ETH Zurich",
|
||||||
|
"Lulus ujian penerimaan ETH Zurich atau memiliki setara gelar sarjana yang diakui",
|
||||||
|
"Menunjukkan prestasi akademik luar biasa (biasanya masuk 10% terbaik angkatan)",
|
||||||
|
"Melengkapi formulir aplikasi beasiswa ESOP yang terpisah dari aplikasi studi",
|
||||||
|
"Mendapatkan surat rekomendasi dari akademisi berwenang"
|
||||||
|
],
|
||||||
|
"syarat_umum_en": [
|
||||||
|
"Apply for a Master's program at ETH Zurich",
|
||||||
|
"Pass the ETH Zurich entrance exam or hold a recognized equivalent bachelor's degree",
|
||||||
|
"Demonstrate outstanding academic performance (usually top 10% of the cohort)",
|
||||||
|
"Complete the ESOP scholarship application form separately from the study application",
|
||||||
|
"Obtain a recommendation letter from authorized academics"
|
||||||
|
],
|
||||||
|
"kontak": "ETH Zurich Scholarship Office — ethz.ch",
|
||||||
|
"tanggal_buka": "2026-11-01",
|
||||||
|
"tanggal_tutup": "2026-11-30",
|
||||||
|
"url_sumber": "https://www.schoters.com/id/beasiswa/eth-zurich-excellence-scholarship-opportunity-programme-esop-5",
|
||||||
|
"tags": ["S2", "Pemerintah", "Swiss", "Fully Funded", "Prestasi"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bsw-006",
|
||||||
|
"nama": "STUST - Scholarship for International Students (S3, Fully Funded)",
|
||||||
|
"kategori": "Swasta",
|
||||||
|
"kategori_en": "Private",
|
||||||
|
"status": "Buka",
|
||||||
|
"thumbnail": "https://images.unsplash.com/photo-1580582932707-520aed937b7b?w=400&q=80&auto=format&fit=crop",
|
||||||
|
"deskripsi_singkat": "Beasiswa penuh (fully funded) dari Southern Taiwan University of Science and Technology untuk mahasiswa internasional jenjang S3 (Doktoral).",
|
||||||
|
"deskripsi_singkat_en": "Fully funded scholarship from Southern Taiwan University of Science and Technology for international doctoral students.",
|
||||||
|
"syarat_umum": [
|
||||||
|
"Mendaftar program Doktoral (S3) di STUST, Taiwan",
|
||||||
|
"Memiliki gelar magister (S2) yang diakui",
|
||||||
|
"Memenuhi persyaratan kemampuan bahasa Inggris atau Mandarin",
|
||||||
|
"Menyerahkan proposal penelitian yang relevan dengan bidang studi yang dipilih",
|
||||||
|
"Mendapatkan persetujuan dari calon pembimbing (supervisor) di STUST"
|
||||||
|
],
|
||||||
|
"syarat_umum_en": [
|
||||||
|
"Apply for a Doctoral program at STUST, Taiwan",
|
||||||
|
"Hold a recognized Master's degree",
|
||||||
|
"Meet the English or Mandarin language proficiency requirements",
|
||||||
|
"Submit a research proposal relevant to the chosen field of study",
|
||||||
|
"Obtain approval from a prospective supervisor at STUST"
|
||||||
|
],
|
||||||
|
"kontak": "STUST International Affairs Office — stust.edu.tw",
|
||||||
|
"tanggal_buka": "2026-09-15",
|
||||||
|
"tanggal_tutup": "2026-11-30",
|
||||||
|
"url_sumber": "https://www.schoters.com/id/beasiswa/southern-taiwan-university-of-science-and-technology-the-stust-scholarship-for-international-students-6",
|
||||||
|
"tags": ["S3", "Swasta", "Taiwan", "Fully Funded"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bsw-007",
|
||||||
|
"nama": "STUST - Scholarship for International Students (S2, Partially Funded)",
|
||||||
|
"kategori": "Swasta",
|
||||||
|
"kategori_en": "Private",
|
||||||
|
"status": "Buka",
|
||||||
|
"thumbnail": "https://images.unsplash.com/photo-1606761568499-6d2451b23c66?w=400&q=80&auto=format&fit=crop",
|
||||||
|
"deskripsi_singkat": "Beasiswa parsial (partially funded) dari Southern Taiwan University of Science and Technology untuk mahasiswa internasional jenjang S2 (Magister).",
|
||||||
|
"deskripsi_singkat_en": "Partially funded scholarship from Southern Taiwan University of Science and Technology for international master's students.",
|
||||||
|
"syarat_umum": [
|
||||||
|
"Mendaftar program Magister (S2) di STUST, Taiwan",
|
||||||
|
"Memiliki gelar sarjana (S1) yang diakui",
|
||||||
|
"Memenuhi persyaratan kemampuan bahasa Inggris atau Mandarin",
|
||||||
|
"Melengkapi berkas aplikasi secara online melalui portal resmi STUST",
|
||||||
|
"Menyertakan transkrip nilai dan surat motivasi"
|
||||||
|
],
|
||||||
|
"syarat_umum_en": [
|
||||||
|
"Apply for a Master's program at STUST, Taiwan",
|
||||||
|
"Hold a recognized Bachelor's degree",
|
||||||
|
"Meet the English or Mandarin language proficiency requirements",
|
||||||
|
"Complete the application file online through the official STUST portal",
|
||||||
|
"Include academic transcripts and a motivation letter"
|
||||||
|
],
|
||||||
|
"kontak": "STUST International Affairs Office — stust.edu.tw",
|
||||||
|
"tanggal_buka": "2026-09-15",
|
||||||
|
"tanggal_tutup": "2026-11-30",
|
||||||
|
"url_sumber": "https://www.schoters.com/id/beasiswa/southern-taiwan-university-of-science-and-technology-the-stust-scholarship-for-international-students-7",
|
||||||
|
"tags": ["S2", "Swasta", "Taiwan", "Partially Funded"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bsw-008",
|
||||||
|
"nama": "University of Melbourne - Melbourne International Undergraduate Scholarship",
|
||||||
|
"kategori": "Swasta",
|
||||||
|
"kategori_en": "Private",
|
||||||
|
"status": "Buka",
|
||||||
|
"thumbnail": "https://images.unsplash.com/photo-1504711434969-e33886168f5c?w=400&q=80&auto=format&fit=crop",
|
||||||
|
"deskripsi_singkat": "Beasiswa untuk mahasiswa internasional berprestasi yang mendaftar program S1 di University of Melbourne, salah satu universitas terbaik di Australia.",
|
||||||
|
"deskripsi_singkat_en": "Scholarship for outstanding international students applying for an undergraduate program at the University of Melbourne, one of Australia's best universities.",
|
||||||
|
"syarat_umum": [
|
||||||
|
"Merupakan mahasiswa internasional (bukan warga negara Australia atau New Zealand)",
|
||||||
|
"Mendaftar program sarjana (S1 / Undergraduate) di University of Melbourne",
|
||||||
|
"Memiliki hasil akademik yang sangat luar biasa di tingkat sekolah menengah atas",
|
||||||
|
"Memenuhi persyaratan kemampuan bahasa Inggris (IELTS minimal 6.5)",
|
||||||
|
"Beasiswa diberikan otomatis berdasarkan nilai akademik, tidak perlu aplikasi terpisah"
|
||||||
|
],
|
||||||
|
"syarat_umum_en": [
|
||||||
|
"Be an international student (not a citizen of Australia or New Zealand)",
|
||||||
|
"Apply for an undergraduate program at the University of Melbourne",
|
||||||
|
"Have an exceptional academic result at the high school level",
|
||||||
|
"Meet the English language proficiency requirements (minimum IELTS 6.5)",
|
||||||
|
"The scholarship is awarded automatically based on academic merit, no separate application required"
|
||||||
|
],
|
||||||
|
"kontak": "University of Melbourne Future Students — unimelb.edu.au",
|
||||||
|
"tanggal_buka": "2026-05-21",
|
||||||
|
"tanggal_tutup": "2026-10-31",
|
||||||
|
"url_sumber": "https://www.schoters.com/id/beasiswa/university-of-melbourne-melbourne-international-undergraduate-scholarship-8",
|
||||||
|
"tags": ["S1", "Swasta", "Australia", "Partially Funded", "Prestasi"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bsw-009",
|
||||||
|
"nama": "Charles Darwin University - CDU Global Merit Scholarship",
|
||||||
|
"kategori": "Swasta",
|
||||||
|
"kategori_en": "Private",
|
||||||
|
"status": "Buka",
|
||||||
|
"thumbnail": "https://images.unsplash.com/photo-1434030216411-0b793f4b4173?w=400&q=80&auto=format&fit=crop",
|
||||||
|
"deskripsi_singkat": "Beasiswa merit global dari Charles Darwin University (CDU) di Darwin, Australia, untuk mahasiswa internasional S1 yang memiliki rekam jejak akademik yang kuat.",
|
||||||
|
"deskripsi_singkat_en": "Global merit scholarship from Charles Darwin University (CDU) in Darwin, Australia, for international undergraduate students with a strong academic track record.",
|
||||||
|
"syarat_umum": [
|
||||||
|
"Mendaftar sebagai mahasiswa internasional baru (new international student) di CDU",
|
||||||
|
"Mendaftar untuk program sarjana (Undergraduate/S1) penuh waktu",
|
||||||
|
"Memiliki nilai ujian akhir sekolah yang baik (dikonversi ke skala CDU)",
|
||||||
|
"Memenuhi persyaratan bahasa Inggris (IELTS 6.0 atau setara)",
|
||||||
|
"Tidak sedang menerima potongan biaya kuliah lainnya dari CDU"
|
||||||
|
],
|
||||||
|
"syarat_umum_en": [
|
||||||
|
"Apply as a new international student at CDU",
|
||||||
|
"Apply for a full-time undergraduate program",
|
||||||
|
"Have a good high school final exam score (converted to CDU scale)",
|
||||||
|
"Meet the English language requirements (IELTS 6.0 or equivalent)",
|
||||||
|
"Not currently receiving any other tuition fee discount from CDU"
|
||||||
|
],
|
||||||
|
"kontak": "CDU International — cdu.edu.au",
|
||||||
|
"tanggal_buka": "2026-05-21",
|
||||||
|
"tanggal_tutup": "2026-10-09",
|
||||||
|
"url_sumber": "https://www.schoters.com/id/beasiswa/charles-darwin-university-cdu-global-merit-scholarship-9",
|
||||||
|
"tags": ["S1", "Swasta", "Australia", "Partially Funded"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bsw-010",
|
||||||
|
"nama": "Deakin University - Vice-Chancellor's International Scholarship",
|
||||||
|
"kategori": "Swasta",
|
||||||
|
"kategori_en": "Private",
|
||||||
|
"status": "Buka",
|
||||||
|
"thumbnail": "https://images.unsplash.com/photo-1541339907198-e08756dedf3f?w=400&q=80&auto=format&fit=crop",
|
||||||
|
"deskripsi_singkat": "Beasiswa bergengsi dari Wakil Rektor (Vice-Chancellor) Deakin University untuk mahasiswa internasional terbaik yang akan menempuh studi S1 atau S2 di Australia.",
|
||||||
|
"deskripsi_singkat_en": "Prestigious Vice-Chancellor's scholarship from Deakin University for the best international students pursuing an undergraduate or postgraduate degree in Australia.",
|
||||||
|
"syarat_umum": [
|
||||||
|
"Merupakan mahasiswa internasional (bukan warga negara Australia/New Zealand)",
|
||||||
|
"Mendaftar untuk program S1 (Undergraduate) atau S2 (Postgraduate Coursework) di Deakin",
|
||||||
|
"Memiliki rekam jejak akademik yang sangat baik dan konsisten",
|
||||||
|
"Memenuhi persyaratan masuk dan kemampuan bahasa Inggris Deakin University",
|
||||||
|
"Mengajukan aplikasi beasiswa sebelum tanggal penutupan yang ditetapkan"
|
||||||
|
],
|
||||||
|
"syarat_umum_en": [
|
||||||
|
"Be an international student (not an Australia/New Zealand citizen)",
|
||||||
|
"Apply for an undergraduate or postgraduate coursework program at Deakin",
|
||||||
|
"Have a consistent and excellent academic track record",
|
||||||
|
"Meet Deakin University's entry and English language requirements",
|
||||||
|
"Submit the scholarship application before the specified closing date"
|
||||||
|
],
|
||||||
|
"kontak": "Deakin University International — deakin.edu.au",
|
||||||
|
"tanggal_buka": "2026-05-21",
|
||||||
|
"tanggal_tutup": "2026-09-25",
|
||||||
|
"url_sumber": "https://www.schoters.com/id/beasiswa/deakin-university-vice-chancellor-s-international-scholarship-10",
|
||||||
|
"tags": ["S1 & S2", "S2", "Swasta", "Australia", "Partially Funded", "Prestasi"]
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "tsm-001",
|
||||||
|
"nama": "Violaine Gunawan",
|
||||||
|
"foto": "https://ui-avatars.com/api/?name=Violaine+Gunawan&background=111a2f&color=fff&size=200&rounded=true",
|
||||||
|
"nama_beasiswa": "BEASISWA BAKTI BCA 2026",
|
||||||
|
"nama_beasiswa_en": "BAKTI BCA SCHOLARSHIP 2026",
|
||||||
|
"kutipan": "Beasiswa ini sangat berdampak besar. Tantangannya menyeimbangkan kuliah, asisten praktikum, organisasi, dan projek besar beasiswa. Namun, relasi di kampus dan masyarakat jadi bertambah luas. Tips: Jangan takut mencoba, pahami fokus beasiswa, sesuaikan visi misi, dan aktiflah berorganisasi!",
|
||||||
|
"kutipan_en": "This scholarship has a massive impact. The challenge is balancing college, practicum assistant duties, organizations, and major scholarship projects. However, connections on campus and in society have expanded. Tip: Don't be afraid to try, understand the scholarship's focus, align your vision and mission, and be active in organizations!"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -1,15 +1,27 @@
|
|||||||
<!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">
|
||||||
<title>Group 10 – Ape Name Tim E | Informatika UNTAN</title>
|
<title>Group 10 – ApeNameTeamE | Informatika UNTAN</title>
|
||||||
<link rel="stylesheet" href="../../assets/global/output.css">
|
<link rel="stylesheet" href="../../assets/global/output.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/style.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>
|
|
||||||
|
<!-- AOS.js -->
|
||||||
|
<link href="https://unpkg.com/aos@2.3.4/dist/aos.css" rel="stylesheet" />
|
||||||
|
|
||||||
|
<!-- Swiper.js -->
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.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 +305,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 +1152,262 @@
|
|||||||
</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="ticker-wrapper" aria-label="Pengumuman beasiswa terbaru" role="marquee">
|
||||||
<div class="mb-8 pb-8 border-b border-slate-200">
|
<!-- Badge block -->
|
||||||
<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>
|
<div class="ticker-badge-block">
|
||||||
<h1 class="text-3xl lg:text-4xl font-bold text-slate-900 mb-2">Beasiswa</h1>
|
<span class="ticker-badge" aria-hidden="true">INFO TERBARU</span>
|
||||||
<p class="text-slate-500 text-sm">Kelompok 10 — Ape Name Tim E · Informatika UNTAN 2026</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Scroll area: inner wrapper berisi 2 copy teks, dianimasikan bersamaan -->
|
||||||
|
<div class="ticker-scroll-area">
|
||||||
|
<div class="ticker-inner" aria-live="off">
|
||||||
|
<span class="ticker-track">Selamat datang di antarmuka situs web Informatika terbaru!
|
||||||
|
| Pendaftaran
|
||||||
|
kolaborasi riset semester genap telah dibuka. | Pantau publikasi dosen dan
|
||||||
|
mahasiswa secara
|
||||||
|
real-time. | Beasiswa KIP Kuliah 2026 dibuka — Daftar sekarang!
|
||||||
|
| Beasiswa LPDP S2
|
||||||
|
tersedia hingga September 2026. </span>
|
||||||
|
<span class="ticker-track" aria-hidden="true">Selamat datang di antarmuka situs web
|
||||||
|
Informatika terbaru!
|
||||||
|
| Pendaftaran kolaborasi riset semester genap telah dibuka. |
|
||||||
|
Pantau publikasi dosen
|
||||||
|
dan mahasiswa secara real-time. | Beasiswa KIP Kuliah 2026 dibuka — Daftar
|
||||||
|
sekarang! |
|
||||||
|
Beasiswa LPDP S2 tersedia hingga September 2026. </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ================================================
|
||||||
|
HERO SECTION (#hero)
|
||||||
|
================================================ -->
|
||||||
|
<section id="hero" class="hero-section" aria-label="Header halaman beasiswa">
|
||||||
|
<div class="hero-bg"></div>
|
||||||
|
|
||||||
|
<div class="hero-content max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 w-full">
|
||||||
|
<!-- Breadcrumb -->
|
||||||
|
<nav class="breadcrumb" aria-label="Breadcrumb">
|
||||||
|
<a href="#" data-i18n="breadcrumb_home">Beranda</a>
|
||||||
|
<span class="sep"><i class="fas fa-chevron-right" style="font-size:7px;"></i></span>
|
||||||
|
<a href="#" data-i18n="ape_breadcrumb_profile">Pendidikan</a>
|
||||||
|
<span class="sep"><i class="fas fa-chevron-right" style="font-size:7px;"></i></span>
|
||||||
|
<span class="current" data-i18n="ape_breadcrumb_scholarship">Beasiswa</span>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="grid lg:grid-cols-2 gap-10 lg:gap-20 items-center">
|
||||||
|
|
||||||
|
<!-- Left: Text Content -->
|
||||||
|
<div data-aos="fade-right" data-aos-duration="700">
|
||||||
|
<!-- Badge (outline style seperti Figma) -->
|
||||||
|
<div class="hero-badge">
|
||||||
|
<span data-i18n="ape_hero_badge">PROGRAM BEASISWA</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- H1 -->
|
||||||
|
<h1 class="hero-title" data-i18n-html="ape_hero_title">
|
||||||
|
Beasiswa Studi &<br>Penelitian
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<!-- Subtitle -->
|
||||||
|
<p class="hero-subtitle" data-i18n="ape_hero_subtitle">
|
||||||
|
Teknik Informatika Universitas Tanjungpura
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Description (sesuai teks Figma) -->
|
||||||
|
<p class="hero-desc" data-i18n="ape_hero_desc">
|
||||||
|
Temukan berbagai peluang beasiswa untuk mendukung perjalanan akademik dan penelitian Anda.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- CTA Button (kuning, teks gelap — sesuai Figma) -->
|
||||||
|
<a href="#directory" class="hero-cta" aria-label="Lihat daftar beasiswa">
|
||||||
|
<span data-i18n="ape_hero_cta">Lihat Daftar Beasiswa</span>
|
||||||
|
<i class="fas fa-arrow-right"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right: Hero Image -->
|
||||||
|
<div class="hidden lg:block" data-aos="fade-left" data-aos-delay="150"
|
||||||
|
data-aos-duration="700">
|
||||||
|
<div class="hero-image-wrap">
|
||||||
|
<img src="assets/images/hero/mahasiswa.png"
|
||||||
|
alt="Mahasiswa Informatika UNTAN berprestasi" width="480" height="400"
|
||||||
|
onerror="this.src='https://images.unsplash.com/photo-1523050854058-8df90110c9f1?w=500&q=80'">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ================================================
|
||||||
|
STATISTIK COUNTER (#statistics)
|
||||||
|
================================================ -->
|
||||||
|
<section id="statistics" class="stats-section" aria-label="Statistik penerima beasiswa">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="grid grid-cols-3 divide-x divide-white/20">
|
||||||
|
|
||||||
|
<div class="stat-item" data-aos="fade-up" data-aos-delay="0">
|
||||||
|
<div class="stat-number" data-counter="20" id="ctr-total">0</div>
|
||||||
|
<div class="stat-label" data-i18n-html="ape_stats_total">
|
||||||
|
Total Mahasiswa Penerima<br>Beasiswa Semester Ini
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat-item" data-aos="fade-up" data-aos-delay="100">
|
||||||
|
<div class="stat-number" data-counter="12" id="ctr-s1">0</div>
|
||||||
|
<div class="stat-label" data-i18n-html="ape_stats_s1">
|
||||||
|
Total Penerima Beasiswa<br>Khusus S1
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat-item" data-aos="fade-up" data-aos-delay="200">
|
||||||
|
<div class="stat-number" data-counter="8" id="ctr-studi">0</div>
|
||||||
|
<div class="stat-label" data-i18n-html="ape_stats_studi">
|
||||||
|
Total Penerima Beasiswa<br>Studi Lanjut
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ================================================
|
||||||
|
DIREKTORI BEASISWA (#directory)
|
||||||
|
================================================ -->
|
||||||
|
<section id="directory" class="py-16 bg-white" aria-label="Direktori beasiswa">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
|
||||||
|
<!-- Section Header -->
|
||||||
|
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-6 mb-10">
|
||||||
|
<div data-aos="fade-right">
|
||||||
|
<!-- Label Badge "DAFTAR LENGKAP" -->
|
||||||
|
<div class="section-badge mb-3">
|
||||||
|
<span data-i18n="ape_dir_label">DAFTAR LENGKAP</span>
|
||||||
|
</div>
|
||||||
|
<h2 class="section-title" data-i18n="ape_dir_title">Daftar Beasiswa</h2>
|
||||||
|
<p class="section-desc" data-i18n="ape_dir_desc">
|
||||||
|
Temukan beragam beasiswa dari berbagai sumber resmi yang tersedia untuk mahasiswa aktif.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Filter & Search Row (UC-02, UC-03) -->
|
||||||
|
<div class="flex flex-col lg:flex-row gap-4 mb-8" data-aos="fade-up" data-aos-delay="50">
|
||||||
|
<!-- Filter Tabs -->
|
||||||
|
<div class="filter-tabs flex-1" id="filter-tabs" role="tablist"
|
||||||
|
aria-label="Filter beasiswa">
|
||||||
|
<button class="filter-tab active" data-filter="semua" role="tab" aria-selected="true"
|
||||||
|
data-i18n="filter_all">Semua</button>
|
||||||
|
<button class="filter-tab" data-filter="s1" role="tab" aria-selected="false"
|
||||||
|
data-i18n="ape_filter_s1">S1</button>
|
||||||
|
<button class="filter-tab" data-filter="studi_lanjut" role="tab" aria-selected="false"
|
||||||
|
data-i18n="ape_filter_studi">Studi Lanjut</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Real-Time Search -->
|
||||||
|
<div class="flex flex-col sm:flex-row gap-3 flex-shrink-0">
|
||||||
|
<div class="search-wrapper w-full sm:w-64">
|
||||||
|
<i class="fas fa-search search-icon" aria-hidden="true"></i>
|
||||||
|
<input type="search" id="search-input" class="search-input"
|
||||||
|
placeholder="Cari beasiswa..." data-i18n-placeholder="ape_search_placeholder"
|
||||||
|
aria-label="Cari beasiswa" autocomplete="off" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Cards Grid (rendered by filter.js) -->
|
||||||
|
<div id="beasiswa-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6" role="list"
|
||||||
|
aria-label="Daftar kartu beasiswa">
|
||||||
|
<!-- Skeleton loading state -->
|
||||||
|
<div class="col-span-full flex flex-col items-center justify-center py-20 text-gray-300">
|
||||||
|
<i class="fas fa-spinner fa-spin text-4xl mb-4"></i>
|
||||||
|
<p class="text-sm font-medium">Memuat data beasiswa...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination (rendered by filter.js) -->
|
||||||
|
<div id="pagination" class="pagination-container" role="navigation"
|
||||||
|
aria-label="Navigasi halaman beasiswa"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ================================================
|
||||||
|
POPUP MODAL DETAIL BEASISWA (UC-04, UC-05)
|
||||||
|
================================================ -->
|
||||||
|
<div id="popup-overlay" role="dialog" aria-modal="true" aria-labelledby="popup-title">
|
||||||
|
<!-- Backdrop (klik untuk tutup - UC-04) -->
|
||||||
|
<div id="popup-backdrop" class="popup-backdrop"></div>
|
||||||
|
|
||||||
|
<!-- Modal Card -->
|
||||||
|
<div id="popup-modal" class="popup-modal" tabindex="-1">
|
||||||
|
<!-- Close Button (×) -->
|
||||||
|
<button id="popup-close" class="popup-close" aria-label="Tutup detail beasiswa">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Content (rendered by popup.js) -->
|
||||||
|
<div id="popup-content"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ================================================
|
||||||
|
TESTIMONI (#testimonial) — UC-07
|
||||||
|
================================================ -->
|
||||||
|
<section id="testimonial" class="testimonials-section" aria-label="Testimoni penerima beasiswa">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="text-center mb-12" data-aos="fade-up">
|
||||||
|
<h2 class="section-title text-center" data-i18n="ape_testimonial_title">Apa Kata Mereka?</h2>
|
||||||
|
<p class="section-desc mx-auto text-center" data-i18n="ape_testimonial_desc">
|
||||||
|
Cerita nyata dari mahasiswa yang telah meraih beasiswa
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Swiper Carousel -->
|
||||||
|
<div class="swiper testimonial-swiper" data-aos="fade-up" data-aos-delay="100">
|
||||||
|
<div class="swiper-wrapper pb-14" id="testimonial-wrapper">
|
||||||
|
<!-- Rendered by carousel.js -->
|
||||||
|
</div>
|
||||||
|
<div class="swiper-pagination testimonial-pagination"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ================================================
|
||||||
|
CTA SECTION (#cta) — UC-06
|
||||||
|
================================================ -->
|
||||||
|
<section id="cta" class="cta-section" aria-label="Hubungi admin beasiswa">
|
||||||
|
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="cta-card" data-aos="zoom-in" data-aos-duration="600">
|
||||||
|
<h2 class="cta-title" data-i18n="ape_cta_title">
|
||||||
|
Ingin Mengetahui Info Lebih Lanjut?
|
||||||
|
</h2>
|
||||||
|
<p class="cta-desc" data-i18n="ape_cta_desc">
|
||||||
|
Bersama kami membangun sumber daya manusia unggul melalui program beasiswa dan
|
||||||
|
kolaborasi riset
|
||||||
|
</p>
|
||||||
|
<div class="cta-buttons">
|
||||||
|
<!-- Scroll ke direktori -->
|
||||||
|
<a href="#directory" class="cta-btn-primary" aria-label="Lihat daftar beasiswa">
|
||||||
|
<span data-i18n="ape_cta_btn1">Daftar Beasiswa</span>
|
||||||
|
</a>
|
||||||
|
<!-- Kontak CTA -->
|
||||||
|
<a id="btn-open-contact" class="cta-btn-secondary" aria-label="Hubungi admin" href="https://wa.me/+6285171655161">
|
||||||
|
<span data-i18n="ape_cta_btn2">Hubungi Kami</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<div class="container mx-auto px-4 lg:px-8 py-12">
|
||||||
<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>
|
|
||||||
<p>Gunakan paragraf, list, tabel, gambar, dan elemen konten lainnya.</p>
|
|
||||||
<!-- ================= END: BAGIAN YANG DIEDIT KELOMPOK ================= -->
|
<!-- ================= END: BAGIAN YANG DIEDIT KELOMPOK ================= -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1215,11 +1472,23 @@
|
|||||||
</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>
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/aos@2.3.4/dist/aos.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
|
||||||
|
|
||||||
|
<script src="../../assets/global/localization.js"></script>
|
||||||
|
|
||||||
|
<script src="assets/js/counter.js"></script>
|
||||||
|
<script type="module" src="assets/js/API.js"></script>
|
||||||
|
<script src="assets/js/popup.js"></script>
|
||||||
|
<script src="assets/js/filter.js"></script>
|
||||||
|
<script src="assets/js/carousel.js"></script>
|
||||||
|
<script src="assets/js/main.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
const menuItems = document.querySelectorAll('[data-menu]');
|
const menuItems = document.querySelectorAll('[data-menu]');
|
||||||
@@ -1312,27 +1581,27 @@
|
|||||||
})();
|
})();
|
||||||
</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>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
{
|
||||||
|
"source": "https://www.schoters.com/id/beasiswa",
|
||||||
|
"title": "Kalender Beasiswa - Schoters",
|
||||||
|
"description": "Kalender dan informasi beasiswa terlengkap dari semua jenjang S1, S2, & S3 di setiap negara dan kampus impian beserta persyaratan dan proses pendaftarannya",
|
||||||
|
"filters": {
|
||||||
|
"jenjang": [
|
||||||
|
{ "level": "D2", "count": 2 },
|
||||||
|
{ "level": "D3", "count": 6 },
|
||||||
|
{ "level": "Non-Degree", "count": 20 },
|
||||||
|
{ "level": "S1", "count": 648 },
|
||||||
|
{ "level": "S2", "count": 683 },
|
||||||
|
{ "level": "S3", "count": 362 }
|
||||||
|
],
|
||||||
|
"tipe_pendanaan": [
|
||||||
|
{ "type": "Fully Funded", "count": 809 },
|
||||||
|
{ "type": "Partially Funded", "count": 590 }
|
||||||
|
],
|
||||||
|
"tipe_beasiswa": [
|
||||||
|
{ "type": "Company/Institution Scholarship", "count": 237 },
|
||||||
|
{ "type": "Government Scholarship", "count": 459 },
|
||||||
|
{ "type": "University Scholarship", "count": 830 }
|
||||||
|
],
|
||||||
|
"negara": [
|
||||||
|
{ "country": "Australia", "count": 161 },
|
||||||
|
{ "country": "Austria", "count": 19 },
|
||||||
|
{ "country": "Azerbaijan", "count": 6 },
|
||||||
|
{ "country": "Belgium", "count": 38 },
|
||||||
|
{ "country": "Brazil", "count": 2 },
|
||||||
|
{ "country": "Brunei", "count": 5 },
|
||||||
|
{ "country": "Canada", "count": 46 },
|
||||||
|
{ "country": "China", "count": 114 },
|
||||||
|
{ "country": "Czech Republic", "count": 2 },
|
||||||
|
{ "country": "Denmark", "count": 13 },
|
||||||
|
{ "country": "Egypt", "count": 3 },
|
||||||
|
{ "country": "Estonia", "count": 2 },
|
||||||
|
{ "country": "Finland", "count": 11 },
|
||||||
|
{ "country": "France", "count": 20 },
|
||||||
|
{ "country": "Germany", "count": 46 },
|
||||||
|
{ "country": "Hong Kong", "count": 24 },
|
||||||
|
{ "country": "Hungary", "count": 5 },
|
||||||
|
{ "country": "India", "count": 2 },
|
||||||
|
{ "country": "Indonesia", "count": 35 },
|
||||||
|
{ "country": "Ireland", "count": 8 },
|
||||||
|
{ "country": "Italy", "count": 19 },
|
||||||
|
{ "country": "Japan", "count": 254 },
|
||||||
|
{ "country": "Kazakhstan", "count": 3 },
|
||||||
|
{ "country": "Latvia", "count": 3 },
|
||||||
|
{ "country": "Libya", "count": 2 },
|
||||||
|
{ "country": "Luxembourg", "count": 1 },
|
||||||
|
{ "country": "Macau", "count": 6 },
|
||||||
|
{ "country": "Malaysia", "count": 12 },
|
||||||
|
{ "country": "Morocco", "count": 2 },
|
||||||
|
{ "country": "Netherlands", "count": 21 },
|
||||||
|
{ "country": "New Zealand", "count": 22 },
|
||||||
|
{ "country": "Pakistan", "count": 1 },
|
||||||
|
{ "country": "Philippines", "count": 2 },
|
||||||
|
{ "country": "Poland", "count": 9 },
|
||||||
|
{ "country": "Portugal", "count": 4 },
|
||||||
|
{ "country": "Qatar", "count": 6 },
|
||||||
|
{ "country": "Romania", "count": 9 },
|
||||||
|
{ "country": "Russia", "count": 6 },
|
||||||
|
{ "country": "Saudi Arabia", "count": 12 },
|
||||||
|
{ "country": "Scotland", "count": 2 },
|
||||||
|
{ "country": "Serbia", "count": 3 },
|
||||||
|
{ "country": "Singapore", "count": 49 },
|
||||||
|
{ "country": "Slovakia", "count": 1 },
|
||||||
|
{ "country": "South Korea", "count": 109 },
|
||||||
|
{ "country": "Spain", "count": 12 },
|
||||||
|
{ "country": "Sweden", "count": 11 },
|
||||||
|
{ "country": "Switzerland", "count": 19 },
|
||||||
|
{ "country": "Taiwan", "count": 112 },
|
||||||
|
{ "country": "Thailand", "count": 38 },
|
||||||
|
{ "country": "Turkey", "count": 11 },
|
||||||
|
{ "country": "United Arab Emirates", "count": 67 },
|
||||||
|
{ "country": "United Kingdom", "count": 76 },
|
||||||
|
{ "country": "United States", "count": 55 },
|
||||||
|
{ "country": "Universal", "count": 2 },
|
||||||
|
{ "country": "Vietnam", "count": 2 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"scholarships_page_1": [
|
||||||
|
{
|
||||||
|
"name": "Boustany Foundation Scholarship, MBA - Harvard University",
|
||||||
|
"funding_type": "Partially Funded",
|
||||||
|
"level": ["S2"],
|
||||||
|
"country": "United States",
|
||||||
|
"open_date": "2026-05-21",
|
||||||
|
"deadline": "2027-05-31",
|
||||||
|
"status": "Pendaftaran dibuka",
|
||||||
|
"url": "https://www.schoters.com/id/beasiswa/boustany-foundation-scholarship-mba-harvard-university-1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "La Trobe University - La Trobe High Achiever Scholarship",
|
||||||
|
"funding_type": "Partially Funded",
|
||||||
|
"level": ["S1"],
|
||||||
|
"country": "Australia",
|
||||||
|
"open_date": "2023-09-29",
|
||||||
|
"deadline": "2026-12-31",
|
||||||
|
"status": "Pendaftaran dibuka",
|
||||||
|
"url": "https://www.schoters.com/id/beasiswa/la-trobe-university-la-trobe-high-achiever-scholarship-2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Charles Sturt University - International Student Support Scholarship",
|
||||||
|
"funding_type": "Partially Funded",
|
||||||
|
"level": ["S1"],
|
||||||
|
"country": "Australia",
|
||||||
|
"open_date": "2026-05-21",
|
||||||
|
"deadline": "2026-12-31",
|
||||||
|
"status": "Pendaftaran dibuka",
|
||||||
|
"url": "https://www.schoters.com/id/beasiswa/charles-sturt-university-international-student-support-scholarship-3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "La Trobe University - La Trobe High Achiever Scholarship",
|
||||||
|
"funding_type": "Partially Funded",
|
||||||
|
"level": ["S2"],
|
||||||
|
"country": "Australia",
|
||||||
|
"open_date": "2023-09-29",
|
||||||
|
"deadline": "2026-12-31",
|
||||||
|
"status": "Pendaftaran dibuka",
|
||||||
|
"url": "https://www.schoters.com/id/beasiswa/la-trobe-university-la-trobe-high-achiever-scholarship-4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ETH Zurich - Excellence Scholarship & Opportunity Programme (ESOP)",
|
||||||
|
"funding_type": "Fully Funded",
|
||||||
|
"level": ["S2"],
|
||||||
|
"country": "Switzerland",
|
||||||
|
"open_date": "2026-11-01",
|
||||||
|
"deadline": "2026-11-30",
|
||||||
|
"status": "Pendaftaran dibuka",
|
||||||
|
"url": "https://www.schoters.com/id/beasiswa/eth-zurich-excellence-scholarship-opportunity-programme-esop-5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Southern Taiwan University of Science and Technology - The STUST Scholarship for International Students",
|
||||||
|
"funding_type": "Fully Funded",
|
||||||
|
"level": ["S3"],
|
||||||
|
"country": "Taiwan",
|
||||||
|
"open_date": "2026-09-15",
|
||||||
|
"deadline": "2026-11-30",
|
||||||
|
"status": "Pendaftaran dibuka",
|
||||||
|
"url": "https://www.schoters.com/id/beasiswa/southern-taiwan-university-of-science-and-technology-the-stust-scholarship-for-international-students-6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Southern Taiwan University of Science and Technology - The STUST Scholarship for International Students",
|
||||||
|
"funding_type": "Partially Funded",
|
||||||
|
"level": ["S2"],
|
||||||
|
"country": "Taiwan",
|
||||||
|
"open_date": "2026-09-15",
|
||||||
|
"deadline": "2026-11-30",
|
||||||
|
"status": "Pendaftaran dibuka",
|
||||||
|
"url": "https://www.schoters.com/id/beasiswa/southern-taiwan-university-of-science-and-technology-the-stust-scholarship-for-international-students-7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "University of Melbourne - Melbourne International Undergraduate Scholarship",
|
||||||
|
"funding_type": "Partially Funded",
|
||||||
|
"level": ["S1"],
|
||||||
|
"country": "Australia",
|
||||||
|
"open_date": "2026-05-21",
|
||||||
|
"deadline": "2026-10-31",
|
||||||
|
"status": "Pendaftaran dibuka",
|
||||||
|
"url": "https://www.schoters.com/id/beasiswa/university-of-melbourne-melbourne-international-undergraduate-scholarship-8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Charles Darwin University - CDU Global Merit Scholarship",
|
||||||
|
"funding_type": "Partially Funded",
|
||||||
|
"level": ["S1"],
|
||||||
|
"country": "Australia",
|
||||||
|
"open_date": "2026-05-21",
|
||||||
|
"deadline": "2026-10-09",
|
||||||
|
"status": "Pendaftaran dibuka",
|
||||||
|
"url": "https://www.schoters.com/id/beasiswa/charles-darwin-university-cdu-global-merit-scholarship-9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Deakin University - Vice-Chancellor's International Scholarship",
|
||||||
|
"funding_type": "Partially Funded",
|
||||||
|
"level": ["S1", "S2"],
|
||||||
|
"country": "Australia",
|
||||||
|
"open_date": "2026-05-21",
|
||||||
|
"deadline": "2026-09-25",
|
||||||
|
"status": "Pendaftaran dibuka",
|
||||||
|
"url": "https://www.schoters.com/id/beasiswa/deakin-university-vice-chancellor-s-international-scholarship-10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pagination": {
|
||||||
|
"current_page": 1,
|
||||||
|
"total_pages": 153
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"email": "ask@schoters.com",
|
||||||
|
"phone": "0811 9424 966",
|
||||||
|
"address": "Gedung Sinarmas MSIG Tower Lt. 33, Jl. Jenderal Sudirman Kav. 21, Karet Kuningan, Setiabudi, Jakarta Selatan, Jakarta 12920"
|
||||||
|
},
|
||||||
|
"social_media": {
|
||||||
|
"instagram": "https://www.instagram.com/schoters/",
|
||||||
|
"facebook": "https://www.facebook.com/schoters",
|
||||||
|
"youtube": "https://www.youtube.com/@Schoters",
|
||||||
|
"tiktok": "https://www.tiktok.com/@schoters.id",
|
||||||
|
"twitter_x": "https://x.com/schoters_com",
|
||||||
|
"linkedin": "https://id.linkedin.com/company/schoters"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
# 🎓 Portal Informasi Penerimaan Mahasiswa Baru (PMB)
|
||||||
|
**Program Studi Informatika - Universitas Tanjungpura (UNTAN)**
|
||||||
|
|
||||||
|

|
||||||
|
*Pratinjau Antarmuka Halaman PMB Informatika UNTAN oleh Kelompok 02*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💡 Pengantar (Project Overview)
|
||||||
|
|
||||||
|
### 📌 Gambaran Umum
|
||||||
|
Proyek ini merupakan halaman web statis yang dikembangkan untuk menyediakan informasi penerimaan mahasiswa baru secara terpusat, terstruktur, dan mudah diakses. Halaman ini bertujuan membantu calon mahasiswa dalam memahami berbagai jalur seleksi serta informasi terkait secara menyeluruh.
|
||||||
|
|
||||||
|
### 🎯 Fitur Utama
|
||||||
|
|
||||||
|
Penyajian informasi terpusat untuk seluruh jalur penerimaan:
|
||||||
|
1. SNBP
|
||||||
|
2. SNBT
|
||||||
|
3. Mandiri-UNTAN
|
||||||
|
4. SMMPTN-Barat
|
||||||
|
|
||||||
|
Informasi detail setiap jalur:
|
||||||
|
1. Persyaratan
|
||||||
|
2. Mekanisme seleksi
|
||||||
|
3. Jadwal
|
||||||
|
4. Biaya pendidikan
|
||||||
|
5. Informasi beasiswa
|
||||||
|
6. FAQ (Frequently Asked Questions)
|
||||||
|
7. Tautan ke situs resmi dan sumber informasi terkait
|
||||||
|
8. Tampilan responsif untuk desktop dan perangkat mobile
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ❓ Tanya Jawab Seputar Proyek (Project Q&A)
|
||||||
|
|
||||||
|
**Q: Apa fokus utama dari rancangan website ini?**
|
||||||
|
A: Website ini dirancang sebagai pusat informasi terpadu yang merangkum empat jalur seleksi masuk UNTAN (SNBP, SNBT, SMMPTN-Barat, dan Mandiri). Pengunjung dapat melihat persyaratan, mekanisme, jadwal, rincian biaya, hingga peluang beasiswa dalam satu tampilan interaktif tanpa harus berpindah halaman.
|
||||||
|
|
||||||
|
**Q: Mengapa dibangun sepenuhnya sebagai halaman statis?**
|
||||||
|
A: Pendekatan statis (HTML, CSS, Vanilla JS) menawarkan tiga keuntungan utama: **Kecepatan** muat yang luar biasa, **Keamanan** tingkat tinggi (karena tidak ada akses *database* langsung dari luar), dan kemudahan **Deployment** di hampir semua layanan *hosting*.
|
||||||
|
|
||||||
|
**Q: Bagaimana sistem dua bahasa (Bilingual) bekerja di website ini?**
|
||||||
|
A: Kami menggunakan *script* kustom (`localization.js`) yang mendeteksi bahasa bawaan dari peramban pengguna (`navigator.language`). Pengguna juga dapat mengubah bahasa secara manual (ID/EN), dan pilihan tersebut akan disimpan di dalam `localStorage` sehingga preferensi tidak hilang saat halaman di-muat ulang.
|
||||||
|
|
||||||
|
**Q: Apakah visualisasi data di website ini bersifat statis (gambar)?**
|
||||||
|
A: Tidak. Kami mengimplementasikan **Chart.js** untuk merender grafik perbandingan antara "Jumlah Peminat" dan "Daya Tampung" secara dinamis dan interaktif.
|
||||||
|
|
||||||
|
**Q: Bagaimana tingkat responsivitas website ini di perangkat seluler?**
|
||||||
|
A: Sangat optimal. Kami menggunakan pendekatan tata letak *Grid* dan *Flexbox* mutakhir. Untuk navigasi kartu jalur masuk, kami bahkan mengintegrasikan *Horizontal Drag Slider* yang ramah sentuhan (touch-friendly) khusus untuk perangkat *mobile* dan tablet.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌐 Navigasi & Antarmuka
|
||||||
|
|
||||||
|
1. Menggunakan pendekatan single-page dengan smooth scrolling
|
||||||
|
2. Navigasi berbasis tab untuk pemilihan jalur seleksi
|
||||||
|
3. Komponen accordion untuk FAQ dan persyaratan
|
||||||
|
4. Layout berbasis card untuk penyajian informasi ringkas
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📎 Sumber Eksternal
|
||||||
|
Sistem menyediakan tautan menuju berbagai sumber resmi, seperti:
|
||||||
|
|
||||||
|
1. Situs resmi SNPMB
|
||||||
|
2. Situs resmi Mandiri-UNTAN
|
||||||
|
3. Situs resmi SMMPTN-Barat
|
||||||
|
4. Portal informasi beasiswa
|
||||||
|
5. Media sosial resmi terkait
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ Tumpukan Teknologi (Tech Stack)
|
||||||
|
|
||||||
|
| Kategori | Teknologi | Kegunaan dalam Proyek |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **Inti (Core)** | HTML5, CSS3, Vanilla JS | Fundamental pembentuk struktur, gaya, dan logika halaman. |
|
||||||
|
| **Visualisasi** | Chart.js 4.4 | Merender grafik batang peminat & daya tampung. |
|
||||||
|
| **Animasi** | AOS (Animate On Scroll) | Memberikan efek transisi halus saat elemen masuk ke *viewport*. |
|
||||||
|
| **Ikonografi** | FontAwesome 6.5 | Menyediakan aset ikon modern dan ringan berskala vektor. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Panduan Deployment (Hosting Guide)
|
||||||
|
Sifat proyek yang 100% statis membuatnya sangat mudah untuk di-deploy (di-online-kan). Pilih salah satu platform favorit Anda di bawah ini:
|
||||||
|
|
||||||
|
### Opsi 1: Vercel (Paling Direkomendasikan & Cepat)
|
||||||
|
|
||||||
|
1. Push proyek ke repositori GitHub, GitLab, atau Bitbucket.
|
||||||
|
|
||||||
|
2. Buat akun dan masuk ke Vercel.
|
||||||
|
|
||||||
|
3. Klik tombol Add New Project dan import repositori Anda.
|
||||||
|
|
||||||
|
4. Pada bagian Framework Preset, pilih Other.
|
||||||
|
|
||||||
|
5. Kosongkan bagian Build Command dan Output Directory.
|
||||||
|
|
||||||
|
6. Klik Deploy. Website Anda akan aktif dalam kurang dari 30 detik.
|
||||||
|
|
||||||
|
### Opsi 2: Coolify (Self-Hosted)
|
||||||
|
|
||||||
|
1. Masuk ke Dashboard Coolify server Anda.
|
||||||
|
|
||||||
|
2. Buka menu proyek lalu tambahkan resource baru (+ Add).
|
||||||
|
|
||||||
|
3. Pilih opsi Application dan hubungkan repositori Git Anda.
|
||||||
|
|
||||||
|
4. Coolify secara otomatis akan menggunakan Nixpacks untuk mendeteksi Static/HTML Site.
|
||||||
|
|
||||||
|
5. Kosongkan Build Command dan Install Command.
|
||||||
|
|
||||||
|
6. Klik Deploy.
|
||||||
|
|
||||||
|
### Opsi 3: GitHub Pages (Paling Praktis)
|
||||||
|
|
||||||
|
1. Pastikan kode sudah di-push ke repositori GitHub.
|
||||||
|
|
||||||
|
2. Masuk ke halaman repositori Anda, klik tab Settings.
|
||||||
|
|
||||||
|
3. Di panel sebelah kiri, pilih menu Pages.
|
||||||
|
|
||||||
|
4. Pada bagian Source, pilih Deploy from a branch.
|
||||||
|
|
||||||
|
5. Pilih branch utama dan pastikan foldernya diatur ke / (root).
|
||||||
|
|
||||||
|
6. Klik Save.
|
||||||
|
|
||||||
|
### Opsi 4: Netlify (Sistem Drag & Drop)
|
||||||
|
|
||||||
|
1. Masuk ke Netlify.
|
||||||
|
|
||||||
|
2. Masuk ke menu Sites -> Klik Add new site.
|
||||||
|
|
||||||
|
3. Kita bisa memilih Import an existing project (lewat Git) atau Deploy manually (cukup seret dan lepas folder proyek ke browser).
|
||||||
|
|
||||||
|
4. Jika lewat Git, biarkan Build command dan Publish directory kosong.
|
||||||
|
|
||||||
|
5. Klik Deploy site.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 👥 Tim Pengembang (Credits)
|
||||||
|
Disusun dan dikembangkan oleh Kelompok 02 untuk memenuhi standar tugas perancangan antarmuka Halaman Statis.
|
||||||
|
|
||||||
|
Nama Kelompok : Habis Ini Seminar
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📂 Struktur Direktori (Folder Structure)
|
||||||
|
|
||||||
|
Pastikan struktur direktori Anda sesuai sebelum menjalankan atau men-deploy proyek ini:
|
||||||
|
|
||||||
|
```text
|
||||||
|
📦 project-root/
|
||||||
|
┣ 📜 localization.js # Script logika terjemahan & State Management
|
||||||
|
┃ 🖼️ logo.png # Logo Universitas/Prodi
|
||||||
|
┃ 🖼️ footer.png # Aset ornamen footer
|
||||||
|
┣ 📜 index.html # Halaman utama aplikasi (Entry point)
|
||||||
|
┣ 📜 style.css # Berkas tata letak dan desain (Styling)
|
||||||
|
┣ 🖼️ Screenshot.png # Gambar pratinjau halaman
|
||||||
|
┗ 📜 README.md # Dokumentasi proyek
|
||||||
|
After Width: | Height: | Size: 118 KiB |
|
After Width: | Height: | Size: 934 KiB |
|
After Width: | Height: | Size: 150 KiB |
|
After Width: | Height: | Size: 753 KiB |
|
After Width: | Height: | Size: 992 KiB |
|
After Width: | Height: | Size: 669 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 924 KiB |
|
After Width: | Height: | Size: 788 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 455 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 889 KiB |
|
After Width: | Height: | Size: 835 KiB |
|
After Width: | Height: | Size: 853 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 737 KiB |
|
After Width: | Height: | Size: 490 KiB |
|
After Width: | Height: | Size: 704 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 70 KiB |
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
|
After Width: | Height: | Size: 103 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 988 KiB |
|
After Width: | Height: | Size: 680 KiB |
|
After Width: | Height: | Size: 803 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 296 KiB |
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "roda",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "roda",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "ISC"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "roda",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Project website untuk menampilkan sorotan dan prestasi karya mahasiswa program studi Informatika Universitas Tanjungpura.",
|
||||||
|
"main": "server.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"start": "node server.js",
|
||||||
|
"dev": "node server.js"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC"
|
||||||
|
}
|
||||||
@@ -0,0 +1,279 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const http = require('http');
|
||||||
|
const path = require('path');
|
||||||
|
const { URL } = require('url');
|
||||||
|
|
||||||
|
const ROOT_DIR = __dirname;
|
||||||
|
const PARENT_DIR = path.join(__dirname, '..', '..');
|
||||||
|
const INDEX_PATH = path.join(ROOT_DIR, 'index.html');
|
||||||
|
const ENV_PATH = path.join(ROOT_DIR, '.env');
|
||||||
|
const HOST = '127.0.0.1';
|
||||||
|
const PORT = Number(process.env.PORT || 3000);
|
||||||
|
|
||||||
|
function logServerError(scope, error, extra = {}) {
|
||||||
|
console.error(`[${scope}]`, error && error.stack ? error.stack : error, extra);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseEnvFile(filePath) {
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const env = {};
|
||||||
|
const lines = fs.readFileSync(filePath, 'utf8').split(/\r?\n/);
|
||||||
|
|
||||||
|
for (const rawLine of lines) {
|
||||||
|
const line = rawLine.trim();
|
||||||
|
if (!line || line.startsWith('#')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const separatorIndex = line.indexOf('=');
|
||||||
|
if (separatorIndex === -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = line.slice(0, separatorIndex).trim();
|
||||||
|
let value = line.slice(separatorIndex + 1).trim();
|
||||||
|
|
||||||
|
if (
|
||||||
|
(value.startsWith('"') && value.endsWith('"')) ||
|
||||||
|
(value.startsWith("'") && value.endsWith("'"))
|
||||||
|
) {
|
||||||
|
value = value.slice(1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
env[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAppConfig() {
|
||||||
|
const env = {
|
||||||
|
...parseEnvFile(ENV_PATH),
|
||||||
|
...process.env
|
||||||
|
};
|
||||||
|
|
||||||
|
const dashboardProxyTemplates = [
|
||||||
|
env.DASHBOARD_PROXY_1,
|
||||||
|
env.DASHBOARD_PROXY_2,
|
||||||
|
env.DASHBOARD_PROXY_3
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
|
return {
|
||||||
|
dashboardTargetUrl: env.DASHBOARD_TARGET_URL || '',
|
||||||
|
dashboardProxyTemplates
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const DASHBOARD_CACHE_TTL_MS = 5 * 60 * 1000;
|
||||||
|
let dashboardCache = { html: null, timestamp: 0 };
|
||||||
|
|
||||||
|
async function fetchDashboardHtml() {
|
||||||
|
const targetUrl = getAppConfig().dashboardTargetUrl;
|
||||||
|
if (!targetUrl) {
|
||||||
|
throw new Error('DASHBOARD_TARGET_URL kosong');
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
if (dashboardCache.html && (now - dashboardCache.timestamp) < DASHBOARD_CACHE_TTL_MS) {
|
||||||
|
return { html: dashboardCache.html, cached: 'HIT' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 12000);
|
||||||
|
try {
|
||||||
|
const response = await fetch(targetUrl, {
|
||||||
|
signal: controller.signal,
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP_ERROR_${response.status}`);
|
||||||
|
}
|
||||||
|
const html = await response.text();
|
||||||
|
dashboardCache = { html, timestamp: Date.now() };
|
||||||
|
return { html, cached: 'MISS' };
|
||||||
|
} catch (error) {
|
||||||
|
if (dashboardCache.html) {
|
||||||
|
return { html: dashboardCache.html, cached: 'STALE' };
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateAppConfig(config) {
|
||||||
|
const errors = [];
|
||||||
|
|
||||||
|
if (!config.dashboardTargetUrl) {
|
||||||
|
errors.push('DASHBOARD_TARGET_URL belum diisi.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(config.dashboardProxyTemplates) || !config.dashboardProxyTemplates.length) {
|
||||||
|
errors.push('Minimal satu DASHBOARD_PROXY_* harus diisi.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
function injectConfig(html) {
|
||||||
|
const configScript = `<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>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendHtml(res) {
|
||||||
|
try {
|
||||||
|
const html = fs.readFileSync(INDEX_PATH, 'utf8');
|
||||||
|
const configErrors = validateAppConfig(getAppConfig());
|
||||||
|
if (configErrors.length) {
|
||||||
|
logServerError('config', new Error('Invalid app config'), { configErrors });
|
||||||
|
}
|
||||||
|
|
||||||
|
const withConfig = injectConfig(html);
|
||||||
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
||||||
|
res.end(withConfig);
|
||||||
|
} catch (error) {
|
||||||
|
logServerError('sendHtml', error);
|
||||||
|
res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
|
||||||
|
res.end('Internal Server Error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMimeType(pathname) {
|
||||||
|
const ext = path.extname(pathname).toLowerCase();
|
||||||
|
const mimeTypes = {
|
||||||
|
'.html': 'text/html; charset=utf-8',
|
||||||
|
'.css': 'text/css; charset=utf-8',
|
||||||
|
'.js': 'application/javascript; charset=utf-8',
|
||||||
|
'.json': 'application/json; charset=utf-8',
|
||||||
|
'.png': 'image/png',
|
||||||
|
'.jpg': 'image/jpeg',
|
||||||
|
'.jpeg': 'image/jpeg',
|
||||||
|
'.gif': 'image/gif',
|
||||||
|
'.svg': 'image/svg+xml',
|
||||||
|
'.ico': 'image/x-icon',
|
||||||
|
'.webp': 'image/webp',
|
||||||
|
'.woff': 'font/woff',
|
||||||
|
'.woff2': 'font/woff2',
|
||||||
|
'.ttf': 'font/ttf',
|
||||||
|
'.eot': 'application/vnd.ms-fontobject'
|
||||||
|
};
|
||||||
|
return mimeTypes[ext] || 'application/octet-stream';
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendStaticFile(res, filepath) {
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(filepath)) {
|
||||||
|
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
|
||||||
|
res.end('Not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = fs.readFileSync(filepath);
|
||||||
|
const mimeType = getMimeType(filepath);
|
||||||
|
res.writeHead(200, { 'Content-Type': mimeType });
|
||||||
|
res.end(content);
|
||||||
|
} catch (error) {
|
||||||
|
logServerError('sendStaticFile', error, { filepath });
|
||||||
|
res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
|
||||||
|
res.end('Internal Server Error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendNotFound(res) {
|
||||||
|
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
|
||||||
|
res.end('Not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const server = http.createServer((req, res) => {
|
||||||
|
try {
|
||||||
|
const requestUrl = new URL(req.url, `http://${req.headers.host}`);
|
||||||
|
const pathname = requestUrl.pathname;
|
||||||
|
|
||||||
|
if (pathname === '/' || pathname === '/index.html') {
|
||||||
|
sendHtml(res);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pathname === '/api/dashboard') {
|
||||||
|
fetchDashboardHtml()
|
||||||
|
.then(({ html, cached }) => {
|
||||||
|
res.writeHead(200, {
|
||||||
|
'Content-Type': 'text/html; charset=utf-8',
|
||||||
|
'X-Cache': cached
|
||||||
|
});
|
||||||
|
res.end(html);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logServerError('api-dashboard', error);
|
||||||
|
res.writeHead(502, { 'Content-Type': 'text/plain; charset=utf-8' });
|
||||||
|
res.end('DASHBOARD_FETCH_FAILED');
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle /assets/ from parent directory
|
||||||
|
if (pathname.startsWith('/assets/')) {
|
||||||
|
const assetsFilepath = path.join(PARENT_DIR, pathname);
|
||||||
|
const normalizedAssetsPath = path.normalize(assetsFilepath);
|
||||||
|
|
||||||
|
if (!normalizedAssetsPath.startsWith(path.normalize(PARENT_DIR))) {
|
||||||
|
sendNotFound(res);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendStaticFile(res, normalizedAssetsPath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve static files from ROOT_DIR
|
||||||
|
const filepath = path.join(ROOT_DIR, pathname);
|
||||||
|
const normalizedPath = path.normalize(filepath);
|
||||||
|
|
||||||
|
// Security check: ensure the requested file is within ROOT_DIR
|
||||||
|
if (!normalizedPath.startsWith(path.normalize(ROOT_DIR))) {
|
||||||
|
sendNotFound(res);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendStaticFile(res, normalizedPath);
|
||||||
|
} catch (error) {
|
||||||
|
logServerError('request-handler', error, { url: req.url });
|
||||||
|
res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
|
||||||
|
res.end('Internal Server Error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.on('clientError', (error, socket) => {
|
||||||
|
logServerError('clientError', error);
|
||||||
|
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(PORT, HOST, () => {
|
||||||
|
console.log(`Server ready at http://${HOST}:${PORT}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
server.on('error', (error) => {
|
||||||
|
logServerError('server', error, { host: HOST, port: PORT });
|
||||||
|
process.exitCode = 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('uncaughtException', (error) => {
|
||||||
|
logServerError('uncaughtException', error);
|
||||||
|
process.exitCode = 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('unhandledRejection', (reason) => {
|
||||||
|
logServerError('unhandledRejection', reason instanceof Error ? reason : new Error(String(reason)));
|
||||||
|
process.exitCode = 1;
|
||||||
|
});
|
||||||
@@ -1,48 +1,310 @@
|
|||||||
:root {
|
.bg-pattern {
|
||||||
--brand-navy: #003150;
|
background-image: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.05'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
|
||||||
--brand-yellow: #feb401;
|
|
||||||
--text-main: #1e293b;
|
|
||||||
--text-soft: #64748b;
|
|
||||||
--bg-soft: #f8fafc;
|
|
||||||
--line: #e2e8f0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
* { box-sizing: border-box; }
|
/* Navbar & Accessibility Fixes */
|
||||||
html, body { margin: 0; padding: 0; }
|
#nav-wrapper {
|
||||||
body { font-family: "Roboto", Arial, sans-serif; color: var(--text-main); background: #fff; line-height: 1.6; }
|
position: fixed !important;
|
||||||
.container { width: min(1100px, 92%); margin-inline: auto; }
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 50;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
-webkit-backface-visibility: hidden;
|
||||||
|
will-change: box-shadow;
|
||||||
|
}
|
||||||
|
|
||||||
.nav { background: var(--brand-navy); color: #fff; border-bottom: 4px solid var(--brand-yellow); padding: 14px 0; }
|
#nav-wrapper>div:first-child {
|
||||||
.brand { font-size: 1.1rem; font-weight: 700; letter-spacing: .02em; }
|
transform: translateZ(0);
|
||||||
|
-webkit-transform: translateZ(0);
|
||||||
|
}
|
||||||
|
|
||||||
.hero { position: relative; color: #fff; min-height: 220px; display: flex; align-items: flex-end; overflow: hidden; }
|
@media (max-width: 1023px) {
|
||||||
.hero-bg { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; filter: brightness(0.45); }
|
#nav-wrapper>div:first-child {
|
||||||
.hero-inner { position: relative; z-index: 1; padding: 36px 0; }
|
min-height: 0 !important;
|
||||||
.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; }
|
max-height: none;
|
||||||
.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; }
|
html {
|
||||||
@media (min-width: 1024px) { .layout { grid-template-columns: minmax(0, 3fr) minmax(260px, 1fr); } }
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
.meta { font-size: .78rem; text-transform: uppercase; color: #475569; display: flex; flex-wrap: wrap; gap: .75rem 1rem; margin-bottom: 1rem; }
|
.ticker-wrap {
|
||||||
.cover { background: var(--bg-soft); border: 1px solid var(--line); margin-bottom: 1.5rem; }
|
mask-image: linear-gradient(to right, transparent 0%, black 10%, black 90%, transparent 100%);
|
||||||
.cover img { width: 100%; height: auto; display: block; }
|
-webkit-mask-image: linear-gradient(to right, transparent 0%, black 10%, black 90%, transparent 100%);
|
||||||
|
}
|
||||||
|
|
||||||
.article-body > * + * { margin-top: 1.1em; }
|
.ticker-track {
|
||||||
.article-body h2 { font-size: 1.35rem; margin: 1.6em 0 .5em; }
|
display: inline-flex;
|
||||||
.article-body h3 { font-size: 1.1rem; margin: 1.4em 0 .4em; }
|
white-space: nowrap;
|
||||||
.article-body p { margin: 0 0 1em; }
|
min-width: max-content;
|
||||||
.article-body ul, .article-body ol { padding-left: 1.4rem; }
|
animation: tickerMove 30s linear infinite;
|
||||||
.article-body blockquote { border-left: 4px solid #005eb8; background: #eef6ff; padding: .8rem 1rem; color: #334155; }
|
will-change: transform;
|
||||||
.article-body a { color: #005eb8; }
|
}
|
||||||
.article-body img { max-width: 100%; border-radius: 8px; }
|
|
||||||
.article-body code { background: #f1f5f9; padding: .1em .35em; border-radius: 4px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
|
|
||||||
|
|
||||||
.note { border: 1px solid var(--line); background: var(--bg-soft); padding: 1rem; }
|
.ticker-wrap:hover .ticker-track {
|
||||||
.note h3 { margin: 0 0 .5rem; font-size: .76rem; text-transform: uppercase; letter-spacing: .12em; color: #475569; }
|
animation-play-state: paused;
|
||||||
|
}
|
||||||
|
|
||||||
.footer { margin-top: 2rem; background: var(--brand-navy); color: #fff; border-top: 4px solid var(--brand-yellow); }
|
@keyframes tickerMove {
|
||||||
.footer-inner { display: flex; align-items: center; justify-content: center; gap: .75rem; padding: 1rem 0; }
|
0% {
|
||||||
.footer-logo { height: 28px; width: auto; }
|
transform: translateX(0);
|
||||||
.footer-ornament { width: 100%; display: block; opacity: .9; }
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive animation timing */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.ticker-track {
|
||||||
|
animation-duration: 25s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1366px) {
|
||||||
|
.ticker-track {
|
||||||
|
animation-duration: 35s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-clamp-1 {
|
||||||
|
overflow: hidden;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-clamp-2 {
|
||||||
|
overflow: hidden;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-clamp-3 {
|
||||||
|
overflow: hidden;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reveal {
|
||||||
|
opacity: 1;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reveal-ready .reveal {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(24px);
|
||||||
|
transition: opacity 0.65s ease, transform 0.65s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reveal-ready .reveal.is-visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
html {
|
||||||
|
scroll-behavior: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ticker-track {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reveal {
|
||||||
|
opacity: 1;
|
||||||
|
transform: none;
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dosen grid: subgrid rows agar header & footer setiap card sejajar */
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
#dosen-grid {
|
||||||
|
grid-template-rows: auto auto;
|
||||||
|
}
|
||||||
|
#dosen-grid > a {
|
||||||
|
grid-row: span 2;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: subgrid;
|
||||||
|
}
|
||||||
|
/* White section: flex column so expertise sits at top, rank/link pinned to bottom */
|
||||||
|
#dosen-grid > a > div:last-child {
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
padding-top: 1rem !important;
|
||||||
|
}
|
||||||
|
#dosen-grid > a > div:last-child > div:last-child {
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom responsive breakpoints - overriding Tailwind CDN defaults */
|
||||||
|
/* Mobile: 390px and up */
|
||||||
|
@media (min-width: 390px) {
|
||||||
|
body {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tablet: 768px and up */
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
#kbk-card-grid {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Laptop/Desktop: 1024px and up */
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
nav.bg-\[#feb401\] {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#lang-toggle-btn {
|
||||||
|
display: flex !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden.lg\:flex {
|
||||||
|
display: flex !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide hamburger button on desktop (aligns with ACAAB lg:1024px) */
|
||||||
|
#mobile-menu-button {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#kbk-card-grid {
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
#dosen-grid {
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
#riset-grid {
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Desktop: 1366px and up */
|
||||||
|
@media (min-width: 1366px) {
|
||||||
|
#kbk-card-grid {
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
#riset-grid {
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
#dosen-grid {
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Extra Large Desktop: 1920px and up */
|
||||||
|
@media (min-width: 1920px) {
|
||||||
|
body {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== MEGA MENU STYLES FROM LIMAEM ===== */
|
||||||
|
.mega-panel {
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: opacity 0.2s ease, visibility 0.2s ease;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mega-panel.active {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mega-overlay {
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: opacity 0.2s ease, visibility 0.2s ease;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mega-overlay.active {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mega-link-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
background: white;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mega-link-card:hover {
|
||||||
|
border-color: #3b82f6;
|
||||||
|
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mega-link-icon {
|
||||||
|
width: 2.5rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu-btn.active {
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
color: #1e40af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#main-nav {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mega-menu-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mega-menu-container .mega-panel {
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu-item {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu-item::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -12px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
/* ===========================================
|
||||||
|
Base / Reset — scoped under .tapops-doc-root
|
||||||
|
Prevents contaminating Tailwind nav/footer.
|
||||||
|
=========================================== */
|
||||||
|
|
||||||
|
.tapops-doc-root *,
|
||||||
|
.tapops-doc-root *::before,
|
||||||
|
.tapops-doc-root *::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Font is set on the root only (inherited), NOT on `*`.
|
||||||
|
A universal `.tapops-doc-root *` rule would tie Font Awesome's
|
||||||
|
`.fas` font-family in specificity and—loading later—override it,
|
||||||
|
blanking every icon. Inheritance loses to FA's explicit rule, so
|
||||||
|
icons render while normal text still gets Roboto. */
|
||||||
|
.tapops-doc-root {
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
color: var(--text-dark);
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form controls don't inherit font-family by default — opt them in.
|
||||||
|
These elements never hold Font Awesome glyphs, so this is icon-safe. */
|
||||||
|
.tapops-doc-root input,
|
||||||
|
.tapops-doc-root select,
|
||||||
|
.tapops-doc-root textarea,
|
||||||
|
.tapops-doc-root button {
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tapops-doc-root a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tapops-doc-root img {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Shared Section Styles --- */
|
||||||
|
|
||||||
|
.tapops-doc-root .section-header {
|
||||||
|
margin-bottom: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tapops-doc-root .section-tag {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
background-color: var(--secondary-gold);
|
||||||
|
color: var(--primary-blue);
|
||||||
|
display: inline-block;
|
||||||
|
padding: 6px 14px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tapops-doc-root .section-title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
letter-spacing: -1px;
|
||||||
|
line-height: 1.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Shared Button Styles --- */
|
||||||
|
|
||||||
|
.tapops-doc-root .btn-join,
|
||||||
|
.tapops-doc-root .btn-outline {
|
||||||
|
padding: 17px 30px;
|
||||||
|
font-weight: 800;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tapops-doc-root .btn-join {
|
||||||
|
background-color: var(--secondary-gold);
|
||||||
|
color: var(--primary-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tapops-doc-root .btn-outline {
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tapops-doc-root .btn-join:hover,
|
||||||
|
.tapops-doc-root .btn-outline:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tapops-doc-root .btn-join:hover {
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tapops-doc-root .btn-outline:hover {
|
||||||
|
border-color: var(--secondary-gold);
|
||||||
|
color: var(--secondary-gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.tapops-doc-root .btn-join,
|
||||||
|
.tapops-doc-root .btn-outline {
|
||||||
|
padding: 14px 24px;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.tapops-doc-root .btn-join,
|
||||||
|
.tapops-doc-root .btn-outline {
|
||||||
|
padding: 12px 20px;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
@import url("repository/index.css");
|
||||||
@@ -0,0 +1,276 @@
|
|||||||
|
/* ===========================================
|
||||||
|
Hero Section (Centered & Immersive) — Tapops
|
||||||
|
=========================================== */
|
||||||
|
|
||||||
|
.research-hero.immersive-hero {
|
||||||
|
background: radial-gradient(circle at center, #003150 0%, #001e35 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 160px 5% 180px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
padding-bottom: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.research-hero::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 800px;
|
||||||
|
height: 800px;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
|
||||||
|
background: conic-gradient(from 180deg at 50% 50%,
|
||||||
|
transparent 0deg,
|
||||||
|
rgba(254, 180, 1, 0.15) 180deg,
|
||||||
|
transparent 360deg);
|
||||||
|
|
||||||
|
-webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 2px), #fff 0);
|
||||||
|
mask: radial-gradient(farthest-side, transparent calc(100% - 2px), #fff 0);
|
||||||
|
|
||||||
|
border-radius: 50%;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1;
|
||||||
|
animation: rotate-orbit 30s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotate-orbit {
|
||||||
|
from {
|
||||||
|
transform: translate(-50%, -50%) rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate(-50%, -50%) rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-container-centered {
|
||||||
|
max-width: 900px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-kicker {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
letter-spacing: 4px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--secondary-gold);
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-title {
|
||||||
|
font-size: 4.8rem;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1.05;
|
||||||
|
letter-spacing: -3px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-tagline {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
opacity: 0.85;
|
||||||
|
margin-bottom: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Floating Insight Bar --- */
|
||||||
|
|
||||||
|
.hero-floating-stat-bar {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 50px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
padding: 25px 60px;
|
||||||
|
border-radius: 4px;
|
||||||
|
z-index: 3;
|
||||||
|
gap: 40px;
|
||||||
|
box-shadow: 0 30px 60px rgba(0, 0, 0, 0.3);
|
||||||
|
max-width: calc(100% - 32px);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-bar-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-bar-value {
|
||||||
|
font-size: 2.2rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--secondary-gold);
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-bar-label {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
letter-spacing: 1.5px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 700;
|
||||||
|
opacity: 0.6;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-bar-divider {
|
||||||
|
width: 1px;
|
||||||
|
height: 40px;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
Hero Responsive
|
||||||
|
=========================================== */
|
||||||
|
|
||||||
|
@media (max-width: 992px) {
|
||||||
|
.research-hero.immersive-hero {
|
||||||
|
padding: 120px 5% 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-title {
|
||||||
|
font-size: 3.5rem;
|
||||||
|
letter-spacing: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-tagline {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-floating-stat-bar {
|
||||||
|
padding: 20px 32px;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-bar-value {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.research-hero.immersive-hero {
|
||||||
|
padding: 100px 16px 130px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-kicker {
|
||||||
|
font-size: 0.68rem;
|
||||||
|
letter-spacing: 2.5px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-title {
|
||||||
|
font-size: 2.2rem;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-tagline {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 12px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 320px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-floating-stat-bar {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 320px;
|
||||||
|
padding: 18px 24px;
|
||||||
|
gap: 20px;
|
||||||
|
bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-bar-value {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-bar-label {
|
||||||
|
font-size: 0.6rem;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-bar-divider {
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.research-hero::before {
|
||||||
|
width: 400px;
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.research-hero.immersive-hero {
|
||||||
|
padding: 80px 12px 125px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-kicker {
|
||||||
|
font-size: 0.6rem;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-title {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-tagline {
|
||||||
|
font-size: 0.82rem;
|
||||||
|
line-height: 1.45;
|
||||||
|
margin-bottom: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-actions {
|
||||||
|
max-width: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-floating-stat-bar {
|
||||||
|
flex-direction: row;
|
||||||
|
bottom: 20px;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 14px 20px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 280px;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-bar-divider {
|
||||||
|
width: 1px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-bar-value {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.research-hero::before {
|
||||||
|
width: 280px;
|
||||||
|
height: 280px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,286 @@
|
|||||||
|
/* ===========================================
|
||||||
|
Navigation & Mega Menu Styles
|
||||||
|
Extracted from inline <style> in index.html
|
||||||
|
=========================================== */
|
||||||
|
|
||||||
|
/* ===== MOBILE NAV STABILITY FIX =====
|
||||||
|
Prevents logo bar from collapsing/shifting on Android when
|
||||||
|
address bar hides/shows and triggers viewport reflow.
|
||||||
|
===================================== */
|
||||||
|
#nav-wrapper {
|
||||||
|
position: fixed !important;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 50;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
-webkit-backface-visibility: hidden;
|
||||||
|
will-change: box-shadow;
|
||||||
|
}
|
||||||
|
|
||||||
|
#nav-wrapper>div:first-child {
|
||||||
|
transform: translateZ(0);
|
||||||
|
-webkit-transform: translateZ(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1023px) {
|
||||||
|
#nav-wrapper>div:first-child {
|
||||||
|
min-height: 0 !important;
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#pengumuman-bar {
|
||||||
|
z-index: 40;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pengumuman-bar.is-stuck {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 40;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Skip Link Styling */
|
||||||
|
.skip-link {
|
||||||
|
position: absolute;
|
||||||
|
top: -40px;
|
||||||
|
left: 0;
|
||||||
|
background: #003150;
|
||||||
|
color: white;
|
||||||
|
padding: 8px 16px;
|
||||||
|
z-index: 100;
|
||||||
|
transition: top 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skip-link:focus {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus visible for better keyboard navigation */
|
||||||
|
:focus-visible {
|
||||||
|
outline: 3px solid #feb401;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mega Menu Styles */
|
||||||
|
.hero-pagination .swiper-pagination-bullet {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0.4);
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-pagination .swiper-pagination-bullet-active {
|
||||||
|
background: rgba(255, 255, 255, 1);
|
||||||
|
width: 24px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mega-panel {
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: opacity 0.2s ease, visibility 0.2s ease;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mega-panel.active {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Overlay transition */
|
||||||
|
#mega-overlay {
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: opacity 0.2s ease, visibility 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mega-overlay.active {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mega-link-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
background: white;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mega-link-card:hover {
|
||||||
|
border-color: #3b82f6;
|
||||||
|
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mega-link-icon {
|
||||||
|
width: 2.5rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu-btn.active {
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
color: #1e40af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mega menu positioning - no gap */
|
||||||
|
#main-nav {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mega-menu-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mega-menu-container .mega-panel {
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keep pointer bridge under nav to prevent hover loss */
|
||||||
|
.nav-menu-item {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu-item::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -12px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== Luco Chatbot Animations ===== */
|
||||||
|
@keyframes slideInRight {
|
||||||
|
from {
|
||||||
|
transform: translateX(100px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from {
|
||||||
|
transform: translateY(10px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes lucoPulse {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#luco-container {
|
||||||
|
animation: slideInRight 0.5s ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
#luco-bubble {
|
||||||
|
animation: fadeInUp 0.3s ease-out forwards;
|
||||||
|
width: min(18rem, calc(100vw - 2rem));
|
||||||
|
}
|
||||||
|
|
||||||
|
#luco-btn {
|
||||||
|
animation: lucoPulse 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
#luco-btn:hover {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Typing indicator */
|
||||||
|
.typing-indicator span {
|
||||||
|
display: inline-block;
|
||||||
|
animation: bounce 1.4s infinite ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typing-indicator span:nth-child(1) {
|
||||||
|
animation-delay: 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typing-indicator span:nth-child(2) {
|
||||||
|
animation-delay: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typing-indicator span:nth-child(3) {
|
||||||
|
animation-delay: 0.4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bounce {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
80%,
|
||||||
|
100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
40% {
|
||||||
|
transform: translateY(-6px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Luco speech bubble arrow */
|
||||||
|
#luco-bubble .bubble-arrow {
|
||||||
|
position: absolute;
|
||||||
|
right: 24px;
|
||||||
|
bottom: -6px;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
background: white;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
border-right: 1px solid #e2e8f0;
|
||||||
|
border-bottom: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
/* --- Document Cards Grid --- */
|
||||||
|
|
||||||
|
.document-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-card {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
padding: 30px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
transition: all 0.3s cubic-bezier(0.165, 0.84, 0.44, 1);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-card:hover {
|
||||||
|
border-color: var(--secondary-gold);
|
||||||
|
box-shadow: 0 20px 40px rgba(0, 49, 80, 0.05);
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-type-icon {
|
||||||
|
font-size: 2.2rem;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-type-pdf {
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-type-docx {
|
||||||
|
color: #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-type-pptx {
|
||||||
|
color: #f97316;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-type-xlsx {
|
||||||
|
color: #16a34a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-type-txt {
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-type-archive {
|
||||||
|
color: #7c3aed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-type-image {
|
||||||
|
color: #ec4899;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-type-file {
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-title {
|
||||||
|
font-size: 1.15rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-meta {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-badge {
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-weight: 800;
|
||||||
|
padding: 4px 10px;
|
||||||
|
background: #f1f5f9;
|
||||||
|
color: #64748b;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-badge.date {
|
||||||
|
background: hsl(200, 95%, 95%);
|
||||||
|
color: hsl(200, 95%, 30%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Tag Badges --- */
|
||||||
|
|
||||||
|
.doc-tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-tags-icon {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: #94a3b8;
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-tag {
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 3px 10px;
|
||||||
|
background: hsl(43, 100%, 95%);
|
||||||
|
color: hsl(43, 80%, 35%);
|
||||||
|
border: 1px solid hsl(43, 80%, 85%);
|
||||||
|
border-radius: 20px;
|
||||||
|
letter-spacing: 0.3px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-tag:hover {
|
||||||
|
background: hsl(43, 80%, 88%);
|
||||||
|
border-color: hsl(43, 80%, 70%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-actions {
|
||||||
|
margin-top: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-action-primary,
|
||||||
|
.btn-action-outline {
|
||||||
|
padding: 12px;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 800;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.btn-action-primary {
|
||||||
|
background: var(--primary-blue);
|
||||||
|
color: white;
|
||||||
|
border: 1px solid var(--primary-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
a.btn-action-primary:hover {
|
||||||
|
background: var(--secondary-gold);
|
||||||
|
border-color: var(--secondary-gold);
|
||||||
|
color: var(--primary-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
a.btn-action-outline {
|
||||||
|
background: transparent;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.btn-action-outline:hover {
|
||||||
|
border-color: var(--primary-blue);
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
/* --- Content Area & Search --- */
|
||||||
|
|
||||||
|
.repo-main-content {
|
||||||
|
min-height: 800px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-content-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 30px;
|
||||||
|
margin-bottom: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-title-area .section-title {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-subtitle {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: #64748b;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-controls {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 24px;
|
||||||
|
background: white;
|
||||||
|
padding: 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-container {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 260px;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input-group {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-icon {
|
||||||
|
position: absolute;
|
||||||
|
left: 18px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#doc-search {
|
||||||
|
width: 100%;
|
||||||
|
padding: 16px 16px 16px 50px;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#doc-search:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Filter Controls --- */
|
||||||
|
|
||||||
|
.filter-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-range-group {
|
||||||
|
min-width: 300px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-label {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #94a3b8;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-wrapper {
|
||||||
|
position: relative;
|
||||||
|
min-width: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sort-filter-wrapper {
|
||||||
|
position: relative;
|
||||||
|
min-width: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-icon {
|
||||||
|
position: absolute;
|
||||||
|
left: 14px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
color: var(--primary-blue);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
@@ -0,0 +1,422 @@
|
|||||||
|
/* --- Date Range Filter & Popover --- */
|
||||||
|
.date-picker-trigger {
|
||||||
|
width: 100%;
|
||||||
|
padding: 14px 18px;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-picker-trigger:hover {
|
||||||
|
border-color: var(--secondary-gold);
|
||||||
|
background: #fffdf5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-picker-trigger:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--primary-blue);
|
||||||
|
box-shadow: 0 0 0 3px rgba(0, 49, 80, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-picker-trigger .trigger-left-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-picker-trigger .trigger-arrow {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
transition: transform 0.25s ease;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-range-group.active .date-picker-trigger .trigger-arrow {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-range-group.active .date-picker-trigger {
|
||||||
|
border-color: var(--primary-blue);
|
||||||
|
box-shadow: 0 0 0 3px rgba(0, 49, 80, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Backdrop */
|
||||||
|
.date-picker-backdrop {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
z-index: 40;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Popover */
|
||||||
|
.date-picker-popover {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + 6px);
|
||||||
|
right: 0;
|
||||||
|
width: 630px;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
z-index: 45;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-range-group.active .date-picker-popover {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Top Bar */
|
||||||
|
.date-picker-top-bar {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
background: #f8fafc;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-display-inputs {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-display-field input {
|
||||||
|
width: 110px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border: 1px solid #cbd5e1;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
text-align: center;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-picker-separator {
|
||||||
|
color: #94a3b8;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Body */
|
||||||
|
.date-picker-body {
|
||||||
|
display: flex;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Calendar Pane */
|
||||||
|
.calendar-container {
|
||||||
|
flex: 1;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-nav {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.month-labels {
|
||||||
|
display: flex;
|
||||||
|
gap: 90px;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.month-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.year-select-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.year-select-wrapper:hover {
|
||||||
|
background: #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-year-select {
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
padding: 2px 16px 2px 6px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.year-select-wrapper .select-arrow {
|
||||||
|
position: absolute;
|
||||||
|
right: 4px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
font-size: 0.6rem;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: #64748b;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-btn:hover {
|
||||||
|
background: #f1f5f9;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-grids {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-grid-pane {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day-headers {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(7, 1fr);
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #94a3b8;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.days-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(7, 1fr);
|
||||||
|
row-gap: 4px;
|
||||||
|
column-gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day-cell {
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #475569;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day-cell:hover {
|
||||||
|
background: #f1f5f9;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.day-cell.empty {
|
||||||
|
pointer-events: none;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day-cell.disabled {
|
||||||
|
color: #cbd5e1;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day-cell.range-start {
|
||||||
|
background: var(--primary-blue) !important;
|
||||||
|
color: white !important;
|
||||||
|
border-radius: 4px 0 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day-cell.range-end {
|
||||||
|
background: var(--primary-blue) !important;
|
||||||
|
color: white !important;
|
||||||
|
border-radius: 0 4px 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day-cell.range-start.range-end {
|
||||||
|
border-radius: 4px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day-cell.range-between {
|
||||||
|
background: #eff6ff !important;
|
||||||
|
color: var(--primary-blue) !important;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Presets Pane */
|
||||||
|
.presets-container {
|
||||||
|
width: 190px;
|
||||||
|
border-left: 1px solid var(--border-color);
|
||||||
|
padding: 16px;
|
||||||
|
background: white;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presets-title {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #94a3b8;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presets-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
max-height: 240px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preset-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
text-align: left;
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #475569;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preset-btn:hover {
|
||||||
|
background: #f8fafc;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preset-btn.active {
|
||||||
|
background: #eff6ff;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preset-btn.active::after {
|
||||||
|
content: '✓';
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer Action Buttons */
|
||||||
|
.date-picker-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
padding: 8px 20px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 700;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #cbd5e1;
|
||||||
|
color: #475569;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn:hover {
|
||||||
|
background: #f1f5f9;
|
||||||
|
border-color: #94a3b8;
|
||||||
|
color: #1e293b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.apply-btn {
|
||||||
|
background: var(--primary-blue);
|
||||||
|
border: 1px solid var(--primary-blue);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.apply-btn:hover {
|
||||||
|
background: #002238;
|
||||||
|
border-color: #002238;
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-divider {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #94a3b8;
|
||||||
|
text-transform: uppercase;
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-select,
|
||||||
|
select#sort-filter {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 12px 12px 36px;
|
||||||
|
background: #f8fafc;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
cursor: pointer;
|
||||||
|
appearance: none;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-select:hover,
|
||||||
|
select#sort-filter:hover {
|
||||||
|
border-color: var(--secondary-gold);
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-select:focus,
|
||||||
|
select#sort-filter:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
/* --- Custom UI Dropdown --- */
|
||||||
|
.custom-dropdown {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dropdown-trigger {
|
||||||
|
width: 100%;
|
||||||
|
padding: 14px 18px;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dropdown-trigger:hover {
|
||||||
|
border-color: var(--secondary-gold);
|
||||||
|
background: #fffdf5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dropdown-trigger:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--primary-blue);
|
||||||
|
box-shadow: 0 0 0 3px rgba(0, 49, 80, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dropdown-trigger .trigger-arrow {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
transition: transform 0.25s ease;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dropdown.active .custom-dropdown-trigger .trigger-arrow {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dropdown.active .custom-dropdown-trigger {
|
||||||
|
border-color: var(--primary-blue);
|
||||||
|
box-shadow: 0 0 0 3px rgba(0, 49, 80, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dropdown-menu {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + 6px);
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 100;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transform: translateY(-8px);
|
||||||
|
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
overflow: hidden;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dropdown.active .custom-dropdown-menu {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dropdown-item {
|
||||||
|
padding: 12px 18px;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #475569;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dropdown-item:hover {
|
||||||
|
background: #f1f5f9;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dropdown-item.active {
|
||||||
|
background: #f0f7ff;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tick mark for selected item */
|
||||||
|
.custom-dropdown-item.active::after {
|
||||||
|
content: "\f00c";
|
||||||
|
font-family: "Font Awesome 5 Free";
|
||||||
|
font-weight: 900;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Filter Skeleton Loading --- */
|
||||||
|
|
||||||
|
.filter-skeleton {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-skeleton-item {
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: linear-gradient(90deg, #f1f5f9 25%, #e2e8f0 50%, #f1f5f9 75%);
|
||||||
|
background-size: 200% 100%;
|
||||||
|
animation: skeleton-shimmer 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-skeleton-item.short {
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes skeleton-shimmer {
|
||||||
|
0% { background-position: 200% 0; }
|
||||||
|
100% { background-position: -200% 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-info-box {
|
||||||
|
background: #f1f5f9;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-info-box i {
|
||||||
|
color: var(--primary-blue);
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-info-box p {
|
||||||
|
font-size: 0.82rem;
|
||||||
|
color: #64748b;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
@import url("layout.css");
|
||||||
|
@import url("dropdown.css");
|
||||||
|
@import url("controls.css");
|
||||||
|
@import url("date-picker.css");
|
||||||
|
@import url("cards.css");
|
||||||
|
@import url("responsive.css");
|
||||||
|
@import url("pagination.css");
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
/* ===========================================
|
||||||
|
Document Hub & Dashboard Layout
|
||||||
|
=========================================== */
|
||||||
|
|
||||||
|
.documents-section {
|
||||||
|
padding: 120px 5%;
|
||||||
|
background: hsl(0, 0%, 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-dashboard-container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 280px 1fr;
|
||||||
|
gap: 40px;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Sidebar Styling --- */
|
||||||
|
|
||||||
|
.repo-sidebar {
|
||||||
|
position: sticky;
|
||||||
|
top: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile category dropdown wrapper (hidden on desktop) */
|
||||||
|
.mobile-category-control-group {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-title {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #94a3b8;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-filter-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Desktop: show buttons, hide dropdown */
|
||||||
|
.filter-btn-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-select-group {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-filter-btn {
|
||||||
|
text-align: left;
|
||||||
|
padding: 14px 18px;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-filter-btn.active {
|
||||||
|
background: var(--primary-blue);
|
||||||
|
color: white;
|
||||||
|
border-color: var(--primary-blue);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 49, 80, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-filter-btn:hover:not(.active) {
|
||||||
|
border-color: var(--secondary-gold);
|
||||||
|
background: #fffdf5;
|
||||||
|
}
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
/* ===========================================
|
||||||
|
Pagination
|
||||||
|
=========================================== */
|
||||||
|
|
||||||
|
.doc-pagination {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 32px 0 0;
|
||||||
|
gap: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-info {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #94a3b8;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-nav {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-btn {
|
||||||
|
min-width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
padding: 0 10px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
background: white;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-btn:hover:not(.disabled):not(.active) {
|
||||||
|
border-color: var(--secondary-gold);
|
||||||
|
background: #fffdf5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-btn.active {
|
||||||
|
background: var(--primary-blue);
|
||||||
|
border-color: var(--primary-blue);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-btn.disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: not-allowed;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-ellipsis {
|
||||||
|
min-width: 32px;
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #94a3b8;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1rem;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.doc-pagination {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.pagination-btn {
|
||||||
|
min-width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-nav {
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
Responsive — Phone (≤480px)
|
||||||
|
=========================================== */
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.documents-section {
|
||||||
|
padding: 50px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dropdown-trigger {
|
||||||
|
padding: 12px 14px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Single column cards */
|
||||||
|
.document-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-card {
|
||||||
|
padding: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-title {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-type-icon {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-meta {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-tags {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-tag {
|
||||||
|
font-size: 0.6rem;
|
||||||
|
padding: 2px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-action-primary,
|
||||||
|
.btn-action-outline {
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-actions {
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-controls {
|
||||||
|
padding: 12px;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#doc-search {
|
||||||
|
padding: 12px 12px 12px 40px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-icon {
|
||||||
|
left: 14px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-content-header {
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
/* ===========================================
|
||||||
|
Responsive — Tablet (≤1024px)
|
||||||
|
=========================================== */
|
||||||
|
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.repo-dashboard-container {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-sidebar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-category-control-group {
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile: show dropdown */
|
||||||
|
.filter-select-group {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Skeleton single bar on mobile */
|
||||||
|
.filter-skeleton {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-skeleton-item {
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-skeleton-item.short {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-skeleton-item:not(.short) ~ .filter-skeleton-item:not(.short) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-controls {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-container {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-controls {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-group {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
Responsive — Tablet / Large Phone (≤768px)
|
||||||
|
=========================================== */
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.documents-section {
|
||||||
|
padding: 80px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-content-header {
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-controls {
|
||||||
|
padding: 16px;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#doc-search {
|
||||||
|
padding: 14px 14px 14px 44px;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-controls {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-group {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-range-group {
|
||||||
|
min-width: 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.date-picker-open {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.date-picker-open .date-picker-backdrop {
|
||||||
|
display: block;
|
||||||
|
z-index: 998;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-picker-popover {
|
||||||
|
/* Default mobile popover positioning inside media query is overridden below,
|
||||||
|
but let's define the base transitions or layouts. */
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.date-picker-popover {
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
right: auto;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: calc(100% - 32px);
|
||||||
|
max-width: 380px;
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.15), 0 10px 10px -5px rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-picker-top-bar {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-grids {
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#right-calendar-pane {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.month-labels {
|
||||||
|
gap: 0;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#right-month-label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-picker-body {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presets-container {
|
||||||
|
width: 100%;
|
||||||
|
border-left: none;
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presets-list {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 6px;
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preset-btn {
|
||||||
|
padding: 6px 10px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-wrapper,
|
||||||
|
#sort-filter-wrapper {
|
||||||
|
min-width: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cards: 2-column when space permits, else single */
|
||||||
|
.document-grid {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-card {
|
||||||
|
padding: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-card:hover {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-type-icon {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-title {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-meta {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-tags {
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-main-content {
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
Responsive — Phone (≤480px)
|
||||||
|
=========================================== */
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.date-dd, .date-mm {
|
||||||
|
width: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-yyyy {
|
||||||
|
min-width: 38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-calendar-btn {
|
||||||
|
padding-left: 4px;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/* ===========================================
|
||||||
|
Responsive Breakpoints — Document Sections
|
||||||
|
=========================================== */
|
||||||
|
|
||||||
|
/* --- Tablet (≤768px) --- */
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.tapops-doc-root .section-title {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tapops-doc-root .section-tag {
|
||||||
|
font-size: 0.68rem;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
padding: 5px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tapops-doc-root .section-header {
|
||||||
|
margin-bottom: 35px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Phone (≤480px) --- */
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.tapops-doc-root .section-title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tapops-doc-root .section-tag {
|
||||||
|
font-size: 0.6rem;
|
||||||
|
letter-spacing: 1.5px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tapops-doc-root .section-subtitle {
|
||||||
|
font-size: 0.82rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tapops-doc-root .section-header {
|
||||||
|
margin-bottom: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,429 @@
|
|||||||
|
/* ===========================================
|
||||||
|
Procedures (Modern Process Flow)
|
||||||
|
=========================================== */
|
||||||
|
|
||||||
|
.journey-section {
|
||||||
|
padding: 120px 5%;
|
||||||
|
background: #ffffff;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.journey-container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-flow-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 40px;
|
||||||
|
margin-top: 80px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-step {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-visual {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-icon-box {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 1.8rem;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
box-shadow: 0 10px 25px rgba(0, 49, 80, 0.05);
|
||||||
|
transition: all 0.3s cubic-bezier(0.165, 0.84, 0.44, 1);
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-step:hover .step-icon-box {
|
||||||
|
border-color: var(--secondary-gold);
|
||||||
|
color: var(--secondary-gold);
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 15px 35px rgba(254, 180, 1, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-number-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: calc(50% - 38px);
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
background: var(--secondary-gold);
|
||||||
|
color: var(--primary-blue);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 800;
|
||||||
|
z-index: 3;
|
||||||
|
border: 2px solid white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-connector {
|
||||||
|
position: absolute;
|
||||||
|
left: calc(50% + 50px);
|
||||||
|
top: 50%;
|
||||||
|
width: calc(100% - 100px);
|
||||||
|
height: 2px;
|
||||||
|
background: repeating-linear-gradient(to right, #e2e8f0, #e2e8f0 5px, transparent 5px, transparent 10px);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-content h3 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
font-weight: 800;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
letter-spacing: -0.3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-content p {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: #64748b;
|
||||||
|
line-height: 1.7;
|
||||||
|
max-width: 280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.process-flow-wrapper {
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-connector {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.process-flow-wrapper {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-step {
|
||||||
|
flex-direction: row;
|
||||||
|
text-align: left;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-visual {
|
||||||
|
width: 80px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-number-badge {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-content p {
|
||||||
|
margin: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
Help Center (Interaction Cards)
|
||||||
|
=========================================== */
|
||||||
|
|
||||||
|
.help-center-section {
|
||||||
|
padding: 100px 5%;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-container {
|
||||||
|
max-width: 1100px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-header-centered {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-cards-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interaction-card {
|
||||||
|
position: relative;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-official {
|
||||||
|
background: linear-gradient(135deg, var(--primary-blue) 0%, #001e35 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-urgent {
|
||||||
|
background: linear-gradient(135deg, var(--secondary-gold) 0%, #ffd54f 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-inner {
|
||||||
|
background: white;
|
||||||
|
padding: 50px;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-official .card-inner {
|
||||||
|
border: 1px solid rgba(0, 49, 80, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-icon {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
background: #f8fafc;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-urgent .card-icon {
|
||||||
|
background: rgba(254, 180, 1, 0.1);
|
||||||
|
color: var(--secondary-gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.interaction-card h3 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
margin-bottom: 15px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interaction-card p {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: #64748b;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-checklist {
|
||||||
|
list-style: none;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-checklist li {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-checklist li i {
|
||||||
|
color: #10b981;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.btn-interaction,
|
||||||
|
a.btn-interaction-alt {
|
||||||
|
width: 100%;
|
||||||
|
padding: 18px;
|
||||||
|
font-weight: 800;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.btn-interaction {
|
||||||
|
background: var(--primary-blue);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.btn-interaction-alt {
|
||||||
|
background: var(--secondary-gold);
|
||||||
|
color: var(--primary-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
a.btn-interaction:hover {
|
||||||
|
background: #0f172a;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.btn-interaction-alt:hover {
|
||||||
|
background: #facc15;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
color: var(--primary-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.journey-section {
|
||||||
|
padding: 80px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-center-section {
|
||||||
|
padding: 80px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-cards-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-inner {
|
||||||
|
padding: 35px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interaction-card h3 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interaction-card p {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-icon {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.btn-interaction,
|
||||||
|
a.btn-interaction-alt {
|
||||||
|
padding: 14px;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
letter-spacing: 1.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-flow-wrapper {
|
||||||
|
margin-top: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-content h3 {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-content p {
|
||||||
|
font-size: 0.88rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.journey-section {
|
||||||
|
padding: 50px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-center-section {
|
||||||
|
padding: 50px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-header-centered {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-inner {
|
||||||
|
padding: 28px 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-icon {
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interaction-card h3 {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interaction-card p {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-checklist {
|
||||||
|
margin-bottom: 28px;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-checklist li {
|
||||||
|
font-size: 0.78rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.btn-interaction,
|
||||||
|
a.btn-interaction-alt {
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-flow-wrapper {
|
||||||
|
margin-top: 35px;
|
||||||
|
gap: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-step {
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-icon-box {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-content h3 {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-content p {
|
||||||
|
font-size: 0.82rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-number-badge {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
font-size: 0.6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||