diff --git a/admin.html b/admin.html
index 098fe8e..52617c3 100644
--- a/admin.html
+++ b/admin.html
@@ -1099,6 +1099,35 @@
}
}
+ /* ---- Custom Marker ---- */
+ .custom-marker {
+ display: flex; justify-content: center; align-items: center;
+ }
+ .marker-halo {
+ position: absolute;
+ width: 40px; height: 40px;
+ border-radius: 50%;
+ animation: pulse 2s infinite ease-out;
+ }
+ .marker-halo.spbu { background: rgba(34, 197, 94, 0.5); }
+ .marker-halo.spbu-off { background: rgba(245, 158, 11, 0.5); }
+ .marker-halo.ibadah { background: rgba(14, 165, 233, 0.5); }
+ .marker-halo.miskin { background: rgba(239, 68, 68, 0.5); }
+
+ .marker-icon-inner {
+ position: relative; z-index: 2;
+ width: 28px; height: 28px;
+ background: white; border-radius: 50%;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.5);
+ display: flex; justify-content: center; align-items: center;
+ font-size: 14px;
+ }
+
+ @keyframes pulse {
+ 0% { transform: scale(0.5); opacity: 1; }
+ 100% { transform: scale(1.5); opacity: 0; }
+ }
+
/* ---- Floating Panels ---- */
.search-panel {
position: absolute; top: 16px; left: 50%; transform: translateX(-50%);
diff --git a/js/app.js b/js/app.js
index 3a68875..4f06d42 100644
--- a/js/app.js
+++ b/js/app.js
@@ -989,15 +989,16 @@ function renderMapLayers() {
// Render SPBU
window.appState.data.spbu.forEach(item => {
- const color = item.status === '24jam' ? '#22c55e' : '#f59e0b';
- const marker = L.circleMarker([item.latitude, item.longitude], {
- radius: 6,
- fillColor: color,
- color: '#fff',
- weight: 2,
- opacity: 1,
- fillOpacity: 0.8
- }).bindPopup(`${item.nama}
Status: ${item.status === '24jam' ? '24 Jam' : 'Tidak 24 Jam'}`);
+ const is24 = item.status === '24jam';
+ const haloClass = is24 ? 'spbu' : 'spbu-off';
+ const customIcon = L.divIcon({
+ className: 'custom-marker',
+ html: `
⛽
`,
+ iconSize: [28, 28], iconAnchor: [14, 14], popupAnchor: [0, -14]
+ });
+
+ const marker = L.marker([item.latitude, item.longitude], { icon: customIcon })
+ .bindPopup(`${item.nama}
Status: ${is24 ? '24 Jam' : 'Tidak 24 Jam'}`);
const layer = item.status === '24jam' ? window.appState.layers.spbu24 : window.appState.layers.spbuTidak;
marker.addTo(layer);
@@ -1036,27 +1037,25 @@ function renderMapLayers() {
// Render Ibadah
window.appState.data.ibadah.forEach(item => {
- const marker = L.circleMarker([item.latitude, item.longitude], {
- radius: 5,
- fillColor: '#8b5cf6',
- color: '#fff',
- weight: 2,
- opacity: 1,
- fillOpacity: 0.8
- }).bindPopup(`${item.nama}
${item.alamat}`);
+ const ibadahIcon = L.divIcon({
+ className: 'custom-marker',
+ html: `🕌
`,
+ iconSize: [28, 28], iconAnchor: [14, 14], popupAnchor: [0, -14]
+ });
+ const marker = L.marker([item.latitude, item.longitude], { icon: ibadahIcon })
+ .bindPopup(`${item.nama}
${item.alamat}`);
marker.addTo(window.appState.layers.ibadah);
});
// Render Miskin
window.appState.data.miskin.forEach(item => {
- const marker = L.circleMarker([item.latitude, item.longitude], {
- radius: 5,
- fillColor: '#000000',
- color: '#fff',
- weight: 2,
- opacity: 1,
- fillOpacity: 0.8
- }).bindPopup(`${item.nama_kk}
${item.keterangan || ''}`);
+ const miskinIcon = L.divIcon({
+ className: 'custom-marker',
+ html: `🏠
`,
+ iconSize: [28, 28], iconAnchor: [14, 14], popupAnchor: [0, -14]
+ });
+ const marker = L.marker([item.latitude, item.longitude], { icon: miskinIcon })
+ .bindPopup(`${item.nama_kk}
${item.keterangan || ''}`);
marker.addTo(window.appState.layers.miskin);
});
}
diff --git a/poverty.html b/poverty.html
index b8ec6d0..6bd6d95 100644
--- a/poverty.html
+++ b/poverty.html
@@ -145,6 +145,33 @@
#loader { position: fixed; inset: 0; background: var(--clr-bg); color: var(--clr-text); display: flex; flex-direction: column; justify-content: center; align-items: center; z-index: 9999; gap: 12px; }
.loader-spinner { width: 36px; height: 36px; border: 3px solid var(--clr-border); border-top-color: var(--clr-danger); border-radius: 50%; animation: spin 0.8s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
+ /* ---- Custom Marker ---- */
+ .custom-marker {
+ display: flex; justify-content: center; align-items: center;
+ }
+ .marker-halo {
+ position: absolute;
+ width: 40px; height: 40px;
+ border-radius: 50%;
+ animation: pulse 2s infinite ease-out;
+ }
+ .marker-halo.ibadah { background: rgba(34, 197, 94, 0.5); }
+ .marker-halo.miskin-in { background: rgba(239, 68, 68, 0.5); }
+ .marker-halo.miskin-out { background: rgba(30, 41, 59, 0.5); }
+
+ .marker-icon-inner {
+ position: relative; z-index: 2;
+ width: 28px; height: 28px;
+ background: white; border-radius: 50%;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.5);
+ display: flex; justify-content: center; align-items: center;
+ font-size: 14px;
+ }
+
+ @keyframes pulse {
+ 0% { transform: scale(0.5); opacity: 1; }
+ 100% { transform: scale(1.5); opacity: 0; }
+ }
@@ -274,9 +301,23 @@
ibadahLayer = L.layerGroup().addTo(map);
miskinLayer = L.layerGroup().addTo(map);
- ibadahIcon = L.icon({ iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png', shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png', iconSize: [25,41], iconAnchor: [12,41], popupAnchor: [1,-34], shadowSize: [41,41] });
- miskinRedIcon = L.icon({ iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png', shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png', iconSize: [25,41], iconAnchor: [12,41], popupAnchor: [1,-34], shadowSize: [41,41] });
- miskinBlackIcon = L.icon({ iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-black.png', shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png', iconSize: [25,41], iconAnchor: [12,41], popupAnchor: [1,-34], shadowSize: [41,41] });
+ const ibadahCustomIcon = L.divIcon({
+ className: 'custom-marker',
+ html: `🕌
`,
+ iconSize: [28, 28], iconAnchor: [14, 14], popupAnchor: [0, -14]
+ });
+
+ const miskinInIcon = L.divIcon({
+ className: 'custom-marker',
+ html: `🏠
`,
+ iconSize: [28, 28], iconAnchor: [14, 14], popupAnchor: [0, -14]
+ });
+
+ const miskinOutIcon = L.divIcon({
+ className: 'custom-marker',
+ html: `🏠
`,
+ iconSize: [28, 28], iconAnchor: [14, 14], popupAnchor: [0, -14]
+ });
try {
const [ir, mr] = await Promise.all([fetch('backend/read_ibadah.php'), fetch('backend/read_miskin.php')]);
@@ -289,8 +330,6 @@
window.updateRadius = function(id, val) {
ibadahRadiuses[id] = parseInt(val);
- const el = document.getElementById('radius-val-' + id);
- if (el) el.textContent = val;
renderBuffers();
renderMiskin();
};
@@ -314,18 +353,23 @@
ibadahLayer.clearLayers(); ibadahPoints = [];
const bounds = [];
const f = document.getElementById('search-ibadah').value.toLowerCase();
- ibadahData.forEach(item => {
- if (f && !item.nama.toLowerCase().includes(f)) return;
- const lat = parseFloat(item.latitude), lng = parseFloat(item.longitude);
+ ibadahData.forEach(i => {
+ if (f && !i.nama.toLowerCase().includes(f)) return;
+ const lat = parseFloat(i.latitude), lng = parseFloat(i.longitude);
if (isNaN(lat) || isNaN(lng)) return;
- ibadahPoints.push({ id: item.id, pt: turf.point([lng, lat]) });
- const r = ibadahRadiuses[item.id] || 0;
- const popup = '' + item.nama + '
' + item.alamat + ''
- + '';
- L.marker([lat, lng], { icon: ibadahIcon }).bindPopup(popup, { minWidth: 220 }).addTo(ibadahLayer);
+ const rVal = ibadahRadiuses[i.id] || 0;
+ ibadahPoints.push({ id: i.id, geom: [lng, lat], rad: rVal });
+
+ const marker = L.marker([lat, lng], { icon: L.divIcon({ className: 'custom-marker', html: `🕌
`, iconSize: [28, 28], iconAnchor: [14, 14], popupAnchor: [0, -14] }) })
+ .bindPopup(`
+ ${i.nama}
${i.alamat}
+
+
+ ${rVal}
+ `)
+ .addTo(ibadahLayer);
bounds.push([lat, lng]);
});
@@ -339,18 +383,24 @@
miskinLayer.clearLayers();
const bounds = [];
const f = document.getElementById('search-miskin').value.toLowerCase();
- miskinData.forEach(item => {
- if (f && !item.nama_kk.toLowerCase().includes(f)) return;
- const lat = parseFloat(item.latitude), lng = parseFloat(item.longitude);
+ miskinData.forEach(m => {
+ if (f && !m.nama_kk.toLowerCase().includes(f)) return;
+ const lat = parseFloat(m.latitude), lng = parseFloat(m.longitude);
if (isNaN(lat) || isNaN(lng)) return;
- const pt = turf.point([lng, lat]);
+ const pt = [lng, lat];
let inRadius = false;
- for (const ib of ibadahPoints) {
- const km = ibadahRadiuses[ib.id] / 1000;
- if (km > 0 && turf.distance(pt, ib.pt, { units: 'kilometers' }) <= km) { inRadius = true; break; }
- }
- L.marker([lat, lng], { icon: inRadius ? miskinRedIcon : miskinBlackIcon })
- .bindPopup('KK: ' + item.nama_kk + '
' + (item.keterangan || '-') + '
Status: ' + (inRadius ? '🔴 Dalam Radius' : '⚫ Luar Radius'))
+ ibadahPoints.forEach(ib => {
+ if (ib.rad > 0 && turf.distance(turf.point(ib.geom), turf.point(pt), { units: 'meters' }) <= ib.rad) inRadius = true;
+ });
+
+ const icon = L.divIcon({
+ className: 'custom-marker',
+ html: `🏠
`,
+ iconSize: [28, 28], iconAnchor: [14, 14], popupAnchor: [0, -14]
+ });
+
+ L.marker([lat, lng], { icon: icon })
+ .bindPopup('KK: ' + m.nama_kk + '
' + (m.keterangan || '-') + '
Status: ' + (inRadius ? '🔴 Dalam Radius' : '⚫ Luar Radius'))
.addTo(miskinLayer);
bounds.push([lat, lng]);
});
diff --git a/spbu.html b/spbu.html
index c795348..a7dd760 100644
--- a/spbu.html
+++ b/spbu.html
@@ -147,6 +147,34 @@
.legend-panel h3 { font-size: 11px; font-weight: 700; color: var(--clr-muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 10px; }
.legend-row { display: flex; align-items: center; gap: 10px; margin-bottom: 7px; color: var(--clr-text); font-size: 12px; }
.legend-dot { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; }
+
+ /* ---- Custom Marker ---- */
+ .custom-marker {
+ display: flex; justify-content: center; align-items: center;
+ }
+ .marker-halo {
+ position: absolute;
+ width: 40px; height: 40px;
+ border-radius: 50%;
+ animation: pulse 2s infinite ease-out;
+ }
+ .marker-halo.spbu { background: rgba(34, 197, 94, 0.5); }
+ .marker-halo.spbu-off { background: rgba(245, 158, 11, 0.5); }
+ .marker-halo.user { background: rgba(59, 130, 246, 0.5); }
+
+ .marker-icon-inner {
+ position: relative; z-index: 2;
+ width: 28px; height: 28px;
+ background: white; border-radius: 50%;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.5);
+ display: flex; justify-content: center; align-items: center;
+ font-size: 14px;
+ }
+
+ @keyframes pulse {
+ 0% { transform: scale(0.5); opacity: 1; }
+ 100% { transform: scale(1.5); opacity: 0; }
+ }
@@ -275,9 +303,19 @@
if (filterText && !spbu.nama.toLowerCase().includes(filterText.toLowerCase()) && !spbu.no_spbu.toLowerCase().includes(filterText.toLowerCase())) return;
const lat = parseFloat(spbu.latitude), lng = parseFloat(spbu.longitude);
if (isNaN(lat) || isNaN(lng)) return;
- const color = spbu.status === '24jam' ? '#22c55e' : '#f59e0b';
- const marker = L.circleMarker([lat, lng], { radius: 7, fillColor: color, color: '#fff', weight: 2, opacity: 1, fillOpacity: 0.9 })
- .bindPopup('' + spbu.nama + '
No: ' + spbu.no_spbu + '
Status: ' + (spbu.status === '24jam' ? '✅ 24 Jam' : '⏰ Tidak 24 Jam'))
+
+ const is24 = spbu.status === '24jam';
+ const haloClass = is24 ? 'spbu' : 'spbu-off';
+ const customIcon = L.divIcon({
+ className: 'custom-marker',
+ html: `⛽
`,
+ iconSize: [28, 28],
+ iconAnchor: [14, 14],
+ popupAnchor: [0, -14]
+ });
+
+ const marker = L.marker([lat, lng], { icon: customIcon })
+ .bindPopup('' + spbu.nama + '
No: ' + spbu.no_spbu + '
Status: ' + (is24 ? '✅ 24 Jam' : '⏰ Tidak 24 Jam'))
.addTo(spbuLayer);
bounds.push([lat, lng]);
});
@@ -335,10 +373,11 @@
renderSPBU(spbu.nama.toLowerCase(), true);
};
- const userIcon = L.icon({
- iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png',
- shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
- iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41]
+ const userIcon = L.divIcon({
+ className: 'custom-marker',
+ html: `📍
`,
+ iconSize: [28, 28],
+ iconAnchor: [14, 14]
});
document.getElementById('btn-location').addEventListener('click', () => {