Files
2026-06-11 16:37:05 +07:00

532 lines
17 KiB
PHP

<?php
// ─── Matikan exception mysqli — wajib sebelum koneksi apapun ─────────────
mysqli_report(MYSQLI_REPORT_OFF);
// ─── Safe stats: selalu return nilai, tidak pernah crash ──────────────────
function safeCount($db, $table) {
try {
$conn = @new mysqli("localhost", "root", "", $db);
if (!$conn || $conn->connect_error) return null;
$conn->set_charset("utf8mb4");
$r = $conn->query("SELECT COUNT(*) AS c FROM `$table`");
$val = ($r) ? (int)$r->fetch_assoc()['c'] : null;
$conn->close();
return $val;
} catch (Throwable $e) { return null; }
}
function safeSum($db, $table, $col) {
try {
$conn = @new mysqli("localhost", "root", "", $db);
if (!$conn || $conn->connect_error) return null;
$conn->set_charset("utf8mb4");
$r = $conn->query("SELECT COALESCE(SUM(`$col`),0) AS s FROM `$table`");
$val = ($r) ? (float)$r->fetch_assoc()['s'] : null;
$conn->close();
return $val;
} catch (Throwable $e) { return null; }
}
function safeQuery($db, $sql) {
try {
$conn = @new mysqli("localhost", "root", "", $db);
if (!$conn || $conn->connect_error) return null;
$conn->set_charset("utf8mb4");
$r = $conn->query($sql);
$val = ($r) ? $r->fetch_assoc() : null;
$conn->close();
return $val;
} catch (Throwable $e) { return null; }
}
// Coba kedua nama database untuk kemiskinan
$dbPoverty = null;
foreach (['webgis_poverty', 'webgis'] as $dbName) {
try {
$test = @new mysqli("localhost", "root", "", $dbName);
if ($test && !$test->connect_error) { $dbPoverty = $dbName; $test->close(); break; }
} catch (Throwable $e) { continue; }
}
$stats = [
'jalan_ruas' => safeCount('jalan', 'jalan'),
'jalan_km' => round((safeSum('jalan', 'jalan', 'panjang_meter') ?? 0) / 1000, 2),
'parsil' => safeCount('jalan', 'parsil_tanah'),
'spbu_total' => safeCount('spbu', 'spbu'),
'spbu_24' => null,
'kk_miskin' => $dbPoverty ? safeCount($dbPoverty, 'penduduk_miskin') : null,
'log_bantuan' => $dbPoverty ? safeCount($dbPoverty, 'log_bantuan') : null,
'ibadah' => $dbPoverty ? safeCount($dbPoverty, 'rumah_ibadah') : null,
];
$spbuRow = safeQuery('spbu', "SELECT SUM(buka_24_jam=1) AS b FROM spbu");
if ($spbuRow) $stats['spbu_24'] = (int)$spbuRow['b'];
// Helper: tampilkan nilai atau dash
function sv($v, $suffix = '') {
if ($v === null) return '<span class="stat-na">—</span>';
return htmlspecialchars($v . $suffix);
}
$year = date('Y');
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebGIS Pontianak — Portal</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;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>
/* ── Reset ── */
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
/* ── Tokens ── */
:root {
--bg: #0b0e17;
--surf: #131722;
--surf2: #1a2133;
--bdr: rgba(255,255,255,.07);
--bdr2: rgba(255,255,255,.13);
--txt: #e8edf5;
--muted: #7a8499;
--j: #3b82f6;
--p: #10b981;
--s: #f97316;
--k: #a855f7;
--gj: rgba(59,130,246,.15);
--gp: rgba(16,185,129,.15);
--gs: rgba(249,115,22,.15);
--gk: rgba(168,85,247,.15);
--r: 16px;
--r-sm: 10px;
}
/* ── Base ── */
html { scroll-behavior: smooth; }
body {
font-family: 'Plus Jakarta Sans', sans-serif;
background: var(--bg);
color: var(--txt);
min-height: 100vh;
overflow-x: hidden;
}
/* ── Ambient background ── */
.bg-ambient {
position: fixed; inset: 0; z-index: 0; pointer-events: none;
background:
radial-gradient(ellipse 60% 50% at 15% 10%, rgba(59,130,246,.08) 0%, transparent 70%),
radial-gradient(ellipse 50% 40% at 85% 80%, rgba(168,85,247,.08) 0%, transparent 70%),
radial-gradient(ellipse 40% 35% at 78% 12%, rgba(16,185,129,.05) 0%, transparent 60%);
}
.bg-dots {
position: fixed; inset: 0; z-index: 0; pointer-events: none; opacity: .3;
background-image: radial-gradient(circle, rgba(255,255,255,.15) 1px, transparent 1px);
background-size: 28px 28px;
}
/* ── Layout ── */
.wrap {
position: relative; z-index: 1;
max-width: 1100px;
margin: 0 auto;
padding: 0 24px 80px;
}
/* ── Header ── */
header { padding: 52px 0 8px; }
.eyebrow {
display: flex; align-items: center; gap: 8px;
font-size: 11px; font-weight: 700;
letter-spacing: .12em; text-transform: uppercase;
color: var(--muted); margin-bottom: 14px;
}
.eyebrow-dot {
width: 7px; height: 7px; border-radius: 50%;
background: var(--p);
box-shadow: 0 0 0 3px rgba(16,185,129,.2);
animation: livepulse 2.2s ease-in-out infinite;
}
@keyframes livepulse {
0%,100% { box-shadow: 0 0 0 3px rgba(16,185,129,.2); }
50% { box-shadow: 0 0 0 7px rgba(16,185,129,.0); }
}
.title {
font-size: clamp(32px, 5vw, 52px);
font-weight: 800;
line-height: 1.06;
letter-spacing: -.025em;
color: #fff;
margin-bottom: 14px;
}
.title-grad {
background: linear-gradient(95deg, var(--j) 0%, var(--k) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.subtitle {
font-size: 15px; color: var(--muted);
max-width: 540px; line-height: 1.65; font-weight: 400;
margin-bottom: 40px;
}
/* ── Section label ── */
.sec-label {
font-size: 10px; font-weight: 700;
letter-spacing: .12em; text-transform: uppercase;
color: var(--muted); margin-bottom: 16px;
display: flex; align-items: center; gap: 10px;
}
.sec-label::after { content:''; flex:1; height:1px; background:var(--bdr); }
/* ── Summary bar ── */
.summary-bar {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
margin-bottom: 36px;
}
@media (max-width:700px) { .summary-bar { grid-template-columns: repeat(2,1fr); } }
@media (max-width:400px) { .summary-bar { grid-template-columns: 1fr; } }
.sum-card {
background: var(--surf);
border: 1px solid var(--bdr);
border-radius: var(--r-sm);
padding: 18px 20px;
position: relative; overflow: hidden;
}
.sum-card::before {
content:''; position:absolute;
left:0; top:0; bottom:0; width:3px;
background: var(--c);
border-radius: 3px 0 0 3px;
}
.sum-num {
font-size: 26px; font-weight: 800;
color: var(--c); line-height: 1; margin-bottom: 6px;
}
.sum-lbl {
font-size: 10px; font-weight: 600;
text-transform: uppercase; letter-spacing: .07em;
color: var(--muted);
}
.stat-na { opacity: .35; font-weight: 400; }
/* ── Divider ── */
.divider { height:1px; background:var(--bdr); margin: 4px 0 28px; }
/* ── Apps grid ── */
.apps-grid {
display: grid;
grid-template-columns: repeat(2,1fr);
gap: 16px;
}
@media (max-width:640px) { .apps-grid { grid-template-columns: 1fr; } }
/* ── App card ── */
.app-card {
background: var(--surf);
border: 1px solid var(--bdr);
border-radius: var(--r);
padding: 26px 26px 66px;
text-decoration: none;
color: var(--txt);
display: block;
position: relative;
overflow: hidden;
transition: border-color .2s, transform .18s, background .2s;
}
.app-card::after {
content:''; position:absolute;
top:-70px; right:-70px;
width:180px; height:180px;
border-radius:50%;
background: var(--cg);
transition: opacity .3s, transform .3s;
opacity: .6;
}
.app-card:hover {
border-color: var(--ca);
background: var(--surf2);
transform: translateY(-3px);
}
.app-card:hover::after { opacity:1; transform:scale(1.15); }
/* Card accent variables per type */
.c-jalan { --ca:var(--j); --cg:var(--gj); }
.c-penduduk { --ca:var(--p); --cg:var(--gp); }
.c-spbu { --ca:var(--s); --cg:var(--gs); }
.c-kemiskin { --ca:var(--k); --cg:var(--gk); }
/* Card top row */
.card-head {
display: flex; align-items: flex-start;
justify-content: space-between;
margin-bottom: 18px;
position: relative; z-index:1;
}
.card-ico {
width: 46px; height: 46px; border-radius: 13px;
background: var(--cg);
border: 1px solid rgba(255,255,255,.09);
display: flex; align-items: center; justify-content: center;
font-size: 20px; color: var(--ca);
}
.card-tag {
font-size: 9px; font-weight: 700;
letter-spacing: .07em; text-transform: uppercase;
padding: 4px 10px; border-radius: 99px;
border: 1px solid var(--ca);
color: var(--ca); opacity: .75;
}
/* Card body */
.card-body { position: relative; z-index:1; margin-bottom: 18px; }
.card-name {
font-size: 18px; font-weight: 700;
color: #fff; line-height: 1.2; margin-bottom: 8px;
}
.card-desc {
font-size: 12.5px; color: var(--muted);
line-height: 1.6;
}
/* Card mini-stats */
.card-stats {
display: flex; gap: 18px;
padding-top: 16px;
border-top: 1px solid var(--bdr);
position: relative; z-index:1;
}
.cs-val {
font-size: 17px; font-weight: 700;
color: var(--ca); line-height: 1; margin-bottom: 4px;
}
.cs-lbl {
font-size: 9px; font-weight: 600;
text-transform: uppercase; letter-spacing: .05em;
color: var(--muted);
}
/* Arrow button */
.card-arrow {
position: absolute; bottom: 22px; right: 22px; z-index:2;
width: 34px; height: 34px; border-radius: 50%;
background: rgba(255,255,255,.04);
border: 1px solid var(--bdr2);
display: flex; align-items: center; justify-content: center;
font-size: 12px; color: var(--muted);
transition: all .2s;
}
.app-card:hover .card-arrow {
background: var(--ca);
border-color: var(--ca);
color: #fff;
transform: translateX(2px);
}
/* ── Footer ── */
footer {
margin-top: 60px; padding-top: 22px;
border-top: 1px solid var(--bdr);
display: flex; justify-content: space-between;
align-items: center; flex-wrap: wrap; gap: 12px;
}
.ft-l { font-size: 12px; color: var(--muted); }
.ft-r { display:flex; gap:7px; align-items:center; }
.ft-dot { width:5px; height:5px; border-radius:50%; background:var(--p); }
.ft-txt { font-size: 11px; color: var(--muted); }
</style>
</head>
<body>
<div class="bg-ambient"></div>
<div class="bg-dots"></div>
<div class="wrap">
<!-- Header -->
<header>
<div class="eyebrow">
<span class="eyebrow-dot"></span>
WebGIS Kota Pontianak
</div>
<h1 class="title">
Portal <span class="title-grad">Sistem Informasi</span><br>Geospasial
</h1>
<p class="subtitle">
Platform terpadu pemetaan infrastruktur, kependudukan, energi, dan kemiskinan
Kota Pontianak — Kalimantan Barat.
</p>
</header>
<!-- Ringkasan Data -->
<div class="sec-label">Ringkasan Data</div>
<div class="summary-bar">
<div class="sum-card" style="--c:var(--j)">
<div class="sum-num"><?= sv($stats['jalan_ruas']) ?></div>
<div class="sum-lbl">Ruas Jalan</div>
</div>
<div class="sum-card" style="--c:var(--p)">
<div class="sum-num">6</div>
<div class="sum-lbl">Kecamatan</div>
</div>
<div class="sum-card" style="--c:var(--s)">
<div class="sum-num"><?= sv($stats['spbu_total']) ?></div>
<div class="sum-lbl">SPBU Terdaftar</div>
</div>
<div class="sum-card" style="--c:var(--k)">
<div class="sum-num"><?= sv($stats['kk_miskin']) ?></div>
<div class="sum-lbl">KK Miskin</div>
</div>
</div>
<div class="divider"></div>
<!-- App Cards -->
<div class="sec-label">Aplikasi Tersedia</div>
<div class="apps-grid">
<!-- Jalan -->
<a href="jalan/index.php" class="app-card c-jalan">
<div class="card-head">
<div class="card-ico"><i class="fas fa-road"></i></div>
<span class="card-tag">GIS Layer</span>
</div>
<div class="card-body">
<div class="card-name">Pemetaan Jalan<br>&amp; Persil Tanah</div>
<div class="card-desc">
Peta interaktif ruas jalan (polyline) dan persil tanah (polygon)
dengan fitur CRUD dan penggambaran langsung di peta.
</div>
</div>
<div class="card-stats">
<div>
<div class="cs-val"><?= sv($stats['jalan_ruas']) ?></div>
<div class="cs-lbl">Ruas Jalan</div>
</div>
<div>
<div class="cs-val"><?= sv($stats['jalan_km'], ' km') ?></div>
<div class="cs-lbl">Panjang Total</div>
</div>
<div>
<div class="cs-val"><?= sv($stats['parsil']) ?></div>
<div class="cs-lbl">Persil Tanah</div>
</div>
</div>
<div class="card-arrow"><i class="fas fa-arrow-right"></i></div>
</a>
<!-- Penduduk -->
<a href="penduduk/index.php" class="app-card c-penduduk">
<div class="card-head">
<div class="card-ico"><i class="fas fa-users"></i></div>
<span class="card-tag">Choropleth</span>
</div>
<div class="card-body">
<div class="card-name">Kepadatan<br>Penduduk</div>
<div class="card-desc">
Visualisasi choropleth kepadatan penduduk per kecamatan di
Kota Pontianak dengan legenda gradasi warna interaktif.
</div>
</div>
<div class="card-stats">
<div>
<div class="cs-val">6</div>
<div class="cs-lbl">Kecamatan</div>
</div>
<div>
<div class="cs-val">9.33</div>
<div class="cs-lbl">Kepadatan Maks</div>
</div>
<div>
<div class="cs-val">UTM</div>
<div class="cs-lbl">Proyeksi</div>
</div>
</div>
<div class="card-arrow"><i class="fas fa-arrow-right"></i></div>
</a>
<!-- SPBU -->
<a href="spbu/index.php" class="app-card c-spbu">
<div class="card-head">
<div class="card-ico"><i class="fas fa-gas-pump"></i></div>
<span class="card-tag">Point Layer</span>
</div>
<div class="card-body">
<div class="card-name">Peta SPBU<br>Pontianak</div>
<div class="card-desc">
Sebaran lokasi SPBU Pertamina dengan informasi operasional,
nomor WhatsApp, status 24 jam, dan fitur geser marker.
</div>
</div>
<div class="card-stats">
<div>
<div class="cs-val"><?= sv($stats['spbu_total']) ?></div>
<div class="cs-lbl">Total SPBU</div>
</div>
<div>
<div class="cs-val"><?= sv($stats['spbu_24']) ?></div>
<div class="cs-lbl">Buka 24 Jam</div>
</div>
<div>
<div class="cs-val">WA</div>
<div class="cs-lbl">Kontak</div>
</div>
</div>
<div class="card-arrow"><i class="fas fa-arrow-right"></i></div>
</a>
<!-- Kemiskinan -->
<a href="webgis-poverty-pontianak/index.php" class="app-card c-kemiskin">
<div class="card-head">
<div class="card-ico"><i class="fas fa-map-marked-alt"></i></div>
<span class="card-tag">Multi Layer</span>
</div>
<div class="card-body">
<div class="card-name">Pemetaan<br>Kemiskinan</div>
<div class="card-desc">
Sistem manajemen penduduk miskin, rumah ibadah sebagai pusat
bantuan, log distribusi bantuan, dan dashboard analitik.
</div>
</div>
<div class="card-stats">
<div>
<div class="cs-val"><?= sv($stats['kk_miskin']) ?></div>
<div class="cs-lbl">KK Miskin</div>
</div>
<div>
<div class="cs-val"><?= sv($stats['ibadah']) ?></div>
<div class="cs-lbl">Pusat Bantuan</div>
</div>
<div>
<div class="cs-val"><?= sv($stats['log_bantuan']) ?></div>
<div class="cs-lbl">Log Bantuan</div>
</div>
</div>
<div class="card-arrow"><i class="fas fa-arrow-right"></i></div>
</a>
</div>
<!-- Footer -->
<footer>
<div class="ft-l">WebGIS Pontianak &copy; <?= $year ?> — Sistem Informasi Geospasial Kota</div>
<div class="ft-r">
<div class="ft-dot"></div>
<div class="ft-txt">Kalimantan Barat, Indonesia</div>
</div>
</footer>
</div><!-- /wrap -->
</body>
</html>