update admin light/dark mode

This commit is contained in:
naukyy
2026-06-13 00:31:36 +07:00
parent 2c123f5af2
commit 1c96173d55
28 changed files with 2703 additions and 775 deletions
+229 -87
View File
@@ -1,33 +1,88 @@
/* ═══════════════════════════════════════════════════
WebGIS P01 — main.css | Dark / Light Theme
═══════════════════════════════════════════════════ */
/* ── Design Tokens ── */
:root {
--primary: #6366F1;
--primary-hover: #4F46E5;
--bg-dark: #0F172A;
--bg-glass: rgba(15, 23, 42, 0.75);
--border-glass: rgba(255, 255, 255, 0.1);
--text-light: #F8FAFC;
--text-muted: #94A3B8;
--radius: 20px;
--shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
--ease: cubic-bezier(0.4, 0, 0.2, 1);
--transition: all 0.25s var(--ease);
--font: 'Inter', sans-serif;
--radius: 16px;
}
* { box-sizing: border-box; }
/* ── Dark Theme (default) ── */
[data-theme="dark"] {
--primary: #818CF8;
--primary-deep: #6366F1;
--primary-glow: rgba(99,102,241,0.3);
--bg-dark: #0A0F1E;
--bg-glass: rgba(17,24,39,0.82);
--bg-input: rgba(255,255,255,0.05);
--border-glass: rgba(255,255,255,0.08);
--border-focus: #6366F1;
--text-main: #F1F5F9;
--text-muted: #94A3B8;
--shadow: 0 25px 50px -12px rgba(0,0,0,0.55);
--dot-color: rgba(255,255,255,0.025);
--btn-spbu: #10B981;
--btn-jalan: #F59E0B;
--btn-kavling: #3B82F6;
--btn-rumah: #8B5CF6;
--btn-warga: #EF4444;
}
/* ── Light Theme ── */
[data-theme="light"] {
--primary: #4F46E5;
--primary-deep: #3730A3;
--primary-glow: rgba(79,70,229,0.15);
--bg-dark: #F0F4FF;
--bg-glass: rgba(255,255,255,0.92);
--bg-input: #FFFFFF;
--border-glass: #E2E8F0;
--border-focus: #4F46E5;
--text-main: #0F172A;
--text-muted: #64748B;
--shadow: 0 25px 50px -12px rgba(79,70,229,0.12);
--dot-color: rgba(79,70,229,0.035);
--btn-spbu: #059669;
--btn-jalan: #D97706;
--btn-kavling: #2563EB;
--btn-rumah: #7C3AED;
--btn-warga: #DC2626;
}
/* ── Reset ── */
*, *::before, *::after { box-sizing: border-box; }
body {
margin: 0;
font-family: var(--font);
background: var(--bg-dark);
color: var(--text-light);
color: var(--text-main);
overflow: hidden;
transition: background 0.3s var(--ease), color 0.3s var(--ease);
}
/* Dot grid background */
body::before {
content: '';
position: fixed;
inset: 0;
background-image: radial-gradient(circle, var(--dot-color) 1px, transparent 1px);
background-size: 28px 28px;
pointer-events: none;
z-index: 0;
}
/* ── Map ── */
#map {
width: 100vw;
height: 100vh;
z-index: 1;
}
/* Floating Dock */
/* ── Floating Dock ── */
.floating-dock {
position: absolute;
top: 24px;
@@ -37,90 +92,178 @@ body {
backdrop-filter: blur(24px);
-webkit-backdrop-filter: blur(24px);
border: 1px solid var(--border-glass);
border-radius: var(--radius);
padding: 24px;
width: 340px;
border-radius: 20px;
padding: 20px;
width: 320px;
box-shadow: var(--shadow);
display: flex;
flex-direction: column;
gap: 20px;
gap: 16px;
max-height: calc(100vh - 48px);
overflow-y: auto;
transition: background 0.3s var(--ease), border-color 0.3s var(--ease);
}
/* Custom Scrollbar for Dock */
.floating-dock::-webkit-scrollbar { width: 6px; }
.floating-dock::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.2); border-radius: 10px; }
.floating-dock::-webkit-scrollbar { width: 4px; }
.floating-dock::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.15); border-radius: 10px; }
.header h2 { margin: 0 0 8px 0; font-size: 1.4rem; font-weight: 700; background: linear-gradient(135deg, #818CF8, #C084FC); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.header p { margin: 0; font-size: 0.85rem; color: var(--text-muted); line-height: 1.5; }
/* ── Dock Top Bar ── */
.dock-topbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
/* Buttons */
.menu-group { display: flex; flex-direction: column; gap: 10px; }
.menu-title { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 1px; color: var(--text-muted); font-weight: 600; margin-bottom: 4px; }
.btn-back-portal {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 10px;
border-radius: 8px;
border: 1px solid var(--border-glass);
background: var(--bg-input);
color: var(--text-muted);
font-size: 0.72rem;
font-weight: 500;
cursor: pointer;
text-decoration: none;
transition: var(--transition);
white-space: nowrap;
}
.btn-back-portal:hover {
background: var(--primary-deep);
border-color: var(--primary-deep);
color: white;
}
.dock-controls {
display: flex;
gap: 6px;
}
.btn-theme-dock {
width: 30px;
height: 30px;
border-radius: 8px;
border: 1px solid var(--border-glass);
background: var(--bg-input);
color: var(--text-muted);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.8rem;
transition: var(--transition);
}
.btn-theme-dock:hover {
background: var(--primary-deep);
border-color: var(--primary-deep);
color: white;
}
/* ── Header ── */
.header h2 {
margin: 0 0 6px 0;
font-size: 1.25rem;
font-weight: 700;
background: linear-gradient(135deg, var(--primary), #C084FC);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.header p {
margin: 0;
font-size: 0.8rem;
color: var(--text-muted);
line-height: 1.5;
}
/* ── Buttons ── */
.menu-group { display: flex; flex-direction: column; gap: 8px; }
.menu-title {
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--text-muted);
font-weight: 700;
margin-bottom: 2px;
}
.btn-action {
background: rgba(255, 255, 255, 0.05);
background: var(--bg-input);
border: 1px solid var(--border-glass);
color: var(--text-light);
padding: 14px 16px;
color: var(--text-main);
padding: 11px 14px;
border-radius: 12px;
cursor: pointer;
font-weight: 500;
font-family: var(--font);
display: flex;
align-items: center;
gap: 12px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
font-size: 0.9rem;
gap: 10px;
transition: var(--transition);
font-size: 0.875rem;
text-align: left;
}
.btn-action svg { width: 20px; height: 20px; flex-shrink: 0; }
.btn-action svg { width: 18px; height: 18px; flex-shrink: 0; }
.btn-action:hover {
background: rgba(255, 255, 255, 0.1);
transform: translateX(4px);
background: rgba(255,255,255,0.08);
transform: translateX(3px);
border-color: var(--primary);
}
[data-theme="light"] .btn-action:hover {
background: #EEF2FF;
}
.btn-action.active {
background: var(--primary);
border-color: var(--primary);
box-shadow: 0 10px 15px -3px rgba(99, 102, 241, 0.4);
background: var(--primary-deep);
border-color: var(--primary-deep);
color: white;
box-shadow: 0 4px 12px var(--primary-glow);
}
/* Button Variants */
.btn-spbu svg { color: #10B981; }
.btn-jalan svg { color: #F59E0B; }
.btn-kavling svg { color: #3B82F6; }
.btn-rumah svg { color: #8B5CF6; }
.btn-warga svg { color: #EF4444; }
/* Button color variants */
.btn-spbu svg { color: var(--btn-spbu); }
.btn-jalan svg { color: var(--btn-jalan); }
.btn-kavling svg { color: var(--btn-kavling); }
.btn-rumah svg { color: var(--btn-rumah); }
.btn-warga svg { color: var(--btn-warga); }
/* Form Panel */
/* ── Form Panel ── */
.form-panel {
background: rgba(0, 0, 0, 0.4);
background: rgba(0,0,0,0.3);
border-radius: 12px;
padding: 16px;
padding: 14px;
border: 1px solid var(--border-glass);
display: flex;
flex-direction: column;
gap: 12px;
gap: 10px;
}
.form-group { display: flex; flex-direction: column; gap: 6px; }
.form-group label { font-size: 0.8rem; color: var(--text-muted); }
[data-theme="light"] .form-panel {
background: rgba(79,70,229,0.04);
}
.form-group { display: flex; flex-direction: column; gap: 5px; }
.form-group label { font-size: 0.78rem; color: var(--text-muted); font-weight: 500; }
.form-control {
background: rgba(255, 255, 255, 0.05);
background: var(--bg-input);
border: 1px solid var(--border-glass);
padding: 10px 12px;
padding: 9px 12px;
border-radius: 8px;
color: white;
color: var(--text-main);
font-family: var(--font);
font-size: 0.9rem;
font-size: 0.875rem;
outline: none;
transition: border 0.2s;
}
.form-control:focus { border-color: var(--primary); }
.form-control:focus { border-color: var(--border-focus); box-shadow: 0 0 0 3px var(--primary-glow); }
.form-control::placeholder { color: var(--text-muted); }
.checkbox-group {
display: flex;
@@ -128,78 +271,77 @@ body {
gap: 8px;
cursor: pointer;
}
.checkbox-group input { width: 16px; height: 16px; accent-color: var(--primary); cursor: pointer; }
.checkbox-group span { font-size: 0.85rem; color: var(--text-light); }
.checkbox-group input { width: 15px; height: 15px; accent-color: var(--primary); cursor: pointer; }
.checkbox-group span { font-size: 0.82rem; color: var(--text-main); }
.btn-submit {
background: var(--primary);
background: var(--primary-deep);
color: white;
border: none;
padding: 12px;
padding: 11px;
border-radius: 8px;
font-weight: 600;
font-family: var(--font);
cursor: pointer;
transition: background 0.2s;
transition: var(--transition);
font-size: 0.875rem;
}
.btn-submit:hover { background: var(--primary-hover); }
.btn-submit:hover { filter: brightness(1.1); transform: translateY(-1px); }
/* Leaflet Customizations */
/* ── Leaflet Customizations ── */
.leaflet-draw-toolbar { display: none !important; }
.leaflet-control-zoom { border: none !important; box-shadow: var(--shadow) !important; }
.leaflet-control-zoom a {
background: var(--bg-glass) !important;
color: var(--text-light) !important;
color: var(--text-main) !important;
border-color: var(--border-glass) !important;
backdrop-filter: blur(10px);
}
.leaflet-control-zoom a:hover { background: var(--primary) !important; }
.leaflet-control-zoom a:hover { background: var(--primary-deep) !important; color: white !important; }
/* Leaflet Popup Premium */
.leaflet-popup-content-wrapper {
background: var(--bg-glass) !important;
backdrop-filter: blur(16px) !important;
border: 1px solid var(--border-glass) !important;
color: var(--text-light) !important;
border-radius: 16px !important;
color: var(--text-main) !important;
border-radius: 14px !important;
box-shadow: var(--shadow) !important;
}
.leaflet-popup-tip { background: var(--bg-glass) !important; border: 1px solid var(--border-glass) !important; }
.leaflet-popup-content { margin: 16px !important; font-family: var(--font); }
.leaflet-popup-content h3 { margin: 0 0 8px 0; font-size: 1.1rem; border-bottom: 1px solid var(--border-glass); padding-bottom: 8px; }
.leaflet-popup-content p { margin: 4px 0; font-size: 0.9rem; color: var(--text-muted); }
.leaflet-popup-tip { background: var(--bg-glass) !important; }
.leaflet-popup-content { margin: 14px !important; font-family: var(--font); }
.leaflet-popup-content h3 { margin: 0 0 8px 0; font-size: 1rem; border-bottom: 1px solid var(--border-glass); padding-bottom: 8px; }
.leaflet-popup-content p { margin: 4px 0; font-size: 0.85rem; color: var(--text-muted); }
.leaflet-popup-content .btn-delete {
background: rgba(239, 68, 68, 0.1);
background: rgba(239,68,68,0.1);
color: #EF4444;
border: 1px solid rgba(239, 68, 68, 0.3);
border: 1px solid rgba(239,68,68,0.3);
padding: 6px 12px;
border-radius: 6px;
cursor: pointer;
margin-top: 12px;
margin-top: 10px;
width: 100%;
transition: all 0.2s;
font-family: var(--font);
font-size: 0.85rem;
transition: var(--transition);
}
.leaflet-popup-content .btn-delete:hover { background: #EF4444; color: white; }
/* Custom Map Marker Icon Animation */
.custom-icon div {
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.custom-icon:hover div {
transform: scale(1.1) translateY(-4px);
}
/* Toast Notification */
/* ── Toast ── */
.toast-container {
position: fixed; bottom: 24px; right: 24px; z-index: 9999;
display: flex; flex-direction: column; gap: 10px;
}
.toast {
background: var(--bg-glass); backdrop-filter: blur(16px);
border: 1px solid var(--border-glass); border-left: 4px solid var(--primary);
padding: 16px 20px; border-radius: 12px; color: white; font-size: 0.9rem;
box-shadow: var(--shadow); animation: slideIn 0.3s cubic-bezier(0.4, 0, 0.2, 1);
border: 1px solid var(--border-glass); border-left: 4px solid var(--primary-deep);
padding: 14px 18px; border-radius: 12px; color: var(--text-main); font-size: 0.875rem;
box-shadow: var(--shadow); animation: slideIn 0.3s var(--ease);
}
.toast.error { border-left-color: #EF4444; }
.toast.error { border-left-color: #EF4444; }
.toast.success { border-left-color: #10B981; }
@keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
+103 -18
View File
@@ -1,53 +1,138 @@
<!DOCTYPE html>
<html lang="id">
<html lang="id" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebGIS - Pertemuan 03</title>
<title>WebGIS Pertemuan 03: Analisis Spasial</title>
<meta name="description" content="Modul 03 — Choropleth Map dinamis berbasis Point-in-Polygon berbasis WebGIS.">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<link rel="stylesheet" href="css/main.css">
<style>
.result-item { background: rgba(255,255,255,0.05); padding: 8px 10px; border-radius: 8px; border: 1px solid var(--border-glass); display: flex; justify-content: space-between; align-items: center; }
.badge { background: var(--primary); padding: 2px 6px; border-radius: 12px; font-size: 0.7rem; font-weight: 700; color: white; }
.result-item {
background: var(--bg-input);
padding: 8px 10px;
border-radius: 8px;
border: 1px solid var(--border-glass);
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.82rem;
}
.badge {
background: var(--primary-deep);
padding: 2px 8px;
border-radius: 12px;
font-size: 0.68rem;
font-weight: 700;
color: white;
}
.stat-row {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.82rem;
}
.stat-row strong { font-weight: 700; }
</style>
</head>
<body>
<!-- Map container -->
<div id="map"></div>
<!-- Floating Dock -->
<div class="floating-dock">
<!-- Dock top bar -->
<div class="dock-topbar">
<a href="../../portal.html" class="btn-back-portal" title="Kembali ke Portal">
<i class="fas fa-chevron-left"></i>
Portal
</a>
<div class="dock-controls">
<button class="btn-theme-dock" id="themeToggle" title="Toggle tema" aria-label="Toggle dark/light mode">
<i class="fas fa-moon" id="themeIcon"></i>
</button>
</div>
</div>
<!-- Header -->
<div class="header">
<h2>P03: Analisis Spasial</h2>
<p>Kumulatif P01 & P02 + Choropleth Map (Point in Polygon).</p>
<p>Kumulatif P01 &amp; P02 + Choropleth Map (Point-in-Polygon).</p>
</div>
<!-- Draw Tools -->
<div class="menu-group">
<div class="menu-title">Draw Tools</div>
<button id="btn-draw-spbu" class="btn-action btn-spbu"><svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.242-4.243a8 8 0 1111.314 0z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path></svg> Tambah SPBU</button>
<button id="btn-draw-jalan" class="btn-action btn-jalan"><svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"></path></svg> Tambah Jalan</button>
<button id="btn-draw-kavling" class="btn-action btn-kavling"><svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"></path></svg> Tambah Kavling</button>
<button id="btn-draw-rumah" class="btn-action btn-rumah"><svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path></svg> Tambah Rumah Ibadah</button>
<button id="btn-draw-warga" class="btn-action btn-warga"><svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path></svg> Tambah Warga Miskin</button>
<button id="btn-draw-spbu" class="btn-action btn-spbu">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.242-4.243a8 8 0 1111.314 0z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
Tambah SPBU
</button>
<button id="btn-draw-jalan" class="btn-action btn-jalan">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"></path></svg>
Tambah Jalan
</button>
<button id="btn-draw-kavling" class="btn-action btn-kavling">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"></path></svg>
Tambah Kavling
</button>
<button id="btn-draw-rumah" class="btn-action btn-rumah">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path></svg>
Tambah Rumah Ibadah
</button>
<button id="btn-draw-warga" class="btn-action btn-warga">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path></svg>
Tambah Warga Miskin
</button>
</div>
<div id="choropleth-panel" class="form-panel" style="margin-top: 5px; background: rgba(16, 185, 129, 0.1); border-color: rgba(16, 185, 129, 0.3);">
<h3 style="margin:0 0 5px 0; font-size:0.9rem; color:#10B981;">Statistik Kavling (Choropleth)</h3>
<p style="font-size:0.8rem; margin:0 0 10px 0; color:var(--text-muted);">Pewarnaan otomatis area kavling berdasarkan jumlah warga miskin yang bermukim di atasnya.</p>
<div style="display:flex; justify-content:space-between; margin-bottom:8px;">
<span style="font-size:0.8rem;">Total Penduduk Miskin:</span>
<strong id="stat-total-warga" style="color:white;">0</strong>
<!-- Choropleth stats panel -->
<div id="choropleth-panel" class="form-panel" style="margin-top:4px; background:rgba(16,185,129,0.07); border-color:rgba(16,185,129,0.25);">
<h3 style="margin:0 0 8px 0; font-size:0.85rem; color:#10B981; font-weight:600;">
<i class="fas fa-chart-area" style="margin-right:6px;"></i>Statistik Kavling (Choropleth)
</h3>
<p style="font-size:0.78rem; margin:0 0 10px 0; color:var(--text-muted);">Pewarnaan otomatis area kavling berdasarkan jumlah warga miskin yang bermukim di atasnya.</p>
<div class="stat-row" style="margin-bottom:6px;">
<span>Total Penduduk Miskin:</span>
<strong id="stat-total-warga" style="color:var(--text-main);">0</strong>
</div>
<div style="display:flex; justify-content:space-between;">
<span style="font-size:0.8rem;">Kavling Padat (>2 warga):</span>
<div class="stat-row">
<span>Kavling Padat (&gt;2 warga):</span>
<strong id="stat-kavling-merah" style="color:#EF4444;">0</strong>
</div>
</div>
<!-- Dynamic form container -->
<div id="form-container" style="display: none;"></div>
</div>
<!-- Toast -->
<div id="toast-container" class="toast-container"></div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script>
<script>
// ── Theme toggle ──
const html = document.documentElement;
const btn = document.getElementById('themeToggle');
const icon = document.getElementById('themeIcon');
const saved = localStorage.getItem('portal-theme') || 'dark';
setTheme(saved);
btn.addEventListener('click', () => {
const next = html.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
setTheme(next);
localStorage.setItem('portal-theme', next);
});
function setTheme(t) {
html.setAttribute('data-theme', t);
icon.className = t === 'dark' ? 'fas fa-sun' : 'fas fa-moon';
}
</script>
<script type="module" src="js/app.js"></script>
</body>
</html>
+17 -2
View File
@@ -14,12 +14,27 @@ export const initMap = (containerId) => {
position: 'bottomright'
}).addTo(map);
// Tile Layer Premium (CartoDB Dark Matter untuk Glass UI)
L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
const tileUrl = isDark
? 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png'
: 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png';
// Tile Layer Premium (CartoDB Dark Matter / Light All)
const tileLayer = L.tileLayer(tileUrl, {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>',
subdomains: 'abcd',
maxZoom: 20
}).addTo(map);
// Dynamic theme updater for tile layer
const observer = new MutationObserver(() => {
const dark = document.documentElement.getAttribute('data-theme') === 'dark';
tileLayer.setUrl(dark
? 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png'
: 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png'
);
});
observer.observe(document.documentElement, { attributes: true });
return map;
};