First commit

This commit is contained in:
2026-06-09 19:02:22 +07:00
commit c2c0774b3b
36 changed files with 10493 additions and 0 deletions
+846
View File
@@ -0,0 +1,846 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Login WebGIS Pemetaan Kemiskinan Berbasis Partisipasi Rumah Ibadah">
<title>Masuk WebGIS Poverty Mapping</title>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&display=swap"
rel="stylesheet">
<style>
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--bg: #0a0e1a;
--panel: #0f1422;
--card: #141928;
--border: rgba(255, 255, 255, .07);
--blue: #4f7cff;
--blue-dim: rgba(79, 124, 255, .12);
--green: #22d3a5;
--orange: #ff8c42;
--purple: #9b6dff;
--text: #e8edf5;
--muted: #5c6880;
--sub: #8a96b0;
}
body {
font-family: 'Plus Jakarta Sans', sans-serif;
background: var(--bg);
color: var(--text);
min-height: 100vh;
display: grid;
grid-template-columns: 1fr 480px;
}
/* ── LEFT PANEL ── */
.left-panel {
position: relative;
overflow: hidden;
background: #080c18;
display: flex;
flex-direction: column;
justify-content: flex-end;
padding: 48px;
}
.map-bg {
position: absolute;
inset: 0;
background:
radial-gradient(ellipse 70% 60% at 30% 40%, rgba(79, 124, 255, .18) 0%, transparent 65%),
radial-gradient(ellipse 50% 70% at 70% 70%, rgba(34, 211, 165, .1) 0%, transparent 60%),
radial-gradient(ellipse 40% 40% at 60% 20%, rgba(155, 109, 255, .08) 0%, transparent 55%);
}
/* Animated grid lines */
.grid-lines {
position: absolute;
inset: 0;
background-image:
linear-gradient(rgba(255, 255, 255, .025) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, .025) 1px, transparent 1px);
background-size: 60px 60px;
animation: gridShift 20s linear infinite;
}
@keyframes gridShift {
from {
background-position: 0 0;
}
to {
background-position: 60px 60px;
}
}
/* Floating map dots */
.dot {
position: absolute;
border-radius: 50%;
pointer-events: none;
}
.dot-1 {
width: 12px;
height: 12px;
background: var(--blue);
box-shadow: 0 0 20px var(--blue), 0 0 40px rgba(79, 124, 255, .4);
top: 32%;
left: 28%;
animation: pulseDot 3s ease-in-out infinite;
}
.dot-2 {
width: 8px;
height: 8px;
background: var(--green);
box-shadow: 0 0 15px var(--green);
top: 55%;
left: 55%;
animation: pulseDot 3s ease-in-out infinite 1s;
}
.dot-3 {
width: 10px;
height: 10px;
background: var(--orange);
box-shadow: 0 0 15px var(--orange);
top: 22%;
left: 60%;
animation: pulseDot 3s ease-in-out infinite 2s;
}
.dot-4 {
width: 7px;
height: 7px;
background: var(--purple);
box-shadow: 0 0 12px var(--purple);
top: 68%;
left: 35%;
animation: pulseDot 3s ease-in-out infinite .5s;
}
/* Connecting lines between dots */
.conn-lines {
position: absolute;
inset: 0;
overflow: hidden;
}
.conn-lines svg {
width: 100%;
height: 100%;
}
@keyframes pulseDot {
0%,
100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.4);
opacity: .6;
}
}
/* Ripple rings on dots */
.dot::after {
content: '';
position: absolute;
inset: -8px;
border-radius: 50%;
border: 1.5px solid currentColor;
opacity: 0;
animation: ripple 3s ease-in-out infinite;
}
.dot-1::after {
color: var(--blue);
}
.dot-2::after {
color: var(--green);
animation-delay: 1s;
}
.dot-3::after {
color: var(--orange);
animation-delay: 2s;
}
.dot-4::after {
color: var(--purple);
animation-delay: .5s;
}
@keyframes ripple {
0% {
inset: -4px;
opacity: .7;
}
100% {
inset: -24px;
opacity: 0;
}
}
.left-content {
position: relative;
z-index: 2;
}
.left-tag {
display: inline-flex;
align-items: center;
gap: 7px;
background: rgba(79, 124, 255, .15);
border: 1px solid rgba(79, 124, 255, .3);
border-radius: 20px;
padding: 5px 14px;
font-size: 11px;
font-weight: 600;
color: #7ba4ff;
letter-spacing: .5px;
text-transform: uppercase;
margin-bottom: 20px;
}
.left-tag-dot {
width: 6px;
height: 6px;
background: var(--blue);
border-radius: 50%;
box-shadow: 0 0 8px var(--blue);
animation: pulseDot 2s ease-in-out infinite;
}
.left-title {
font-size: clamp(28px, 3.5vw, 42px);
font-weight: 800;
line-height: 1.15;
letter-spacing: -.5px;
color: #fff;
margin-bottom: 16px;
}
.left-title em {
font-style: normal;
background: linear-gradient(90deg, var(--blue), var(--green));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.left-desc {
font-size: 14px;
color: var(--sub);
line-height: 1.7;
max-width: 400px;
margin-bottom: 32px;
}
.stat-row {
display: flex;
gap: 20px;
}
.stat {
display: flex;
flex-direction: column;
gap: 2px;
}
.stat-val {
font-size: 24px;
font-weight: 800;
color: #fff;
}
.stat-lbl {
font-size: 11px;
color: var(--muted);
font-weight: 500;
}
.stat-sep {
width: 1px;
background: var(--border);
align-self: stretch;
}
/* ── RIGHT PANEL ── */
.right-panel {
background: var(--panel);
border-left: 1px solid var(--border);
display: flex;
flex-direction: column;
justify-content: center;
padding: 48px 40px;
overflow-y: auto;
}
.logo-row {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 40px;
}
.logo-icon {
width: 44px;
height: 44px;
background: linear-gradient(135deg, #1a3a8f, var(--blue));
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
box-shadow: 0 6px 20px rgba(79, 124, 255, .3);
flex-shrink: 0;
}
.logo-text {
display: flex;
flex-direction: column;
}
.logo-name {
font-size: 14px;
font-weight: 800;
color: var(--text);
letter-spacing: -.2px;
}
.logo-sub {
font-size: 11px;
color: var(--muted);
margin-top: 1px;
}
.form-title {
font-size: 26px;
font-weight: 800;
color: var(--text);
letter-spacing: -.4px;
margin-bottom: 6px;
}
.form-sub {
font-size: 13px;
color: var(--sub);
margin-bottom: 32px;
}
/* Role tabs */
.role-tabs {
display: flex;
background: var(--card);
border: 1px solid var(--border);
border-radius: 10px;
padding: 4px;
gap: 4px;
margin-bottom: 28px;
}
.role-tab {
flex: 1;
padding: 8px 6px;
border: none;
border-radius: 7px;
background: transparent;
color: var(--muted);
font-family: inherit;
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: all .2s;
display: flex;
flex-direction: column;
align-items: center;
gap: 3px;
}
.role-tab-icon {
font-size: 18px;
}
.role-tab-name {
font-size: 11px;
}
.role-tab:hover {
color: var(--text);
background: rgba(255, 255, 255, .04);
}
.role-tab.active {
background: var(--blue-dim);
color: var(--blue);
border: 1px solid rgba(79, 124, 255, .25);
}
.role-tab.active[data-role="pengurus"] {
color: var(--purple);
background: rgba(155, 109, 255, .1);
border-color: rgba(155, 109, 255, .25);
}
.role-tab.active[data-role="walikota"] {
color: var(--orange);
background: rgba(255, 140, 66, .1);
border-color: rgba(255, 140, 66, .25);
}
/* Fields */
.field {
margin-bottom: 18px;
}
.field-label {
display: block;
font-size: 12px;
font-weight: 600;
color: var(--sub);
margin-bottom: 7px;
letter-spacing: .3px;
}
.field-wrap {
position: relative;
}
.field-icon {
position: absolute;
left: 14px;
top: 50%;
transform: translateY(-50%);
width: 16px;
text-align: center;
color: var(--muted);
font-size: 14px;
pointer-events: none;
}
.field-input {
width: 100%;
padding: 12px 14px 12px 42px;
background: var(--card);
border: 1px solid var(--border);
border-radius: 10px;
color: var(--text);
font-family: inherit;
font-size: 13.5px;
outline: none;
transition: border-color .2s, box-shadow .2s;
}
.field-input:focus {
border-color: rgba(79, 124, 255, .5);
box-shadow: 0 0 0 3px rgba(79, 124, 255, .1);
}
.field-input::placeholder {
color: var(--muted);
font-size: 13px;
}
.pw-toggle {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
color: var(--muted);
font-size: 14px;
cursor: pointer;
padding: 4px;
transition: color .2s;
}
.pw-toggle:hover {
color: var(--sub);
}
/* Error */
.error-msg {
display: none;
align-items: center;
gap: 8px;
background: rgba(239, 68, 68, .08);
border: 1px solid rgba(239, 68, 68, .2);
color: #fca5a5;
border-radius: 8px;
padding: 10px 14px;
font-size: 12.5px;
margin-bottom: 18px;
animation: shake .3s ease;
}
.error-msg.show {
display: flex;
}
@keyframes shake {
0%,
100% {
transform: translateX(0);
}
25% {
transform: translateX(-5px);
}
75% {
transform: translateX(5px);
}
}
/* Login button */
.btn-masuk {
width: 100%;
padding: 14px;
background: var(--blue);
color: #fff;
border: none;
border-radius: 10px;
font-family: inherit;
font-size: 14px;
font-weight: 700;
cursor: pointer;
transition: all .25s;
box-shadow: 0 4px 20px rgba(79, 124, 255, .35);
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin-bottom: 24px;
}
.btn-masuk:hover {
background: #6b91ff;
transform: translateY(-1px);
box-shadow: 0 6px 28px rgba(79, 124, 255, .45);
}
.btn-masuk:active {
transform: translateY(0);
}
.btn-masuk.loading {
opacity: .7;
pointer-events: none;
}
/* Quick fill */
.quick-fill-label {
font-size: 11px;
font-weight: 600;
color: var(--muted);
text-transform: uppercase;
letter-spacing: .6px;
margin-bottom: 10px;
}
.quick-row {
display: flex;
gap: 8px;
}
.quick-btn {
flex: 1;
padding: 9px 8px;
background: var(--card);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--sub);
font-family: inherit;
font-size: 11px;
font-weight: 600;
cursor: pointer;
transition: all .2s;
display: flex;
align-items: center;
justify-content: center;
gap: 5px;
}
.quick-btn:hover {
background: rgba(255, 255, 255, .05);
border-color: rgba(255, 255, 255, .15);
color: var(--text);
}
.footer-note {
text-align: center;
margin-top: 28px;
font-size: 11px;
color: var(--muted);
}
/* Responsive */
@media (max-width: 768px) {
body {
grid-template-columns: 1fr;
}
.left-panel {
display: none;
}
.right-panel {
padding: 36px 24px;
}
}
</style>
</head>
<body>
<!-- LEFT: Map Visual Panel -->
<div class="left-panel">
<div class="map-bg"></div>
<div class="grid-lines"></div>
<!-- Animated map dots -->
<div class="dot dot-1"></div>
<div class="dot dot-2"></div>
<div class="dot dot-3"></div>
<div class="dot dot-4"></div>
<!-- SVG connecting lines -->
<div class="conn-lines">
<svg viewBox="0 0 100 100" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
<line x1="28" y1="32" x2="55" y2="55" stroke="rgba(79,124,255,.2)" stroke-width=".3"
stroke-dasharray="2 2" />
<line x1="55" y1="55" x2="60" y2="22" stroke="rgba(34,211,165,.15)" stroke-width=".3"
stroke-dasharray="2 2" />
<line x1="28" y1="32" x2="35" y2="68" stroke="rgba(155,109,255,.15)" stroke-width=".3"
stroke-dasharray="2 2" />
<line x1="35" y1="68" x2="55" y2="55" stroke="rgba(255,140,66,.12)" stroke-width=".3"
stroke-dasharray="2 2" />
</svg>
</div>
<div class="left-content">
<div class="left-tag">
<div class="left-tag-dot"></div>
Sistem Aktif
</div>
<h1 class="left-title">
Peta Kemiskinan<br>Berbasis <em>Rumah Ibadah</em>
</h1>
<p class="left-desc">
Platform pemetaan partisipatif yang menghubungkan data kemiskinan dengan jaringan rumah ibadah untuk
perencanaan program yang lebih tepat sasaran.
</p>
<div class="stat-row">
<div class="stat">
<span class="stat-val" style="color:var(--blue)">3</span>
<span class="stat-lbl">Jenis Peran</span>
</div>
<div class="stat-sep"></div>
<div class="stat">
<span class="stat-val" style="color:var(--green)">6+</span>
<span class="stat-lbl">Jenis Rumah Ibadah</span>
</div>
<div class="stat-sep"></div>
<div class="stat">
<span class="stat-val" style="color:var(--orange)">GIS</span>
<span class="stat-lbl">Berbasis Peta</span>
</div>
</div>
</div>
</div>
<!-- RIGHT: Login Panel -->
<div class="right-panel">
<div class="logo-row">
<div class="logo-icon">🗺️</div>
<div class="logo-text">
<span class="logo-name">WebGIS Poverty Mapping</span>
<span class="logo-sub">Sistem Informasi Geografis</span>
</div>
</div>
<h2 class="form-title">Masuk ke Sistem</h2>
<p class="form-sub">Pilih peran, lalu masukkan kredensial Anda.</p>
<!-- Role Tabs -->
<div class="role-tabs" id="role-tabs">
<button class="role-tab" id="tab-admin" data-role="admin" onclick="pilihRole('admin')">
<span class="role-tab-icon">🛡️</span>
<span class="role-tab-name">Admin</span>
</button>
<button class="role-tab" id="tab-pengurus" data-role="pengurus" onclick="pilihRole('pengurus')">
<span class="role-tab-icon">🕌</span>
<span class="role-tab-name">Pengurus</span>
</button>
<button class="role-tab" id="tab-walikota" data-role="walikota" onclick="pilihRole('walikota')">
<span class="role-tab-icon">👑</span>
<span class="role-tab-name">Walikota</span>
</button>
</div>
<!-- Error -->
<div class="error-msg" id="error-msg">
<span>⚠️</span>
<span id="error-text">Username atau password salah.</span>
</div>
<!-- Form -->
<form id="login-form" onsubmit="handleLogin(event)" novalidate>
<div class="field">
<label class="field-label" for="inp-username">Username</label>
<div class="field-wrap">
<span class="field-icon">👤</span>
<input class="field-input" type="text" id="inp-username" placeholder="Masukkan username"
autocomplete="username" required>
</div>
</div>
<div class="field">
<label class="field-label" for="inp-password">Password</label>
<div class="field-wrap">
<span class="field-icon">🔑</span>
<input class="field-input" type="password" id="inp-password" placeholder="Masukkan password"
autocomplete="current-password" required>
<button type="button" class="pw-toggle" id="pw-toggle" onclick="togglePw()">👁️</button>
</div>
</div>
<button type="submit" class="btn-masuk" id="btn-masuk">
<span id="btn-text">Masuk</span>
<span id="btn-arrow"></span>
</button>
</form>
<div style="margin-bottom: 28px;">
<a href="index.html" style="display:flex; justify-content:center; align-items:center; gap:8px; padding:14px; background:rgba(255,255,255,0.03); color:var(--text); border:1px solid var(--border); border-radius:10px; text-decoration:none; font-size:13.5px; font-weight:600; transition:all 0.25s;" onmouseover="this.style.background='rgba(255,255,255,0.08)'" onmouseout="this.style.background='rgba(255,255,255,0.03)'">
<span>🗺️ Lihat Peta & Lapor (Akses Warga)</span>
</a>
</div>
<!-- Quick Fill -->
<div class="quick-fill-label">Akun Demo — Klik untuk Isi Otomatis</div>
<div class="quick-row">
<button class="quick-btn" onclick="isiDemo('admin','admin123','admin')">🛡️ Admin</button>
<button class="quick-btn" onclick="isiDemo('pengurus','pengurus123','pengurus')">🕌 Pengurus</button>
<button class="quick-btn" onclick="isiDemo('walikota','walikota123','walikota')">👑 Walikota</button>
</div>
<div class="footer-note">WebGIS Poverty Mapping · 2026</div>
</div>
<script>
let roleSelected = null;
function pilihRole(role) {
roleSelected = role;
document.querySelectorAll('.role-tab').forEach(t => t.classList.remove('active'));
document.getElementById('tab-' + role).classList.add('active');
}
function isiDemo(user, pass, role) {
document.getElementById('inp-username').value = user;
document.getElementById('inp-password').value = pass;
pilihRole(role);
sembunyiError();
}
function togglePw() {
const inp = document.getElementById('inp-password');
const btn = document.getElementById('pw-toggle');
inp.type = inp.type === 'password' ? 'text' : 'password';
btn.textContent = inp.type === 'password' ? '👁️' : '🙈';
}
function tampilError(msg) {
const el = document.getElementById('error-msg');
document.getElementById('error-text').textContent = msg;
el.classList.add('show');
el.style.animation = 'none';
el.offsetHeight;
el.style.animation = '';
}
function sembunyiError() {
document.getElementById('error-msg').classList.remove('show');
}
async function handleLogin(e) {
e.preventDefault();
sembunyiError();
const username = document.getElementById('inp-username').value.trim();
const password = document.getElementById('inp-password').value;
if (!username) { tampilError('Username wajib diisi.'); return; }
if (!password) { tampilError('Password wajib diisi.'); return; }
if (!roleSelected) { tampilError('Pilih peran terlebih dahulu.'); return; }
const btn = document.getElementById('btn-masuk');
const btnText = document.getElementById('btn-text');
const btnArrow = document.getElementById('btn-arrow');
btn.classList.add('loading');
btnText.textContent = 'Memeriksa...';
btnArrow.textContent = '⏳';
try {
const res = await fetch('api_auth.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password, role: roleSelected })
});
const data = await res.json();
if (data.success) {
sessionStorage.setItem('webgis_user', JSON.stringify({
id: data.user.id,
username: data.user.username,
nama: data.user.nama,
role: data.user.role
}));
btnText.textContent = 'Berhasil!';
btnArrow.textContent = '✓';
btn.style.background = '#22d3a5';
const target = data.user.role === 'walikota' ? 'dashboard_walikota.html' : 'index.html';
setTimeout(() => { window.location.href = target; }, 500);
} else {
tampilError(data.message || 'Username, password, atau peran tidak sesuai.');
btn.classList.remove('loading');
btnText.textContent = 'Masuk';
btnArrow.textContent = '→';
}
} catch {
tampilError('Gagal terhubung ke server. Pastikan Apache & MySQL aktif.');
btn.classList.remove('loading');
btnText.textContent = 'Masuk';
btnArrow.textContent = '→';
}
}
// Handle logout redirect
if (new URLSearchParams(location.search).get('logout')) {
sessionStorage.removeItem('webgis_user');
}
</script>
</body>
</html>