8 Commits

26 changed files with 609 additions and 384 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 984 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

+69 -320
View File
@@ -5,6 +5,31 @@
<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 03 &ndash; HarusSelesaiKP | Informatika UNTAN</title> <title>Group 03 &ndash; HarusSelesaiKP | Informatika UNTAN</title>
<!--
Anti-flicker safety net: body.lang-pending is hidden via style.css
until localization.js (loaded sync below) applies the correct
language on DOMContentLoaded and removes the class.
This inline fallback ensures the rule exists even if style.css
hasn't parsed yet.
-->
<style id="lang-pending-style">
body.lang-pending {
visibility: hidden
}
</style>
<!--
localization.js MUST be loaded synchronously (no defer/async)
so the IIFE inside it can set window.__currentLang before
the browser renders any text. This is the ONLY intentional
render-blocking script.
-->
<script src="localization.js"></script>
<!-- Preload hero image — eliminates LCP delay -->
<link rel="preload" href="images/hero.webp" as="image" type="image/webp" />
<link rel="stylesheet" href="../../assets/global/output.css" /> <link rel="stylesheet" href="../../assets/global/output.css" />
<link rel="stylesheet" href="style.css" /> <link rel="stylesheet" href="style.css" />
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
@@ -12,297 +37,17 @@
<link <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" 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" /> rel="stylesheet" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
<style>
/* ===== 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 { <!-- Font Awesome loaded async — non-render-blocking -->
transform: translateZ(0); <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
-webkit-transform: translateZ(0); media="print" onload="this.media='all'" />
} <noscript>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
@media (max-width: 1023px) { </noscript>
#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;
}
</style>
</head> </head>
<body class="text-slate-800 antialiased bg-white selection:bg-untan-yellow selection:text-black">
<body class="lang-pending 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>
<div id="nav-wrapper" <div id="nav-wrapper"
@@ -351,8 +96,9 @@
<div class="pl-5 border-l border-white/20 flex items-center gap-3"> <div class="pl-5 border-l border-white/20 flex items-center gap-3">
<button <button
class="language-toggle-btn text-xs hover:text-untan-yellow transition-colors flex items-center gap-1"> class="language-toggle-btn text-xs hover:text-untan-yellow transition-colors flex items-center gap-1">
<span class="font-bold">ID</span> <span class="lang-id font-bold">ID</span>
<span class="text-white/40">|</span> EN <span class="text-white/40">|</span>
<span class="lang-en opacity-50 font-normal">EN</span>
</button> </button>
<button <button
class="w-8 h-8 flex items-center justify-center rounded-sm hover:bg-white/10 transition-colors" class="w-8 h-8 flex items-center justify-center rounded-sm hover:bg-white/10 transition-colors"
@@ -1133,9 +879,9 @@
<button <button
class="language-toggle-btn px-2 py-1 rounded-sm border border-white/20 text-xs text-white hover:bg-white/10 transition-colors flex items-center gap-2" class="language-toggle-btn px-2 py-1 rounded-sm border border-white/20 text-xs text-white hover:bg-white/10 transition-colors flex items-center gap-2"
aria-label="Ganti bahasa"> aria-label="Ganti bahasa">
<span class="font-bold">ID</span> <span class="lang-id font-bold">ID</span>
<span class="text-white/40" aria-hidden="true">|</span> <span class="text-white/40" aria-hidden="true">|</span>
EN <span class="lang-en opacity-50 font-normal">EN</span>
</button> </button>
<button <button
class="w-8 h-8 rounded-sm bg-white/10 hover:bg-white/20 flex items-center justify-center text-white transition-colors" class="w-8 h-8 rounded-sm bg-white/10 hover:bg-white/20 flex items-center justify-center text-white transition-colors"
@@ -1411,7 +1157,8 @@
<!-- LIVING COST SECTION --> <!-- LIVING COST SECTION -->
<section class="living-cost-section"> <section class="living-cost-section">
<div class="cost-banner-wrapper"> <div class="cost-banner-wrapper">
<img src="images/biaya-hidup.png" alt="biaya hidup" class="cost-banner-img" /> <img src="images/biaya-hidup.jpg" alt="Ilustrasi biaya hidup mahasiswa di Pontianak"
class="cost-banner-img" loading="lazy" decoding="async" width="1200" height="800" />
<div class="cost-banner-overlay"></div> <div class="cost-banner-overlay"></div>
</div> </div>
@@ -1464,7 +1211,7 @@
<div class="flex flex-col sm:flex-row items-center justify-center gap-4 sm:gap-6 mb-8 text-left"> <div class="flex flex-col sm:flex-row items-center justify-center gap-4 sm:gap-6 mb-8 text-left">
<img src="https://upload.wikimedia.org/wikipedia/id/0/03/Lambang_Universitas_Tanjungpura.png" <img src="https://upload.wikimedia.org/wikipedia/id/0/03/Lambang_Universitas_Tanjungpura.png"
class="h-16 sm:h-20 shrink-0" alt="Logo UNTAN" /> class="h-16 sm:h-20 shrink-0" alt="Logo UNTAN" loading="lazy" decoding="async" />
<div <div
class="border-t sm:border-t-0 sm:border-l-2 border-white/30 pt-4 sm:pt-0 sm:pl-5 py-1 flex flex-col justify-center leading-tight"> class="border-t sm:border-t-0 sm:border-l-2 border-white/30 pt-4 sm:pt-0 sm:pl-5 py-1 flex flex-col justify-center leading-tight">
<h2 class="text-2xl sm:text-3xl font-bold font-sans tracking-widest uppercase mb-1"> <h2 class="text-2xl sm:text-3xl font-bold font-sans tracking-widest uppercase mb-1">
@@ -1499,7 +1246,8 @@
<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" <img src="../../assets/global/footer.png" alt="Footer Illustration"
class="w-full h-auto object-cover object-bottom" style="object-position: bottom" /> class="w-full h-auto object-cover object-bottom" style="object-position: bottom" loading="lazy"
decoding="async" />
</div> </div>
</footer> </footer>
@@ -1616,7 +1364,8 @@
})(); })();
</script> </script>
<script src="../../assets/global/localization.js"></script> <!-- localization.js is now loaded in <head> synchronously for anti-flicker -->
<!-- See the <head> section for its <script> tag -->
<!-- JavaScript Kelompok HarusSelesaiKP --> <!-- JavaScript Kelompok HarusSelesaiKP -->
<!-- JavaScript Facilities Section --> <!-- JavaScript Facilities Section -->
@@ -1671,7 +1420,7 @@
desc: "Fasilitas olahraga outdoor dan indoor lengkap untuk menjaga kebugaran dan menyalurkan bakat.", desc: "Fasilitas olahraga outdoor dan indoor lengkap untuk menjaga kebugaran dan menyalurkan bakat.",
}, },
{ {
imgSrc: "images/masjid-muhtadin.png", imgSrc: "images/masjid-muhtadin.jpeg",
icon: "icons/tabler-mosque.svg", icon: "icons/tabler-mosque.svg",
titleKey: "skp_fac_title_masjid", titleKey: "skp_fac_title_masjid",
descKey: "skp_fac_desc_masjid", descKey: "skp_fac_desc_masjid",
@@ -1687,7 +1436,7 @@
desc: "Fasilitas laboratorium modern UNTAN yang mendukung riset, praktikum, dan inovasi mahasiswa.", desc: "Fasilitas laboratorium modern UNTAN yang mendukung riset, praktikum, dan inovasi mahasiswa.",
}, },
{ {
imgSrc: "images/audit-untan.png", imgSrc: "images/audit-untan.webp",
icon: "icons/building-home.svg", icon: "icons/building-home.svg",
titleKey: "skp_fac_title_audit", titleKey: "skp_fac_title_audit",
descKey: "skp_fac_desc_audit", descKey: "skp_fac_desc_audit",
@@ -1710,10 +1459,10 @@
cardsHtmlHTML += ` cardsHtmlHTML += `
<div class="facility-card"> <div class="facility-card">
<img src="${item.imgSrc}" alt="${displayTitle}" class="card-image" /> <img src="${item.imgSrc}" alt="${displayTitle}" class="card-image" loading="lazy" decoding="async" />
<div class="card-body"> <div class="card-body">
<span class="card-icon-badge"> <span class="card-icon-badge">
<img src="${item.icon}" alt="" class="card-icon" /> <img src="${item.icon}" alt="" class="card-icon" loading="lazy" decoding="async" />
</span> </span>
<h3 class="card-title">${displayTitle}</h3> <h3 class="card-title">${displayTitle}</h3>
<p class="card-desc">${displayDesc}</p> <p class="card-desc">${displayDesc}</p>
@@ -1759,7 +1508,7 @@
const directoriesData = [ const directoriesData = [
{ {
category: "Organisasi", category: "Organisasi",
image: "images/bem-untan.png", image: "images/bem-untan.webp",
titleKey: "skp_campus_dir_bem_title", titleKey: "skp_campus_dir_bem_title",
descKey: "skp_campus_dir_bem_desc", descKey: "skp_campus_dir_bem_desc",
}, },
@@ -1771,25 +1520,25 @@
}, },
{ {
category: "Organisasi", category: "Organisasi",
image: "images/hmif-untan.png", image: "images/hmif-untan.webp",
titleKey: "skp_campus_dir_hmif_title", titleKey: "skp_campus_dir_hmif_title",
descKey: "skp_campus_dir_hmif_desc", descKey: "skp_campus_dir_hmif_desc",
}, },
{ {
category: "Organisasi", category: "Organisasi",
image: "images/cafatifa-untan.png", image: "images/cafatifa-untan.webp",
titleKey: "skp_campus_dir_cafa_title", titleKey: "skp_campus_dir_cafa_title",
descKey: "skp_campus_dir_cafa_desc", descKey: "skp_campus_dir_cafa_desc",
}, },
{ {
category: "UKM", category: "UKM",
image: "images/mapala-untan.png", image: "images/mapala-untan.webp",
titleKey: "skp_campus_dir_mapala_title", titleKey: "skp_campus_dir_mapala_title",
descKey: "skp_campus_dir_mapala_desc", descKey: "skp_campus_dir_mapala_desc",
}, },
{ {
category: "UKM", category: "UKM",
image: "images/lisma-untan.png", image: "images/lisma-untan.webp",
titleKey: "skp_campus_dir_lisma_title", titleKey: "skp_campus_dir_lisma_title",
descKey: "skp_campus_dir_lisma_desc", descKey: "skp_campus_dir_lisma_desc",
}, },
@@ -1851,7 +1600,7 @@
return ` return `
<div class="directory-card"> <div class="directory-card">
<div class="img-wrapper"> <div class="img-wrapper">
<img src="${dir.image}" alt="${displayTitle}" /> <img src="${dir.image}" alt="${displayTitle}" loading="lazy" decoding="async" />
</div> </div>
<div class="card-info"> <div class="card-info">
<h4>${displayTitle}</h4> <h4>${displayTitle}</h4>
@@ -1920,13 +1669,13 @@
const destinationsData = [ const destinationsData = [
{ {
image: "images/khatulistiwa.png", image: "images/khatulistiwa.webp",
nameKey: "skp_dest_khatulistiwa_name", nameKey: "skp_dest_khatulistiwa_name",
descKey: "skp_dest_khatulistiwa_desc", descKey: "skp_dest_khatulistiwa_desc",
href: "https://maps.app.goo.gl/BUw4GWfSSDxvMCKA6", href: "https://maps.app.goo.gl/BUw4GWfSSDxvMCKA6",
}, },
{ {
image: "images/waterfront.png", image: "images/waterfront.webp",
nameKey: "skp_dest_waterfront_name", nameKey: "skp_dest_waterfront_name",
descKey: "skp_dest_waterfront_desc", descKey: "skp_dest_waterfront_desc",
href: "https://maps.app.goo.gl/2uo4jwNZtGTsvogU6", href: "https://maps.app.goo.gl/2uo4jwNZtGTsvogU6",
@@ -1938,7 +1687,7 @@
href: "https://maps.app.goo.gl/dfLJPdFJMK9ifja66", href: "https://maps.app.goo.gl/dfLJPdFJMK9ifja66",
}, },
{ {
image: "images/rumah-radank.png", image: "images/rumah-radank.webp",
nameKey: "skp_dest_radank_name", nameKey: "skp_dest_radank_name",
descKey: "skp_dest_radank_desc", descKey: "skp_dest_radank_desc",
href: "https://maps.app.goo.gl/UqBchR3zimQSdTqW6", href: "https://maps.app.goo.gl/UqBchR3zimQSdTqW6",
@@ -1950,13 +1699,13 @@
href: "https://maps.app.goo.gl/2SWXaJB3D9VXx9Kg7", href: "https://maps.app.goo.gl/2SWXaJB3D9VXx9Kg7",
}, },
{ {
image: "images/amalzone.png", image: "images/amalzone.jpg",
nameKey: "skp_dest_amalzone_name", nameKey: "skp_dest_amalzone_name",
descKey: "skp_dest_amalzone_desc", descKey: "skp_dest_amalzone_desc",
href: "https://maps.app.goo.gl/386expVMhHgMAmXA9", href: "https://maps.app.goo.gl/386expVMhHgMAmXA9",
}, },
{ {
image: "images/hutan-mangrove.png", image: "images/hutan-mangrove.jpg",
nameKey: "skp_dest_mangrove_name", nameKey: "skp_dest_mangrove_name",
descKey: "skp_dest_mangrove_desc", descKey: "skp_dest_mangrove_desc",
href: "#", href: "#",
@@ -2015,14 +1764,14 @@
const cuisinesData = [ const cuisinesData = [
{ {
category: "RM", category: "RM",
image: "images/gebuk-sambalado.png", image: "images/gebuk-sambalado.webp",
name: "Gebuk Sambalado", name: "Gebuk Sambalado",
descKey: "skp_cul_gebuk_desc", descKey: "skp_cul_gebuk_desc",
href: "https://maps.app.goo.gl/J1xCUCqVyTqPStG6A", href: "https://maps.app.goo.gl/J1xCUCqVyTqPStG6A",
}, },
{ {
category: "RM", category: "RM",
image: "images/kota-sari.png", image: "images/kota-sari.webp",
name: "Kota Sari", name: "Kota Sari",
descKey: "skp_cul_sari_desc", descKey: "skp_cul_sari_desc",
href: "https://maps.app.goo.gl/Tx5Rbt6TxCtFuiGdA", href: "https://maps.app.goo.gl/Tx5Rbt6TxCtFuiGdA",
@@ -2043,21 +1792,21 @@
}, },
{ {
category: "RM", category: "RM",
image: "images/ayam-melepar.png", image: "images/ayam-melepar.jpg",
name: "Ayam Melepar", name: "Ayam Melepar",
descKey: "skp_cul_melepar_desc", descKey: "skp_cul_melepar_desc",
href: "https://maps.app.goo.gl/DRbCvAS7EPAbxjvM6", href: "https://maps.app.goo.gl/DRbCvAS7EPAbxjvM6",
}, },
{ {
category: "RM", category: "RM",
image: "images/geprek-umi.png", image: "images/geprek-umi.jpg",
name: "Geprek Umi", name: "Geprek Umi",
descKey: "skp_cul_umi_desc", descKey: "skp_cul_umi_desc",
href: "https://maps.app.goo.gl/HV8X9qyLkjT7rULC7", href: "https://maps.app.goo.gl/HV8X9qyLkjT7rULC7",
}, },
{ {
category: "CS", category: "CS",
image: "images/cw-coffee.png", image: "images/cw-coffee.jpg",
name: "CW Coffee", name: "CW Coffee",
descKey: "skp_cul_cw_desc", descKey: "skp_cul_cw_desc",
href: "https://maps.app.goo.gl/xRJn6eM93PonZFfWA", href: "https://maps.app.goo.gl/xRJn6eM93PonZFfWA",
@@ -2071,7 +1820,7 @@
}, },
{ {
category: "CS", category: "CS",
image: "images/toko-kami.png", image: "images/toko-kami.jpg",
name: "Toko Kami", name: "Toko Kami",
descKey: "skp_cul_kami_desc", descKey: "skp_cul_kami_desc",
href: "https://maps.app.goo.gl/Nc9TX7aSQAJNr7es7", href: "https://maps.app.goo.gl/Nc9TX7aSQAJNr7es7",
@@ -2085,14 +1834,14 @@
}, },
{ {
category: "CS", category: "CS",
image: "images/weng-coffee.png", image: "images/weng-coffee.jpg",
name: "Weng Coffee", name: "Weng Coffee",
descKey: "skp_cul_weng_desc", descKey: "skp_cul_weng_desc",
href: "https://maps.app.goo.gl/EW3C4fjh4ZEpiRf18", href: "https://maps.app.goo.gl/EW3C4fjh4ZEpiRf18",
}, },
{ {
category: "CS", category: "CS",
image: "images/tropic-coffee.png", image: "images/tropic.jpg",
name: "Tropic Coffee", name: "Tropic Coffee",
descKey: "skp_cul_tropic_desc", descKey: "skp_cul_tropic_desc",
href: "https://maps.app.goo.gl/b3P9QJMk4mBPHoQM9", href: "https://maps.app.goo.gl/b3P9QJMk4mBPHoQM9",
@@ -2139,7 +1888,7 @@
return ` return `
<div class="culinary-card"> <div class="culinary-card">
<img src="${item.image}" alt="${item.name}" class="card-thumb" /> <img src="${item.image}" alt="${item.name}" class="card-thumb" loading="lazy" decoding="async" />
<div class="card-body"> <div class="card-body">
<h3>${item.name}</h3> <h3>${item.name}</h3>
<p>${displayDesc}</p> <p>${displayDesc}</p>
+99
View File
@@ -1,3 +1,29 @@
/**
* localization.js — HarusSelesaiKP
*
* ANTI-FLICKER STRATEGY:
* 1. IIFE `initLanguage()` runs SYNCHRONOUSLY (no defer/async) to set
* `window.__currentLang` before the browser paints any content.
* 2. HTML body carries `lang-pending` class; body is hidden via CSS until
* `applyTranslations()` fires on DOMContentLoaded and removes the class.
* 3. `languageChanged` CustomEvent signals dynamic sections (Facilities,
* Campus, Culinary, LivingCost) to re-render in the new language.
*/
// ─── 1. Determine language SYNCHRONOUSLY (before first paint) ────────────────
(function initLanguage() {
try {
const saved = localStorage.getItem("lang");
const browserLang = navigator.language?.startsWith("id") ? "id" : "en";
window.__currentLang = saved || browserLang;
} catch (e) {
window.__currentLang = "id";
}
// Stamp <html lang> immediately so screen readers get the right language
document.documentElement.lang = window.__currentLang;
})();
// ─── 2. Translation data ─────────────────────────────────────────────────────
const translations = { const translations = {
id: { id: {
// Kelompok HarusSelesaiKP (skp) // Kelompok HarusSelesaiKP (skp)
@@ -330,3 +356,76 @@ const translations = {
skp_cost_type_transport: "Transport", skp_cost_type_transport: "Transport",
}, },
}; };
// ─── 3. Public translation helper ────────────────────────────────────────────
/**
* t(key) — returns translated string for the active language.
* Falls back to Indonesian if key is missing in the active language.
*/
function t(key) {
const lang = window.__currentLang || "id";
return translations[lang]?.[key] ?? translations["id"]?.[key] ?? key;
}
// ─── 4. Apply translations to all [data-i18n] elements ───────────────────────
function applyTranslations(lang) {
window.__currentLang = lang;
document.documentElement.lang = lang;
try {
localStorage.setItem("lang", lang);
} catch (e) {
// localStorage may be blocked (private mode, etc.) — fail silently
}
document.querySelectorAll("[data-i18n]").forEach((el) => {
const key = el.dataset.i18n;
const text = t(key);
if (text && text !== key) {
el.textContent = text;
}
});
// Signal dynamic components to re-render
document.dispatchEvent(new CustomEvent("languageChanged"));
}
// ─── 5. Toggle language (called by UI buttons) ───────────────────────────────
function toggleLanguage() {
const next = window.__currentLang === "id" ? "en" : "id";
applyTranslations(next);
updateLanguageButtonUI(next);
}
function updateLanguageButtonUI(lang) {
document.querySelectorAll(".language-toggle-btn").forEach((btn) => {
const idSpan = btn.querySelector(".lang-id");
const enSpan = btn.querySelector(".lang-en");
if (idSpan && enSpan) {
if (lang === "id") {
idSpan.className = "lang-id font-bold text-white";
enSpan.className = "lang-en font-normal opacity-50";
} else {
idSpan.className = "lang-id font-normal opacity-50";
enSpan.className = "lang-en font-bold text-white";
}
}
});
}
// ─── 6. Boot on DOMContentLoaded ─────────────────────────────────────────────
document.addEventListener("DOMContentLoaded", () => {
// Apply translations to all static [data-i18n] nodes
applyTranslations(window.__currentLang);
// Sync language button label
updateLanguageButtonUI(window.__currentLang);
// Wire up toggle buttons
document.querySelectorAll(".language-toggle-btn").forEach((btn) => {
btn.addEventListener("click", toggleLanguage);
});
// ── Reveal body: remove anti-flicker class after translations applied ──
document.body.classList.remove("lang-pending");
});
+441 -64
View File
@@ -119,7 +119,7 @@ body {
display: block; display: block;
} }
.article-body>*+* { .article-body > * + * {
margin-top: 1.1em; margin-top: 1.1em;
} }
@@ -252,7 +252,8 @@ body {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
padding: 8px; padding-block: 4px;
padding-inline: 8px;
background-color: #ffffff; background-color: #ffffff;
color: #1f2937; color: #1f2937;
border-radius: 8px; border-radius: 8px;
@@ -268,8 +269,8 @@ body {
} }
.map-icon { .map-icon {
width: 16px; width: 12px;
height: 16px; height: 12px;
flex-shrink: 0; flex-shrink: 0;
} }
@@ -299,10 +300,12 @@ body {
position: absolute; position: absolute;
inset: 0; inset: 0;
z-index: 1; z-index: 1;
background: linear-gradient(270deg, background: linear-gradient(
rgba(0, 0, 0, 0) 21.76%, 270deg,
rgba(0, 0, 0, 0.4) 63.31%, rgba(0, 0, 0, 0) 21.76%,
rgba(0, 49, 80, 0.72) 78.24%); rgba(0, 0, 0, 0.4) 63.31%,
rgba(0, 49, 80, 0.72) 78.24%
);
} }
/* Judul Hero */ /* Judul Hero */
@@ -375,11 +378,11 @@ body {
.about-section { .about-section {
display: flex; display: flex;
flex-direction: column; align-items: center;
gap: 24px; gap: 24px;
padding: 20px 12px; padding: 20px 12px;
--box-size: 300px; --box-size: 150px;
.about-image-wrapper { .about-image-wrapper {
position: relative; position: relative;
@@ -414,17 +417,17 @@ body {
.about-title { .about-title {
color: var(--brand-navy); color: var(--brand-navy);
font-size: 1.25rem; font-size: 12px;
font-weight: 900; font-weight: 900;
letter-spacing: -0.05em; letter-spacing: -0.05em;
} }
.about-description { .about-description {
color: var(--text-soft); color: var(--text-soft);
font-size: 0.75rem; font-size: 8px;
font-weight: 500; font-weight: 500;
p+p { p + p {
margin-top: 8px; margin-top: 8px;
} }
} }
@@ -438,7 +441,7 @@ body {
color: var(--brand-navy); color: var(--brand-navy);
font-weight: 700; font-weight: 700;
text-decoration: none; text-decoration: none;
font-size: 0.75rem; font-size: 7px;
.link-icon { .link-icon {
margin-bottom: 2px; margin-bottom: 2px;
@@ -456,9 +459,18 @@ body {
--box-size: 240px; --box-size: 240px;
.about-content { .about-content {
.about-title {
font-size: 1.5rem;
}
.about-description { .about-description {
font-size: 0.75rem;
line-height: 16px; line-height: 16px;
} }
.about-link {
font-size: 0.75rem;
}
} }
} }
@@ -545,7 +557,7 @@ body {
/* Layouting Grid Kartu */ /* Layouting Grid Kartu */
.facilities-grid { .facilities-grid {
display: grid; display: grid;
grid-template-columns: repeat(1, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px; gap: 16px;
width: 100%; width: 100%;
} }
@@ -598,16 +610,15 @@ body {
} }
.card-title { .card-title {
font-size: 0.875rem; font-size: 10px;
font-weight: 900; font-weight: 900;
color: var(--brand-navy); color: var(--brand-navy);
margin-top: 8px; margin-top: 8px;
} }
.card-desc { .card-desc {
font-size: 0.75rem; font-size: 7px;
color: var(--text-soft); color: var(--text-soft);
line-height: 1.25rem;
} }
} }
@@ -630,6 +641,14 @@ body {
height: 20px; height: 20px;
} }
} }
.card-title {
font-size: 1rem;
}
.card-desc {
font-size: 0.75rem;
}
} }
} }
@@ -710,7 +729,6 @@ body {
/* Layout Wrapper */ /* Layout Wrapper */
.campus-layout { .campus-layout {
display: flex; display: flex;
flex-direction: column;
gap: 16px; gap: 16px;
width: 100%; width: 100%;
} }
@@ -718,9 +736,8 @@ body {
/* Styling Tombol Kategori (Kiri) */ /* Styling Tombol Kategori (Kiri) */
.category-list-container { .category-list-container {
display: flex; display: flex;
flex-direction: row; flex-direction: column;
gap: 12px; gap: 12px;
justify-content: center;
.category-btn { .category-btn {
display: flex; display: flex;
@@ -781,8 +798,8 @@ body {
} }
.img-wrapper { .img-wrapper {
width: 128px; width: 75px;
height: 128px; height: 75px;
flex-shrink: 0; flex-shrink: 0;
display: flex; display: flex;
align-items: center; align-items: center;
@@ -806,15 +823,14 @@ body {
h4 { h4 {
font-weight: 900; font-weight: 900;
letter-spacing: -0.05em; letter-spacing: -0.05em;
font-size: 1rem; font-size: 10px;
color: var(--brand-navy); color: var(--brand-navy);
margin-bottom: 4px; margin-bottom: 4px;
} }
p { p {
font-size: 0.75rem; font-size: 7px;
color: var(--text-soft); color: var(--text-soft);
line-height: 1.25rem;
} }
} }
} }
@@ -837,11 +853,26 @@ body {
.category-list-container { .category-list-container {
flex-direction: column; flex-direction: column;
justify-content: flex-start; justify-content: flex-start;
min-width: 25%;
} }
.category-list-container .category-btn { .category-list-container .category-btn {
font-size: 0.875rem; font-size: 0.875rem;
} }
.directory-list-container {
.directory-card {
.card-info {
h4 {
font-size: 1rem;
}
p {
font-size: 0.75rem;
}
}
}
}
} }
@media (min-width: 768px) { @media (min-width: 768px) {
@@ -953,10 +984,12 @@ body {
.pontianak-overlay { .pontianak-overlay {
position: absolute; position: absolute;
inset: 0; inset: 0;
background: linear-gradient(0deg, background: linear-gradient(
rgba(0, 0, 0, 0.9) -14.48%, 0deg,
rgba(0, 0, 0, 0.4) 42.76%, rgba(0, 0, 0, 0.9) -14.48%,
rgba(0, 0, 0, 0) 100%); rgba(0, 0, 0, 0.4) 42.76%,
rgba(0, 0, 0, 0) 100%
);
z-index: 1; z-index: 1;
} }
@@ -1076,8 +1109,8 @@ body {
/* Layout Grid Destinasi */ /* Layout Grid Destinasi */
.destination-grid { .destination-grid {
display: grid; display: grid;
grid-template-columns: repeat(1, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 24px; gap: 12px;
width: 100%; width: 100%;
} }
@@ -1087,17 +1120,24 @@ body {
background-size: cover; background-size: cover;
background-position: center; background-position: center;
border-radius: 8px; border-radius: 8px;
height: 450px; height: 300px;
overflow: hidden; overflow: hidden;
transition: all 0.3s ease;
&:hover {
transform: scale(1.02);
}
/* Gradasi Hitam di Atas Background */ /* Gradasi Hitam di Atas Background */
.card-overlay { .card-overlay {
position: absolute; position: absolute;
inset: 0; inset: 0;
background: linear-gradient(0deg, background: linear-gradient(
rgba(0, 0, 0, 0.8) 0%, 0deg,
rgba(0, 0, 0, 0.2) 50%, rgba(0, 0, 0, 0.8) 0%,
rgba(0, 0, 0, 0) 100%); rgba(0, 0, 0, 0.2) 50%,
rgba(0, 0, 0, 0) 100%
);
z-index: 1; z-index: 1;
} }
@@ -1110,20 +1150,19 @@ body {
height: 100%; height: 100%;
gap: 8px; gap: 8px;
padding: 16px; padding: 16px;
padding-bottom: 32px; padding-bottom: 24px;
max-width: 288px; max-width: 288px;
.card-name { .card-name {
color: #ffffff; color: #ffffff;
font-weight: 900; font-weight: 900;
letter-spacing: -0.025em; letter-spacing: -0.025em;
font-size: 1rem; font-size: 12px;
} }
.card-desc { .card-desc {
color: #d1d5db; color: #d1d5db;
font-size: 0.75rem; font-size: 8px;
line-height: 1.25rem;
} }
} }
} }
@@ -1133,11 +1172,20 @@ body {
.destination-grid { .destination-grid {
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 24px;
}
.destination-card .card-content .card-name {
font-size: 1rem;
} }
.destination-card .card-content .card-desc { .destination-card .card-content .card-desc {
font-size: 0.875rem; font-size: 0.875rem;
} }
.destination-card {
height: 400px;
}
} }
@media (min-width: 768px) { @media (min-width: 768px) {
@@ -1163,6 +1211,10 @@ body {
.destination-title { .destination-title {
font-size: 2.25rem; font-size: 2.25rem;
} }
.destination-card {
height: 450px;
}
} }
@media (min-width: 1280px) { @media (min-width: 1280px) {
@@ -1234,7 +1286,7 @@ body {
/* Grid Layout Kartu */ /* Grid Layout Kartu */
.culinary-grid { .culinary-grid {
display: grid; display: grid;
grid-template-columns: repeat(1, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 24px; gap: 24px;
width: 100%; width: 100%;
} }
@@ -1249,6 +1301,11 @@ body {
border-radius: 12px; border-radius: 12px;
background-color: #ffffff; background-color: #ffffff;
overflow: hidden; overflow: hidden;
transition: all 0.3s ease;
&:hover {
transform: scale(1.02);
}
.card-thumb { .card-thumb {
width: 100%; width: 100%;
@@ -1270,11 +1327,10 @@ body {
} }
p { p {
font-size: 0.75rem; font-size: 9px;
font-weight: 500; font-weight: 500;
color: var(--text-soft); color: var(--text-soft);
line-height: 1.25rem; min-height: 26px;
min-height: 60px;
} }
/* Modifikasi Khusus MapButton di dalam Kartu Kuliner */ /* Modifikasi Khusus MapButton di dalam Kartu Kuliner */
@@ -1332,7 +1388,6 @@ body {
.living-cost-section { .living-cost-section {
display: flex; display: flex;
flex-direction: column;
width: 100%; width: 100%;
/* Wrapper Gambar Kiri */ /* Wrapper Gambar Kiri */
@@ -1351,10 +1406,12 @@ body {
.cost-banner-overlay { .cost-banner-overlay {
position: absolute; position: absolute;
inset: 0; inset: 0;
background: linear-gradient(0deg, background: linear-gradient(
#012338 0%, 0deg,
rgba(1, 35, 56, 0.4) 55%, #012338 0%,
rgba(1, 35, 56, 0) 100%); rgba(1, 35, 56, 0.4) 55%,
rgba(1, 35, 56, 0) 100%
);
} }
} }
@@ -1371,14 +1428,13 @@ body {
color: #ffffff; color: #ffffff;
font-weight: 900; font-weight: 900;
letter-spacing: -0.05em; letter-spacing: -0.05em;
font-size: 1.25rem; font-size: 12px;
} }
.cost-desc { .cost-desc {
color: #ffffff; color: #ffffff;
font-size: 0.75rem; font-size: 8px;
font-weight: 500; font-weight: 500;
line-height: 1.25rem;
} }
.cost-note { .cost-note {
@@ -1407,6 +1463,12 @@ body {
color: #ffffff; color: #ffffff;
flex: 1; flex: 1;
text-align: center; text-align: center;
width: 75px;
transition: all 0.3s ease;
&:hover {
transform: scale(1.02);
}
/* Target styling tag svg di dalam kartu */ /* Target styling tag svg di dalam kartu */
svg { svg {
@@ -1418,19 +1480,19 @@ body {
.cost-type { .cost-type {
text-transform: uppercase; text-transform: uppercase;
color: #9ca3af; color: #9ca3af;
font-size: 0.75rem; font-size: 8px;
margin: 4px 0 0 0; margin: 4px 0 0 0;
} }
.cost-price { .cost-price {
font-weight: 900; font-weight: 900;
font-size: 0.875rem; font-size: 8px;
margin: 0; margin: 0;
} }
.cost-period { .cost-period {
color: #9ca3af; color: #9ca3af;
font-size: 0.875rem; font-size: 8px;
margin: 0; margin: 0;
} }
} }
@@ -1440,17 +1502,21 @@ body {
.cost-info-panel { .cost-info-panel {
padding: 32px; padding: 32px;
.cost-desc {
font-size: 10px;
}
.cost-item-card { .cost-item-card {
.cost-type { .cost-type {
font-size: 0.875rem; font-size: 8px;
} }
.cost-price { .cost-price {
font-size: 1rem; font-size: 12px;
} }
.cost-period { .cost-period {
font-size: 1rem; font-size: 10px;
} }
} }
} }
@@ -1468,10 +1534,12 @@ body {
/* Perubahan Arah Gradasi: Mengabur ke arah kiri panel Navy */ /* Perubahan Arah Gradasi: Mengabur ke arah kiri panel Navy */
.cost-banner-overlay { .cost-banner-overlay {
background: linear-gradient(270deg, background: linear-gradient(
#012338 0%, 270deg,
rgba(0, 0, 0, 0.4) 65%, #012338 0%,
rgba(0, 0, 0, 0) 78%); rgba(0, 0, 0, 0.4) 65%,
rgba(0, 0, 0, 0) 78%
);
} }
} }
@@ -1491,6 +1559,18 @@ body {
flex: none; flex: none;
width: 130px; width: 130px;
padding: 16px; padding: 16px;
.cost-type {
font-size: 12px;
}
.cost-price {
font-size: 1rem;
}
.cost-period {
font-size: 12px;
}
} }
} }
} }
@@ -1523,4 +1603,301 @@ body {
} }
} }
} }
} }
/* ============================================================
ANTI-FLICKER — localization.js sets window.__currentLang
synchronously; body stays hidden until DOMContentLoaded
fires applyTranslations() and removes this class.
============================================================ */
body.lang-pending {
visibility: hidden;
}
/* ============================================================
MOVED FROM index.html <style> TAG
(Originally inline; moved here so the browser can cache it)
============================================================ */
/* ===== 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;
}