2nd commit: Menambahkan landing page, integrasi ikon FontAwesome secara global, dan perbaikan bug

This commit is contained in:
2026-06-12 16:06:49 +07:00
parent 7ee3db6d93
commit 4fe88f3b2e
10 changed files with 378 additions and 83 deletions
+15 -15
View File
@@ -95,7 +95,7 @@ const RightToolbar = L.Control.extend({
onAdd: function() {
const container = L.DomUtil.create('div', 'custom-leaflet-toolbar');
container.innerHTML = `
<button class="tool-btn" id="btn-tool-marker" title="Mode Marker" onclick="toggleMarkerMode()">📍</button>
<button class="tool-btn" id="btn-tool-marker" title="Mode Marker" onclick="toggleMarkerMode()"><i class='fa-solid fa-map-pin'></i></button>
`;
L.DomEvent.disableClickPropagation(container);
return container;
@@ -200,7 +200,7 @@ map.on('click', async function(e) {
// --- UTILITAS & KOSMETIK MODAL ---
function triggerToastNotification(pesan = "Aksi berhasil dieksekusi!") {
const toast = document.getElementById('toast-notification');
toast.innerText = pesan; toast.classList.add('show');
toast.innerHTML = pesan; toast.classList.add('show');
setTimeout(() => { toast.classList.remove('show'); }, 3000);
}
@@ -355,19 +355,19 @@ function renderSPBU() {
// KUNCI: Reverse Geocoding saat Drag-and-Drop
marker.on('dragend', async function(e) {
const pos = e.target.getLatLng();
triggerToastNotification(" Menganalisis alamat baru...");
triggerToastNotification("<i class='fa-solid fa-hourglass-half'></i> Menganalisis alamat baru...");
// Tunggu alamat baru dari satelit sebelum dikirim ke database
const alamatBaru = await dapatkanAlamat(pos.lat, pos.lng);
fetch('otak_spbu/api_update_coords.php', {
fetch('../otak_spbu/api_update_coords.php', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: d.id, lat: pos.lat, lng: pos.lng, alamat: alamatBaru })
}).then(res => res.ok ? res.json() : Promise.reject(res)).then(res => {
dataStore.spbu[d.id].latitude = pos.lat;
dataStore.spbu[d.id].longitude = pos.lng;
dataStore.spbu[d.id].alamat = alamatBaru;
triggerToastNotification("📍 Koordinat & Alamat berhasil digeser!");
triggerToastNotification("<i class='fa-solid fa-map-pin'></i> Koordinat & Alamat berhasil digeser!");
renderSPBU(); // Gambar ulang untuk update text di Sidebar
}).catch(err => {
alert("Gagal menyimpan posisi."); e.target.setLatLng([dataStore.spbu[d.id].latitude, dataStore.spbu[d.id].longitude]);
@@ -456,15 +456,15 @@ window.eksekusiSimpanBaru = () => {
lat: document.getElementById('spbu-lat').value,
lng: document.getElementById('spbu-lng').value
};
safeFetch('../otak_spbu/api_simpan.php', payload).then(() => { tutupModalSpbu(); triggerToastNotification(" SPBU ditambahkan!"); muatDataSPBU(); }).catch(err => alert(err));
safeFetch('../otak_spbu/api_simpan.php', payload).then(() => { tutupModalSpbu(); triggerToastNotification("<i class='fa-solid fa-check'></i> SPBU ditambahkan!"); muatDataSPBU(); }).catch(err => alert(err));
};
window.eksekusiUpdate = (id) => {
const payload = { id: id, nama: document.getElementById('spbu-nama').value, status: document.getElementById('spbu-status').value, telp: document.getElementById('spbu-telp').value };
safeFetch('../otak_spbu/api_update.php', payload).then(() => { tutupModalSpbu(); triggerToastNotification(" SPBU diperbarui!"); muatDataSPBU(); }).catch(err => alert(err));
safeFetch('../otak_spbu/api_update.php', payload).then(() => { tutupModalSpbu(); triggerToastNotification("<i class='fa-solid fa-check'></i> SPBU diperbarui!"); muatDataSPBU(); }).catch(err => alert(err));
};
window.eksekusiHapus = (id) => {
if(!confirm("Hapus SPBU ini?")) return;
safeFetch('../otak_spbu/api_hapus.php', { id: id }).then(() => { tutupModalSpbu(); triggerToastNotification("🗑️ SPBU dihapus."); muatDataSPBU(); }).catch(err => alert(err));
safeFetch('../otak_spbu/api_hapus.php', { id: id }).then(() => { tutupModalSpbu(); triggerToastNotification("<i class='fa-solid fa-trash'></i> SPBU dihapus."); muatDataSPBU(); }).catch(err => alert(err));
};
// --- JALAN ---
@@ -475,15 +475,15 @@ window.muatDataJalan = () => {
};
window.eksekusiSimpanJalan = () => {
const payload = { nama: document.getElementById('jalan-nama').value, status: document.getElementById('jalan-status').value, jarak: document.getElementById('jalan-jarak').value, koordinat: document.getElementById('jalan-koordinat').value };
safeFetch('../otak_jalan/api_simpan.php', payload).then(() => { batalGambarJalan(); triggerToastNotification(" Jalan ditambahkan!"); muatDataJalan(); }).catch(err => alert(err));
safeFetch('../otak_jalan/api_simpan.php', payload).then(() => { batalGambarJalan(); triggerToastNotification("<i class='fa-solid fa-check'></i> Jalan ditambahkan!"); muatDataJalan(); }).catch(err => alert(err));
};
window.eksekusiUpdateJalan = (id) => {
const payload = { id: id, nama: document.getElementById('jalan-nama').value, status: document.getElementById('jalan-status').value };
safeFetch('../otak_jalan/api_update.php', payload).then(() => { document.getElementById('modal-jalan').style.display='none'; triggerToastNotification(" Jalan diperbarui!"); muatDataJalan(); }).catch(err => alert(err));
safeFetch('../otak_jalan/api_update.php', payload).then(() => { document.getElementById('modal-jalan').style.display='none'; triggerToastNotification("<i class='fa-solid fa-check'></i> Jalan diperbarui!"); muatDataJalan(); }).catch(err => alert(err));
};
window.eksekusiHapusJalan = (id) => {
if(!confirm("Hapus jalur ini?")) return;
safeFetch('../otak_jalan/api_hapus.php', { id: id }).then(() => { document.getElementById('modal-jalan').style.display='none'; triggerToastNotification("🗑️ Jalan dihapus."); muatDataJalan(); }).catch(err => alert(err));
safeFetch('../otak_jalan/api_hapus.php', { id: id }).then(() => { document.getElementById('modal-jalan').style.display='none'; triggerToastNotification("<i class='fa-solid fa-trash'></i> Jalan dihapus."); muatDataJalan(); }).catch(err => alert(err));
};
// --- TANAH ---
@@ -494,15 +494,15 @@ window.muatDataTanah = () => {
};
window.eksekusiSimpanTanah = () => {
const payload = { nama: document.getElementById('tanah-nama').value, status: document.getElementById('tanah-status').value, luas: document.getElementById('tanah-luas').value, koordinat: document.getElementById('tanah-koordinat').value };
safeFetch('../otak_parsil/api_simpan.php', payload).then(() => { batalGambarTanah(); triggerToastNotification(" Tanah ditambahkan!"); muatDataTanah(); }).catch(err => alert(err));
safeFetch('../otak_parsil/api_simpan.php', payload).then(() => { batalGambarTanah(); triggerToastNotification("<i class='fa-solid fa-check'></i> Tanah ditambahkan!"); muatDataTanah(); }).catch(err => alert(err));
};
window.eksekusiUpdateTanah = (id) => {
const payload = { id: id, nama: document.getElementById('tanah-nama').value, status: document.getElementById('tanah-status').value };
safeFetch('../otak_parsil/api_update.php', payload).then(() => { document.getElementById('modal-tanah').style.display='none'; triggerToastNotification(" Tanah diperbarui!"); muatDataTanah(); }).catch(err => alert(err));
safeFetch('../otak_parsil/api_update.php', payload).then(() => { document.getElementById('modal-tanah').style.display='none'; triggerToastNotification("<i class='fa-solid fa-check'></i> Tanah diperbarui!"); muatDataTanah(); }).catch(err => alert(err));
};
window.eksekusiHapusTanah = (id) => {
if(!confirm("Hapus parsil ini?")) return;
safeFetch('../otak_parsil/api_hapus.php', { id: id }).then(() => { document.getElementById('modal-tanah').style.display='none'; triggerToastNotification("🗑️ Tanah dihapus."); muatDataTanah(); }).catch(err => alert(err));
safeFetch('../otak_parsil/api_hapus.php', { id: id }).then(() => { document.getElementById('modal-tanah').style.display='none'; triggerToastNotification("<i class='fa-solid fa-trash'></i> Tanah dihapus."); muatDataTanah(); }).catch(err => alert(err));
};
@@ -516,7 +516,7 @@ setTimeout(() => {
Promise.all([
muatDataSPBU()
]).then(() => {
console.log(" Sistem WebGIS V2 Siap dan Data Tersinkronisasi Penuh.");
console.log("<i class='fa-solid fa-check'></i> Sistem WebGIS V2 Siap dan Data Tersinkronisasi Penuh.");
}).catch(err => {
console.error("Kegagalan Fatal Jaringan:", err);
});
+2
View File
@@ -7,10 +7,12 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.css" />
<link rel="stylesheet" href="../assets/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
</head>
<body>
<div id="sidebar-container">
<a href="../" class="btn-back-home"> Kembali ke Beranda</a>
<div id="sidebar-header">
WebGis-Pontianak
</div>
+17 -17
View File
@@ -87,9 +87,9 @@ const RightToolbar = L.Control.extend({
onAdd: function() {
const container = L.DomUtil.create('div', 'custom-leaflet-toolbar');
container.innerHTML = `
<button class="tool-btn" id="btn-tool-marker" title="Mode Marker" onclick="toggleMarkerMode()">📍</button>
<button class="tool-btn" id="btn-tool-jalan" title="Mode Jalan" onclick="toggleRoadMode()">🛣️</button>
<button class="tool-btn" id="btn-tool-tanah" title="Mode Tanah" onclick="toggleLandMode()">🟩</button>
<button class="tool-btn" id="btn-tool-marker" title="Mode Marker" onclick="toggleMarkerMode()"><i class='fa-solid fa-map-pin'></i></button>
<button class="tool-btn" id="btn-tool-jalan" title="Mode Jalan" onclick="toggleRoadMode()"><i class='fa-solid fa-road'></i></button>
<button class="tool-btn" id="btn-tool-tanah" title="Mode Tanah" onclick="toggleLandMode()"><i class='fa-solid fa-draw-polygon'></i></button>
`;
L.DomEvent.disableClickPropagation(container);
return container;
@@ -183,7 +183,7 @@ map.on('click', async function(e) {
// --- UTILITAS & KOSMETIK MODAL ---
function triggerToastNotification(pesan = "Aksi berhasil dieksekusi!") {
const toast = document.getElementById('toast-notification');
toast.innerText = pesan; toast.classList.add('show');
toast.innerHTML = pesan; toast.classList.add('show');
setTimeout(() => { toast.classList.remove('show'); }, 3000);
}
@@ -337,19 +337,19 @@ function renderSPBU() {
// KUNCI: Reverse Geocoding saat Drag-and-Drop
marker.on('dragend', async function(e) {
const pos = e.target.getLatLng();
triggerToastNotification(" Menganalisis alamat baru...");
triggerToastNotification("<i class='fa-solid fa-hourglass-half'></i> Menganalisis alamat baru...");
// Tunggu alamat baru dari satelit sebelum dikirim ke database
const alamatBaru = await dapatkanAlamat(pos.lat, pos.lng);
fetch('otak_spbu/api_update_coords.php', {
fetch('../otak_spbu/api_update_coords.php', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: d.id, lat: pos.lat, lng: pos.lng, alamat: alamatBaru })
}).then(res => res.ok ? res.json() : Promise.reject(res)).then(res => {
dataStore.spbu[d.id].latitude = pos.lat;
dataStore.spbu[d.id].longitude = pos.lng;
dataStore.spbu[d.id].alamat = alamatBaru;
triggerToastNotification("📍 Koordinat & Alamat berhasil digeser!");
triggerToastNotification("<i class='fa-solid fa-map-pin'></i> Koordinat & Alamat berhasil digeser!");
renderSPBU(); // Gambar ulang untuk update text di Sidebar
}).catch(err => {
alert("Gagal menyimpan posisi."); e.target.setLatLng([dataStore.spbu[d.id].latitude, dataStore.spbu[d.id].longitude]);
@@ -436,15 +436,15 @@ window.eksekusiSimpanBaru = () => {
lat: document.getElementById('spbu-lat').value,
lng: document.getElementById('spbu-lng').value
};
safeFetch('../otak_spbu/api_simpan.php', payload).then(() => { tutupModalSpbu(); triggerToastNotification(" SPBU ditambahkan!"); muatDataSPBU(); }).catch(err => alert(err));
safeFetch('../otak_spbu/api_simpan.php', payload).then(() => { tutupModalSpbu(); triggerToastNotification("<i class='fa-solid fa-check'></i> SPBU ditambahkan!"); muatDataSPBU(); }).catch(err => alert(err));
};
window.eksekusiUpdate = (id) => {
const payload = { id: id, nama: document.getElementById('spbu-nama').value, status: document.getElementById('spbu-status').value, telp: document.getElementById('spbu-telp').value };
safeFetch('../otak_spbu/api_update.php', payload).then(() => { tutupModalSpbu(); triggerToastNotification(" SPBU diperbarui!"); muatDataSPBU(); }).catch(err => alert(err));
safeFetch('../otak_spbu/api_update.php', payload).then(() => { tutupModalSpbu(); triggerToastNotification("<i class='fa-solid fa-check'></i> SPBU diperbarui!"); muatDataSPBU(); }).catch(err => alert(err));
};
window.eksekusiHapus = (id) => {
if(!confirm("Hapus SPBU ini?")) return;
safeFetch('../otak_spbu/api_hapus.php', { id: id }).then(() => { tutupModalSpbu(); triggerToastNotification("🗑️ SPBU dihapus."); muatDataSPBU(); }).catch(err => alert(err));
safeFetch('../otak_spbu/api_hapus.php', { id: id }).then(() => { tutupModalSpbu(); triggerToastNotification("<i class='fa-solid fa-trash'></i> SPBU dihapus."); muatDataSPBU(); }).catch(err => alert(err));
};
// --- JALAN ---
@@ -455,15 +455,15 @@ window.muatDataJalan = () => {
};
window.eksekusiSimpanJalan = () => {
const payload = { nama: document.getElementById('jalan-nama').value, status: document.getElementById('jalan-status').value, jarak: document.getElementById('jalan-jarak').value, koordinat: document.getElementById('jalan-koordinat').value };
safeFetch('../otak_jalan/api_simpan.php', payload).then(() => { batalGambarJalan(); triggerToastNotification(" Jalan ditambahkan!"); muatDataJalan(); }).catch(err => alert(err));
safeFetch('../otak_jalan/api_simpan.php', payload).then(() => { batalGambarJalan(); triggerToastNotification("<i class='fa-solid fa-check'></i> Jalan ditambahkan!"); muatDataJalan(); }).catch(err => alert(err));
};
window.eksekusiUpdateJalan = (id) => {
const payload = { id: id, nama: document.getElementById('jalan-nama').value, status: document.getElementById('jalan-status').value };
safeFetch('../otak_jalan/api_update.php', payload).then(() => { document.getElementById('modal-jalan').style.display='none'; triggerToastNotification(" Jalan diperbarui!"); muatDataJalan(); }).catch(err => alert(err));
safeFetch('../otak_jalan/api_update.php', payload).then(() => { document.getElementById('modal-jalan').style.display='none'; triggerToastNotification("<i class='fa-solid fa-check'></i> Jalan diperbarui!"); muatDataJalan(); }).catch(err => alert(err));
};
window.eksekusiHapusJalan = (id) => {
if(!confirm("Hapus jalur ini?")) return;
safeFetch('../otak_jalan/api_hapus.php', { id: id }).then(() => { document.getElementById('modal-jalan').style.display='none'; triggerToastNotification("🗑️ Jalan dihapus."); muatDataJalan(); }).catch(err => alert(err));
safeFetch('../otak_jalan/api_hapus.php', { id: id }).then(() => { document.getElementById('modal-jalan').style.display='none'; triggerToastNotification("<i class='fa-solid fa-trash'></i> Jalan dihapus."); muatDataJalan(); }).catch(err => alert(err));
};
// --- TANAH ---
@@ -474,15 +474,15 @@ window.muatDataTanah = () => {
};
window.eksekusiSimpanTanah = () => {
const payload = { nama: document.getElementById('tanah-nama').value, status: document.getElementById('tanah-status').value, luas: document.getElementById('tanah-luas').value, koordinat: document.getElementById('tanah-koordinat').value };
safeFetch('../otak_parsil/api_simpan.php', payload).then(() => { batalGambarTanah(); triggerToastNotification(" Tanah ditambahkan!"); muatDataTanah(); }).catch(err => alert(err));
safeFetch('../otak_parsil/api_simpan.php', payload).then(() => { batalGambarTanah(); triggerToastNotification("<i class='fa-solid fa-check'></i> Tanah ditambahkan!"); muatDataTanah(); }).catch(err => alert(err));
};
window.eksekusiUpdateTanah = (id) => {
const payload = { id: id, nama: document.getElementById('tanah-nama').value, status: document.getElementById('tanah-status').value };
safeFetch('../otak_parsil/api_update.php', payload).then(() => { document.getElementById('modal-tanah').style.display='none'; triggerToastNotification(" Tanah diperbarui!"); muatDataTanah(); }).catch(err => alert(err));
safeFetch('../otak_parsil/api_update.php', payload).then(() => { document.getElementById('modal-tanah').style.display='none'; triggerToastNotification("<i class='fa-solid fa-check'></i> Tanah diperbarui!"); muatDataTanah(); }).catch(err => alert(err));
};
window.eksekusiHapusTanah = (id) => {
if(!confirm("Hapus parsil ini?")) return;
safeFetch('../otak_parsil/api_hapus.php', { id: id }).then(() => { document.getElementById('modal-tanah').style.display='none'; triggerToastNotification("🗑️ Tanah dihapus."); muatDataTanah(); }).catch(err => alert(err));
safeFetch('../otak_parsil/api_hapus.php', { id: id }).then(() => { document.getElementById('modal-tanah').style.display='none'; triggerToastNotification("<i class='fa-solid fa-trash'></i> Tanah dihapus."); muatDataTanah(); }).catch(err => alert(err));
};
@@ -498,7 +498,7 @@ setTimeout(() => {
muatDataJalan(),
muatDataTanah()
]).then(() => {
console.log(" Sistem WebGIS V2 Siap dan Data Tersinkronisasi Penuh.");
console.log("<i class='fa-solid fa-check'></i> Sistem WebGIS V2 Siap dan Data Tersinkronisasi Penuh.");
}).catch(err => {
console.error("Kegagalan Fatal Jaringan:", err);
});
+4 -2
View File
@@ -8,10 +8,12 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.css" />
<link rel="stylesheet" href="../assets/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
</head>
<body>
<div id="sidebar-container">
<a href="../" class="btn-back-home"> Kembali ke Beranda</a>
<div id="sidebar-header">
WebGis-Pontianak
</div>
@@ -23,8 +25,8 @@
</div>
<?php include '../components/modals.php'; ?>
<button id="btn-selesai-jalan" onclick="selesaiGambarJalan()"> Selesai Gambar Jalan</button>
<button id="btn-selesai-tanah" onclick="selesaiGambarTanah()"> Selesai Area Tanah</button>
<button id="btn-selesai-jalan" onclick="selesaiGambarJalan()"><i class='fa-solid fa-check'></i> Selesai Gambar Jalan</button>
<button id="btn-selesai-tanah" onclick="selesaiGambarTanah()"><i class='fa-solid fa-check'></i> Selesai Area Tanah</button>
<div id="map"></div>
+17 -17
View File
@@ -91,9 +91,9 @@ const RightToolbar = L.Control.extend({
onAdd: function() {
const container = L.DomUtil.create('div', 'custom-leaflet-toolbar');
container.innerHTML = `
<button class="tool-btn" id="btn-tool-marker" title="Mode Marker" onclick="toggleMarkerMode()">📍</button>
<button class="tool-btn" id="btn-tool-jalan" title="Mode Jalan" onclick="toggleRoadMode()">🛣️</button>
<button class="tool-btn" id="btn-tool-tanah" title="Mode Tanah" onclick="toggleLandMode()">🟩</button>
<button class="tool-btn" id="btn-tool-marker" title="Mode Marker" onclick="toggleMarkerMode()"><i class='fa-solid fa-map-pin'></i></button>
<button class="tool-btn" id="btn-tool-jalan" title="Mode Jalan" onclick="toggleRoadMode()"><i class='fa-solid fa-road'></i></button>
<button class="tool-btn" id="btn-tool-tanah" title="Mode Tanah" onclick="toggleLandMode()"><i class='fa-solid fa-draw-polygon'></i></button>
`;
L.DomEvent.disableClickPropagation(container);
return container;
@@ -198,7 +198,7 @@ map.on('click', async function(e) {
// --- UTILITAS & KOSMETIK MODAL ---
function triggerToastNotification(pesan = "Aksi berhasil dieksekusi!") {
const toast = document.getElementById('toast-notification');
toast.innerText = pesan; toast.classList.add('show');
toast.innerHTML = pesan; toast.classList.add('show');
setTimeout(() => { toast.classList.remove('show'); }, 3000);
}
@@ -353,19 +353,19 @@ function renderSPBU() {
// KUNCI: Reverse Geocoding saat Drag-and-Drop
marker.on('dragend', async function(e) {
const pos = e.target.getLatLng();
triggerToastNotification(" Menganalisis alamat baru...");
triggerToastNotification("<i class='fa-solid fa-hourglass-half'></i> Menganalisis alamat baru...");
// Tunggu alamat baru dari satelit sebelum dikirim ke database
const alamatBaru = await dapatkanAlamat(pos.lat, pos.lng);
fetch('otak_spbu/api_update_coords.php', {
fetch('../otak_spbu/api_update_coords.php', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: d.id, lat: pos.lat, lng: pos.lng, alamat: alamatBaru })
}).then(res => res.ok ? res.json() : Promise.reject(res)).then(res => {
dataStore.spbu[d.id].latitude = pos.lat;
dataStore.spbu[d.id].longitude = pos.lng;
dataStore.spbu[d.id].alamat = alamatBaru;
triggerToastNotification("📍 Koordinat & Alamat berhasil digeser!");
triggerToastNotification("<i class='fa-solid fa-map-pin'></i> Koordinat & Alamat berhasil digeser!");
renderSPBU(); // Gambar ulang untuk update text di Sidebar
}).catch(err => {
alert("Gagal menyimpan posisi."); e.target.setLatLng([dataStore.spbu[d.id].latitude, dataStore.spbu[d.id].longitude]);
@@ -454,15 +454,15 @@ window.eksekusiSimpanBaru = () => {
lat: document.getElementById('spbu-lat').value,
lng: document.getElementById('spbu-lng').value
};
safeFetch('../otak_spbu/api_simpan.php', payload).then(() => { tutupModalSpbu(); triggerToastNotification(" SPBU ditambahkan!"); muatDataSPBU(); }).catch(err => alert(err));
safeFetch('../otak_spbu/api_simpan.php', payload).then(() => { tutupModalSpbu(); triggerToastNotification("<i class='fa-solid fa-check'></i> SPBU ditambahkan!"); muatDataSPBU(); }).catch(err => alert(err));
};
window.eksekusiUpdate = (id) => {
const payload = { id: id, nama: document.getElementById('spbu-nama').value, status: document.getElementById('spbu-status').value, telp: document.getElementById('spbu-telp').value };
safeFetch('../otak_spbu/api_update.php', payload).then(() => { tutupModalSpbu(); triggerToastNotification(" SPBU diperbarui!"); muatDataSPBU(); }).catch(err => alert(err));
safeFetch('../otak_spbu/api_update.php', payload).then(() => { tutupModalSpbu(); triggerToastNotification("<i class='fa-solid fa-check'></i> SPBU diperbarui!"); muatDataSPBU(); }).catch(err => alert(err));
};
window.eksekusiHapus = (id) => {
if(!confirm("Hapus SPBU ini?")) return;
safeFetch('../otak_spbu/api_hapus.php', { id: id }).then(() => { tutupModalSpbu(); triggerToastNotification("🗑️ SPBU dihapus."); muatDataSPBU(); }).catch(err => alert(err));
safeFetch('../otak_spbu/api_hapus.php', { id: id }).then(() => { tutupModalSpbu(); triggerToastNotification("<i class='fa-solid fa-trash'></i> SPBU dihapus."); muatDataSPBU(); }).catch(err => alert(err));
};
// --- JALAN ---
@@ -473,15 +473,15 @@ window.muatDataJalan = () => {
};
window.eksekusiSimpanJalan = () => {
const payload = { nama: document.getElementById('jalan-nama').value, status: document.getElementById('jalan-status').value, jarak: document.getElementById('jalan-jarak').value, koordinat: document.getElementById('jalan-koordinat').value };
safeFetch('../otak_jalan/api_simpan.php', payload).then(() => { batalGambarJalan(); triggerToastNotification(" Jalan ditambahkan!"); muatDataJalan(); }).catch(err => alert(err));
safeFetch('../otak_jalan/api_simpan.php', payload).then(() => { batalGambarJalan(); triggerToastNotification("<i class='fa-solid fa-check'></i> Jalan ditambahkan!"); muatDataJalan(); }).catch(err => alert(err));
};
window.eksekusiUpdateJalan = (id) => {
const payload = { id: id, nama: document.getElementById('jalan-nama').value, status: document.getElementById('jalan-status').value };
safeFetch('../otak_jalan/api_update.php', payload).then(() => { document.getElementById('modal-jalan').style.display='none'; triggerToastNotification(" Jalan diperbarui!"); muatDataJalan(); }).catch(err => alert(err));
safeFetch('../otak_jalan/api_update.php', payload).then(() => { document.getElementById('modal-jalan').style.display='none'; triggerToastNotification("<i class='fa-solid fa-check'></i> Jalan diperbarui!"); muatDataJalan(); }).catch(err => alert(err));
};
window.eksekusiHapusJalan = (id) => {
if(!confirm("Hapus jalur ini?")) return;
safeFetch('../otak_jalan/api_hapus.php', { id: id }).then(() => { document.getElementById('modal-jalan').style.display='none'; triggerToastNotification("🗑️ Jalan dihapus."); muatDataJalan(); }).catch(err => alert(err));
safeFetch('../otak_jalan/api_hapus.php', { id: id }).then(() => { document.getElementById('modal-jalan').style.display='none'; triggerToastNotification("<i class='fa-solid fa-trash'></i> Jalan dihapus."); muatDataJalan(); }).catch(err => alert(err));
};
// --- TANAH ---
@@ -492,15 +492,15 @@ window.muatDataTanah = () => {
};
window.eksekusiSimpanTanah = () => {
const payload = { nama: document.getElementById('tanah-nama').value, status: document.getElementById('tanah-status').value, luas: document.getElementById('tanah-luas').value, koordinat: document.getElementById('tanah-koordinat').value };
safeFetch('../otak_parsil/api_simpan.php', payload).then(() => { batalGambarTanah(); triggerToastNotification(" Tanah ditambahkan!"); muatDataTanah(); }).catch(err => alert(err));
safeFetch('../otak_parsil/api_simpan.php', payload).then(() => { batalGambarTanah(); triggerToastNotification("<i class='fa-solid fa-check'></i> Tanah ditambahkan!"); muatDataTanah(); }).catch(err => alert(err));
};
window.eksekusiUpdateTanah = (id) => {
const payload = { id: id, nama: document.getElementById('tanah-nama').value, status: document.getElementById('tanah-status').value };
safeFetch('../otak_parsil/api_update.php', payload).then(() => { document.getElementById('modal-tanah').style.display='none'; triggerToastNotification(" Tanah diperbarui!"); muatDataTanah(); }).catch(err => alert(err));
safeFetch('../otak_parsil/api_update.php', payload).then(() => { document.getElementById('modal-tanah').style.display='none'; triggerToastNotification("<i class='fa-solid fa-check'></i> Tanah diperbarui!"); muatDataTanah(); }).catch(err => alert(err));
};
window.eksekusiHapusTanah = (id) => {
if(!confirm("Hapus parsil ini?")) return;
safeFetch('../otak_parsil/api_hapus.php', { id: id }).then(() => { document.getElementById('modal-tanah').style.display='none'; triggerToastNotification("🗑️ Tanah dihapus."); muatDataTanah(); }).catch(err => alert(err));
safeFetch('../otak_parsil/api_hapus.php', { id: id }).then(() => { document.getElementById('modal-tanah').style.display='none'; triggerToastNotification("<i class='fa-solid fa-trash'></i> Tanah dihapus."); muatDataTanah(); }).catch(err => alert(err));
};
@@ -516,7 +516,7 @@ setTimeout(() => {
muatDataJalan(),
muatDataTanah()
]).then(() => {
console.log(" Sistem WebGIS V2 Siap dan Data Tersinkronisasi Penuh.");
console.log("<i class='fa-solid fa-check'></i> Sistem WebGIS V2 Siap dan Data Tersinkronisasi Penuh.");
}).catch(err => {
console.error("Kegagalan Fatal Jaringan:", err);
});
+6 -4
View File
@@ -7,18 +7,20 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.css" />
<link rel="stylesheet" href="../assets/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
</head>
<body>
<div id="sidebar-container">
<a href="../" class="btn-back-home"> Kembali ke Beranda</a>
<div id="sidebar-header">
WebGis-Pontianak
</div>
<div id="filter-bar">
<button class="filter-tool-btn" id="btn-filter-layer" title="Filter Layer" onclick="bukaModalFilterLayer()">⚙️</button>
<button class="filter-tool-btn" id="btn-filter-layer" title="Filter Layer" onclick="bukaModalFilterLayer()"><i class='fa-solid fa-gear'></i></button>
<span class="filter-label">Status SPBU:</span>
<button class="filter-tool-btn" id="btn-filter-spbu" title="Filter SPBU" onclick="bukaModalFilterSPBU()">🔍</button>
<button class="filter-tool-btn" id="btn-filter-spbu" title="Filter SPBU" onclick="bukaModalFilterSPBU()"><i class='fa-solid fa-filter'></i></button>
</div>
<div id="card-container" class="card-list-area">
@@ -29,8 +31,8 @@
<?php include '../components/modals.php'; ?>
<button id="btn-selesai-jalan" onclick="selesaiGambarJalan()"> Selesai Gambar Jalan</button>
<button id="btn-selesai-tanah" onclick="selesaiGambarTanah()"> Selesai Area Tanah</button>
<button id="btn-selesai-jalan" onclick="selesaiGambarJalan()"><i class='fa-solid fa-check'></i> Selesai Gambar Jalan</button>
<button id="btn-selesai-tanah" onclick="selesaiGambarTanah()"><i class='fa-solid fa-check'></i> Selesai Area Tanah</button>
<div id="map"></div>
+19 -19
View File
@@ -86,7 +86,7 @@ async function dapatkanAlamat(lat, lng) {
function triggerToastNotification(pesan) {
const toast = document.getElementById('toast-notification');
toast.innerText = pesan; toast.classList.add('show');
toast.innerHTML = pesan; toast.classList.add('show');
setTimeout(() => { toast.classList.remove('show'); }, 3000);
}
@@ -187,7 +187,7 @@ window.toggleKemiskinanMode = () => {
const wasActive = state.isKemiskinanMode; resetAllModes();
if (!wasActive) {
state.isKemiskinanMode = true; const btn = document.getElementById('btn-add-kemiskinan');
btn.classList.add('active'); btn.innerHTML = `<span class="icon">📍</span> Mode Penempatan Aktif`;
btn.classList.add('active'); btn.innerHTML = `<span class="icon"><i class='fa-solid fa-map-pin'></i></span> Mode Penempatan Aktif`;
map.getContainer().style.cursor = 'none';
}
};
@@ -198,7 +198,7 @@ const RightToolbar = L.Control.extend({
options: { position: 'topright' },
onAdd: function() {
const c = L.DomUtil.create('div', 'custom-leaflet-toolbar');
c.innerHTML = `<button class="tool-btn" id="btn-tool-marker" title="SPBU" onclick="toggleMarkerMode()">📍</button><button class="tool-btn" id="btn-tool-jalan" title="Jalan" onclick="toggleRoadMode()">🛣️</button><button class="tool-btn" id="btn-tool-tanah" title="Tanah" onclick="toggleLandMode()">🟩</button>`;
c.innerHTML = `<button class="tool-btn" id="btn-tool-marker" title="SPBU" onclick="toggleMarkerMode()"><i class='fa-solid fa-map-pin'></i></button><button class="tool-btn" id="btn-tool-jalan" title="Jalan" onclick="toggleRoadMode()"><i class='fa-solid fa-road'></i></button><button class="tool-btn" id="btn-tool-tanah" title="Tanah" onclick="toggleLandMode()"><i class='fa-solid fa-draw-polygon'></i></button>`;
L.DomEvent.disableClickPropagation(c); return c;
}
});
@@ -382,13 +382,13 @@ function renderSPBU() {
marker.on('dragend', async function(e) {
const pos = e.target.getLatLng();
triggerToastNotification(" Menganalisis alamat baru...");
triggerToastNotification("<i class='fa-solid fa-hourglass-half'></i> Menganalisis alamat baru...");
const alamatBaru = await dapatkanAlamat(pos.lat, pos.lng);
fetch('../otak_spbu/api_update_coords.php', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: d.id, lat: pos.lat, lng: pos.lng, alamat: alamatBaru })
}).then(res => res.ok ? res.json() : Promise.reject(res)).then(res => {
dataStore.spbu[d.id].latitude = pos.lat; dataStore.spbu[d.id].longitude = pos.lng; dataStore.spbu[d.id].alamat = alamatBaru;
triggerToastNotification("📍 Koordinat & Alamat berhasil digeser!"); renderSPBU();
triggerToastNotification("<i class='fa-solid fa-map-pin'></i> Koordinat & Alamat berhasil digeser!"); renderSPBU();
}).catch(err => { alert("Gagal."); e.target.setLatLng([dataStore.spbu[d.id].latitude, dataStore.spbu[d.id].longitude]); });
});
@@ -771,11 +771,11 @@ window.eksekusiUpdateGaris = () => {
if(!confirm(`PERINGATAN: Mengubah garis kemiskinan menjadi Rp${val} akan mengkalibrasi ulang status miskin/tidak miskin seluruh penduduk di database saat ini.\n\nLanjutkan?`)) return;
triggerToastNotification(" Mengkalibrasi ulang seluruh data penduduk...");
triggerToastNotification("<i class='fa-solid fa-hourglass-half'></i> Mengkalibrasi ulang seluruh data penduduk...");
safeFetch('../otak_kemiskinan/api_update_gariskemiskinan.php', { nilai: parseInt(val) })
.then(res => {
triggerToastNotification(" " + res.pesan);
triggerToastNotification("<i class='fa-solid fa-check'></i> " + res.pesan);
document.getElementById('input-garis-kemiskinan').value = "";
muatDataSemua();
})
@@ -786,19 +786,19 @@ window.eksekusiUpdateGaris = () => {
window.eksekusiSimpanBaru = () => {
const payload = { nama: document.getElementById('spbu-nama').value, status: document.getElementById('spbu-status').value, telp: document.getElementById('spbu-telp').value, alamat: document.getElementById('spbu-alamat') ? document.getElementById('spbu-alamat').value : "", lat: document.getElementById('spbu-lat').value, lng: document.getElementById('spbu-lng').value };
safeFetch('../otak_spbu/api_simpan.php', payload)
.then(() => { document.getElementById('modal-spbu').style.display='none'; triggerToastNotification(" SPBU ditambahkan!"); muatDataSemua(); })
.then(() => { document.getElementById('modal-spbu').style.display='none'; triggerToastNotification("<i class='fa-solid fa-check'></i> SPBU ditambahkan!"); muatDataSemua(); })
.catch(err => alert(`Gagal menyimpan data.\nAlasan: ${err.message}\nPastikan nama dan lokasi file PHP Anda benar.`));
};
window.eksekusiUpdate = (id) => {
const payload = { id: id, nama: document.getElementById('spbu-nama').value, status: document.getElementById('spbu-status').value, telp: document.getElementById('spbu-telp').value };
safeFetch('../otak_spbu/api_update.php', payload)
.then(() => { document.getElementById('modal-spbu').style.display='none'; triggerToastNotification(" SPBU diperbarui!"); muatDataSemua(); })
.then(() => { document.getElementById('modal-spbu').style.display='none'; triggerToastNotification("<i class='fa-solid fa-check'></i> SPBU diperbarui!"); muatDataSemua(); })
.catch(err => alert(err));
};
window.eksekusiHapus = (id) => {
if(!confirm("Hapus SPBU ini?")) return;
safeFetch('../otak_spbu/api_hapus.php', { id: id })
.then(() => { document.getElementById('modal-spbu').style.display='none'; triggerToastNotification("🗑️ SPBU dihapus."); muatDataSemua(); })
.then(() => { document.getElementById('modal-spbu').style.display='none'; triggerToastNotification("<i class='fa-solid fa-trash'></i> SPBU dihapus."); muatDataSemua(); })
.catch(err => alert(err));
};
@@ -806,19 +806,19 @@ window.eksekusiHapus = (id) => {
window.eksekusiSimpanJalan = () => {
const payload = { nama: document.getElementById('jalan-nama').value, status: document.getElementById('jalan-status').value, jarak: document.getElementById('jalan-jarak').value, koordinat: document.getElementById('jalan-koordinat').value };
safeFetch('../otak_jalan/api_simpan.php', payload)
.then(() => { batalGambarJalan(); triggerToastNotification(" Jalan ditambahkan!"); muatDataSemua(); })
.then(() => { batalGambarJalan(); triggerToastNotification("<i class='fa-solid fa-check'></i> Jalan ditambahkan!"); muatDataSemua(); })
.catch(err => alert(err));
};
window.eksekusiUpdateJalan = (id) => {
const payload = { id: id, nama: document.getElementById('jalan-nama').value, status: document.getElementById('jalan-status').value };
safeFetch('../otak_jalan/api_update.php', payload)
.then(() => { document.getElementById('modal-jalan').style.display='none'; triggerToastNotification(" Jalan diperbarui!"); muatDataSemua(); })
.then(() => { document.getElementById('modal-jalan').style.display='none'; triggerToastNotification("<i class='fa-solid fa-check'></i> Jalan diperbarui!"); muatDataSemua(); })
.catch(err => alert(err));
};
window.eksekusiHapusJalan = (id) => {
if(!confirm("Hapus jalur ini?")) return;
safeFetch('../otak_jalan/api_hapus.php', { id: id })
.then(() => { document.getElementById('modal-jalan').style.display='none'; triggerToastNotification("🗑️ Jalan dihapus."); muatDataSemua(); })
.then(() => { document.getElementById('modal-jalan').style.display='none'; triggerToastNotification("<i class='fa-solid fa-trash'></i> Jalan dihapus."); muatDataSemua(); })
.catch(err => alert(err));
};
@@ -923,7 +923,7 @@ window.updatePendudukPopup = (id, popupRef) => {
window.eksekusiHapusPenduduk = (id) => {
safeFetch('../otak_kemiskinan/api_hapus_penduduk.php', { id: id })
.then(() => {
triggerToastNotification("🗑️ Data Penduduk dihapus permanen.");
triggerToastNotification("<i class='fa-solid fa-trash'></i> Data Penduduk dihapus permanen.");
muatDataSemua();
})
.catch(err => alert(err));
@@ -963,7 +963,7 @@ window.eksekusiSimpanLogBantuan = () => {
safeFetch('../otak_kemiskinan/api_simpan_logbantuan.php', payload)
.then(() => {
document.getElementById('modal-log-bantuan').style.display = 'none';
triggerToastNotification(" Log bantuan berhasil dicatat.");
triggerToastNotification("<i class='fa-solid fa-check'></i> Log bantuan berhasil dicatat.");
})
.catch(err => alert("Gagal menyimpan log bantuan: " + err));
};
@@ -1023,7 +1023,7 @@ window.simpanIbadahPopup = (lat, lng, popupRef) => {
.then(() => {
map.closePopup(popupRef);
resetAllModes();
triggerToastNotification(" Rumah Ibadah berhasil ditambahkan!");
triggerToastNotification("<i class='fa-solid fa-check'></i> Rumah Ibadah berhasil ditambahkan!");
muatDataSemua();
})
.catch(err => alert("Gagal menyimpan data: " + err));
@@ -1081,7 +1081,7 @@ window.updateIbadahPopup = (id, popupRef) => {
.then(() => {
map.closePopup(popupRef);
resetAllModes();
triggerToastNotification(" Data Rumah Ibadah berhasil diperbarui.");
triggerToastNotification("<i class='fa-solid fa-check'></i> Data Rumah Ibadah berhasil diperbarui.");
muatDataSemua();
})
.catch(err => alert("Gagal mengupdate data: " + err));
@@ -1090,7 +1090,7 @@ window.updateIbadahPopup = (id, popupRef) => {
window.eksekusiHapusIbadah = (id) => {
safeFetch('../otak_kemiskinan/api_hapus_ibadah.php', { id: id })
.then(() => {
triggerToastNotification("🗑️ Rumah Ibadah dihapus permanen.");
triggerToastNotification("<i class='fa-solid fa-trash'></i> Rumah Ibadah dihapus permanen.");
muatDataSemua();
})
.catch(err => alert(err));
@@ -1119,7 +1119,7 @@ window.muatDataSemua = () => {
}).catch(e => console.warn("API Garis Kemiskinan Offline"))
]).then(() => {
renderKemiskinan();
console.log(" Sistem WebGIS V3 Siap dan Data Tersinkronisasi Penuh.");
console.log("<i class='fa-solid fa-check'></i> Sistem WebGIS V3 Siap dan Data Tersinkronisasi Penuh.");
});
};
+10 -8
View File
@@ -7,10 +7,12 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.css" />
<link rel="stylesheet" href="../assets/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
</head>
<body>
<div id="sidebar-container">
<a href="../" class="btn-back-home"> Kembali ke Beranda</a>
<div id="sidebar-header">WebGis-Pontianak</div>
<!-- Tab Navigasi -->
<div class="sidebar-tabs">
@@ -20,7 +22,7 @@
<!-- TAB 1: KEMISKINAN (BARU) -->
<div id="tab-kemiskinan" class="tab-content active">
<div class="control-panel">
<button class="btn-gps" onclick="lokasiSaya()">📍 Arahkan ke Lokasi Saya</button>
<button class="btn-gps" onclick="lokasiSaya()"><i class='fa-solid fa-location-crosshairs'></i> Arahkan ke Lokasi Saya</button>
<!-- Input Garis Kemiskinan -->
<div class="add-marker-box" style="margin-bottom: 0; padding: 10px 12px;">
<label>Batas Garis Kemiskinan (Rp):</label>
@@ -31,14 +33,14 @@
</div>
<!-- Tombol Kelola Program Pelatihan -->
<button class="btn btn-warning" onclick="bukaModalPelatihan()" style="margin-bottom: 10px; width: 100%; display: flex; align-items: center; justify-content: center; gap: 8px;">
<span style="font-size: 1.2rem;">📋</span> Kelola Program Pelatihan
<span style="font-size: 1.2rem;"><i class='fa-solid fa-clipboard-list'></i></span> Kelola Program Pelatihan
</button>
<!-- Tombol Tambah Marker Modern -->
<div class="modern-action-box">
<label>Mode Penambahan Data:</label>
<select id="pilihan-marker-baru" class="modern-select">
<option value="ibadah">🕌 Pusat Radius (Rumah Ibadah)</option>
<option value="penduduk">🏠 Data Keluarga (Penduduk)</option>
<option value="ibadah"><i class='fa-solid fa-mosque'></i> Pusat Radius (Rumah Ibadah)</option>
<option value="penduduk"><i class='fa-solid fa-house-chimney-user'></i> Data Keluarga (Penduduk)</option>
</select>
<button class="btn-modern-add" id="btn-add-kemiskinan" onclick="toggleKemiskinanMode()">
<span class="icon">+</span> Aktifkan Kursor Marker
@@ -59,9 +61,9 @@
<!-- TAB 2: FILTER & SPBU (LAMA) -->
<div id="tab-filter" class="tab-content">
<div id="filter-bar">
<button class="filter-tool-btn" onclick="bukaModalFilterLayer()">⚙️</button>
<button class="filter-tool-btn" onclick="bukaModalFilterLayer()"><i class='fa-solid fa-gear'></i></button>
<span class="filter-label">Status SPBU:</span>
<button class="filter-tool-btn" onclick="bukaModalFilterSPBU()">🔍</button>
<button class="filter-tool-btn" onclick="bukaModalFilterSPBU()"><i class='fa-solid fa-filter'></i></button>
</div>
<div id="card-container" class="card-list-area"></div>
</div>
@@ -71,8 +73,8 @@
<?php include '../components/modals.php'; ?>
<button id="btn-selesai-jalan" onclick="selesaiGambarJalan()"> Selesai Gambar Jalan</button>
<button id="btn-selesai-tanah" onclick="selesaiGambarTanah()"> Selesai Area Tanah</button>
<button id="btn-selesai-jalan" onclick="selesaiGambarJalan()"><i class='fa-solid fa-check'></i> Selesai Gambar Jalan</button>
<button id="btn-selesai-tanah" onclick="selesaiGambarTanah()"><i class='fa-solid fa-check'></i> Selesai Area Tanah</button>
<div id="map"></div>
+3 -1
View File
@@ -737,4 +737,6 @@ input[type=range]::-webkit-slider-runnable-track {
.auth-tab.active {
background: var(--blue);
color: var(--white);
}
}
.btn-back-home { display: block; background: var(--navy); color: #A0AEC0; text-align: center; padding: 10px; text-decoration: none; font-weight: 600; font-size: 0.85rem; border-bottom: 1px solid rgba(255,255,255,0.1); transition: all 0.3s; }
.btn-back-home:hover { background: #2a4365; color: #fff; }
+285
View File
@@ -0,0 +1,285 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebGIS Kota Pontianak - Portal Utama</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--primary: #2B6CB0;
--secondary: #2C5282;
--accent: #4FD1C5;
--dark: #1A202C;
--light: #F7FAFC;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Inter', sans-serif;
background: linear-gradient(135deg, #0f2027 0%, #203a43 50%, #2c5364 100%);
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: var(--light);
overflow-x: hidden;
padding: 2rem;
}
/* Latar Belakang Animasi Geometris */
.bg-pattern {
position: fixed;
top: 0; left: 0; width: 100vw; height: 100vh;
background-image: radial-gradient(rgba(255, 255, 255, 0.1) 2px, transparent 2px);
background-size: 30px 30px;
z-index: -1;
opacity: 0.5;
}
/* Tekstur Kertas Elegan */
.paper-texture {
position: fixed;
top: 0; left: 0; width: 100vw; height: 100vh;
background-image: url('https://www.transparenttextures.com/patterns/paper.png');
opacity: 0.4;
z-index: -2;
pointer-events: none;
}
.header-container {
text-align: center;
margin-bottom: 3rem;
animation: fadeInDown 1s ease-out;
}
.header-container h1 {
font-size: 3.5rem;
font-weight: 800;
background: linear-gradient(90deg, #63B3ED, #4FD1C5);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 0.5rem;
letter-spacing: -1px;
}
.header-container p {
font-size: 1.2rem;
color: #A0AEC0;
max-width: 600px;
margin: 0 auto;
line-height: 1.6;
}
.grid-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 2rem;
width: 100%;
max-width: 1100px;
perspective: 1000px;
}
.card {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(15px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16px;
padding: 2rem;
text-decoration: none;
color: var(--light);
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
animation: fadeInUp 0.8s ease-out backwards;
}
/* Staggered animation */
.card:nth-child(1) { animation-delay: 0.1s; }
.card:nth-child(2) { animation-delay: 0.2s; }
.card:nth-child(3) { animation-delay: 0.3s; }
.card:nth-child(4) { animation-delay: 0.4s; }
.card::before {
content: '';
position: absolute;
top: 0; left: 0; width: 100%; height: 100%;
background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 100%);
z-index: 0;
}
.card:hover {
transform: translateY(-10px) scale(1.02) rotateX(2deg);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4);
border-color: var(--accent);
background: rgba(255, 255, 255, 0.1);
}
.card-content {
position: relative;
z-index: 1;
flex-grow: 1;
}
.card-number {
font-size: 4rem;
font-weight: 800;
color: rgba(255, 255, 255, 0.05);
position: absolute;
top: -20px;
right: -10px;
transition: color 0.4s;
}
.card:hover .card-number {
color: rgba(79, 209, 197, 0.2);
}
.card h2 {
font-size: 1.5rem;
margin-bottom: 1rem;
font-weight: 600;
display: flex;
align-items: center;
gap: 10px;
}
.card p {
color: #CBD5E0;
font-size: 0.95rem;
line-height: 1.5;
margin-bottom: 1.5rem;
}
.card-btn {
margin-top: auto;
display: inline-flex;
align-items: center;
justify-content: center;
background: linear-gradient(90deg, var(--primary), var(--secondary));
color: white;
padding: 10px 20px;
border-radius: 8px;
font-weight: 600;
text-transform: uppercase;
font-size: 0.85rem;
letter-spacing: 1px;
transition: all 0.3s;
border: none;
width: fit-content;
}
.card:hover .card-btn {
background: linear-gradient(90deg, var(--accent), #319795);
box-shadow: 0 4px 15px rgba(79, 209, 197, 0.4);
transform: translateX(5px);
}
.footer {
margin-top: 4rem;
text-align: center;
border-top: 1px solid rgba(255, 255, 255, 0.1);
padding-top: 2rem;
width: 100%;
max-width: 800px;
animation: fadeIn 1s ease-out 1s backwards;
}
.footer p {
color: #A0AEC0;
font-size: 0.9rem;
margin-bottom: 5px;
}
.footer .highlight {
color: var(--accent);
font-weight: 600;
}
@keyframes fadeInDown {
from { opacity: 0; transform: translateY(-30px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
</style>
</head>
<body>
<div class="paper-texture"></div>
<div class="bg-pattern"></div>
<div class="header-container">
<h1>WebGIS Pontianak</h1>
<p>Evolusi Pemetaan Infrastruktur Spasial Terpusat. Silakan pilih iterasi arsitektur di bawah ini untuk memulai eksplorasi peta.</p>
</div>
<div class="grid-container">
<!-- Versi 001 -->
<a href="001/" class="card">
<span class="card-number">01</span>
<div class="card-content">
<h2><i class="fa-solid fa-map-location-dot" style="color: var(--accent);"></i> Iterasi V1</h2>
<p>Versi pondasi awal pemetaan dasar. Menampilkan integrasi map dengan data SPBU sederhana menggunakan arsitektur lawas.</p>
<div class="card-btn">Buka Peta</div>
</div>
</a>
<!-- Versi 002 -->
<a href="002/" class="card">
<span class="card-number">02</span>
<div class="card-content">
<h2><i class="fa-solid fa-eye" style="color: var(--accent);"></i> Iterasi V2</h2>
<p>Fokus pada visualisasi murni. Menampilkan SPBU, Jaringan Jalan, dan Parsil Tanah secara keseluruhan tanpa distraksi fitur filter.</p>
<div class="card-btn">Buka Peta</div>
</div>
</a>
<!-- Versi 003 -->
<a href="003/" class="card">
<span class="card-number">03</span>
<div class="card-content">
<h2><i class="fa-solid fa-layer-group" style="color: var(--accent);"></i> Iterasi V3</h2>
<p>Memperkenalkan <i>Smart Filter</i> & <i>Layer Control</i>. Berinteraksi dengan data spasial dengan cara menyaring status secara real-time.</p>
<div class="card-btn">Buka Peta</div>
</div>
</a>
<!-- Versi 004 -->
<a href="004/" class="card">
<span class="card-number">04</span>
<div class="card-content">
<h2><i class="fa-solid fa-cubes" style="color: var(--accent);"></i> Iterasi V4</h2>
<p>Arsitektur Lanjutan. Dilengkapi dengan infrastruktur navigasi terpusat dan pemisahan logika backend API secara modular.</p>
<div class="card-btn">Buka Peta</div>
</div>
</a>
</div>
<div class="footer">
<p>Proyek Akhir Mata Kuliah Sistem Informasi Geografis (SIG)</p>
<p>Dikembangkan oleh <span class="highlight">Nelson Davey</span> | NIM: <span class="highlight">D1041231058</span></p>
</div>
</body>
</html>