landing_page

This commit is contained in:
tistopandita
2026-06-11 15:01:02 +07:00
parent 5386300498
commit b350e4e2c8
2 changed files with 506 additions and 7 deletions
+15 -7
View File
@@ -13,6 +13,11 @@ const PORT = process.env.PORT || 3000;
app.use(cors());
app.use(express.json());
app.use("/api/poverty", povertyRoutes);
// ─── Root: landing page (harus sebelum express.static) ───────────────────────
app.get("/", (req, res) => {
res.sendFile(path.join(__dirname, "../frontend/landing.html"));
});
// Sajikan folder frontend secara statis
app.use(express.static(path.join(__dirname, "../frontend")));
@@ -644,19 +649,22 @@ app.delete("/api/bangunan/:id", async (req, res) => {
}
});
// ─── Fallback: semua route non-API ke index.html ─────────────────────────────
// ─── Fallback: semua route non-API ke landing.html ───────────────────────────
app.get("*", (req, res) => {
res.sendFile(path.join(__dirname, "../frontend/index.html"));
res.sendFile(path.join(__dirname, "../frontend/landing.html"));
});
// ─── Start ────────────────────────────────────────────────────────────────────
initDatabase().then(() => {
app.listen(PORT, () => {
console.log(`\n🚀 Server berjalan di http://localhost:${PORT}`);
console.log(` Peta → http://localhost:${PORT}/`);
console.log(` Admin → http://localhost:${PORT}/admin.html`);
console.log(` API → http://localhost:${PORT}/api/spbu`);
console.log(` API → http://localhost:${PORT}/api/jalan`);
console.log(` API → http://localhost:${PORT}/api/bangunan\n`);
console.log(` Portal → http://localhost:${PORT}/`);
console.log(` SPBU → http://localhost:${PORT}/index.html`);
console.log(` Poverty → http://localhost:${PORT}/poverty-map.html`);
console.log(` Pengajuan → http://localhost:${PORT}/pengajuan.html`);
console.log(` Admin → http://localhost:${PORT}/admin.html`);
console.log(` Admin POV → http://localhost:${PORT}/admin-poverty.html`);
console.log(` API SPBU → http://localhost:${PORT}/api/spbu`);
console.log(` API POV → http://localhost:${PORT}/api/poverty\n`);
});
});
+491
View File
@@ -0,0 +1,491 @@
<!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 Sistem Informasi Geografis</title>
<meta name="description" content="Portal WebGIS Kota Pontianak — Sistem Informasi SPBU dan WebGIS Distribusi Bantuan Sosial Gereja berbasis pemetaan interaktif." />
<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=Inter:ital,wght@0,400;0,600;0,700;0,800;0,900;1,400&display=swap" rel="stylesheet" />
<style>
:root {
--c1: #7c3aed;
--c2: #06b6d4;
--c3: #f97316;
--dark: #060d1f;
--panel: rgba(255,255,255,.055);
--border: rgba(255,255,255,.10);
--shadow: 0 32px 80px rgba(6,13,31,.55);
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body {
font-family: "Inter", system-ui, sans-serif;
background: var(--dark);
color: #f1f5f9;
min-height: 100vh;
overflow-x: hidden;
}
a { text-decoration: none; color: inherit; }
/* ── Animated mesh background ─────────────────────────────── */
.bg {
position: fixed;
inset: 0;
z-index: 0;
background:
radial-gradient(ellipse 80% 60% at 20% -10%, rgba(124,58,237,.35) 0%, transparent 55%),
radial-gradient(ellipse 60% 50% at 85% 90%, rgba(6,182,212,.28) 0%, transparent 50%),
radial-gradient(ellipse 50% 40% at 70% 10%, rgba(249,115,22,.18) 0%, transparent 45%),
linear-gradient(135deg, #060d1f 0%, #0b1730 50%, #0d1635 100%);
animation: bgShift 18s ease-in-out infinite alternate;
}
@keyframes bgShift {
0% { filter: hue-rotate(0deg); }
100% { filter: hue-rotate(20deg); }
}
/* Floating orbs */
.orb {
position: fixed;
border-radius: 50%;
filter: blur(90px);
opacity: .22;
animation: orbFloat 12s ease-in-out infinite alternate;
pointer-events: none;
z-index: 0;
}
.orb-1 { width: 480px; height: 480px; background: var(--c1); top: -120px; left: -80px; animation-duration: 14s; }
.orb-2 { width: 360px; height: 360px; background: var(--c2); bottom: -80px; right: -60px; animation-duration: 10s; animation-delay: -4s; }
.orb-3 { width: 260px; height: 260px; background: var(--c3); top: 40%; left: 60%; animation-duration: 16s; animation-delay: -8s; }
@keyframes orbFloat {
from { transform: translate(0, 0) scale(1); }
to { transform: translate(30px, -40px) scale(1.08); }
}
/* ── Layout ───────────────────────────────────────────────── */
.wrapper {
position: relative;
z-index: 1;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 24px 80px;
}
/* ── Badge ────────────────────────────────────────────────── */
.badge {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 18px;
border-radius: 999px;
border: 1px solid rgba(124,58,237,.5);
background: rgba(124,58,237,.18);
backdrop-filter: blur(12px);
font-size: .78rem;
font-weight: 800;
letter-spacing: .08em;
text-transform: uppercase;
color: #c4b5fd;
margin-bottom: 28px;
animation: fadeDown .6s ease both;
}
.badge .dot {
width: 7px; height: 7px;
border-radius: 50%;
background: #7c3aed;
box-shadow: 0 0 8px #7c3aed;
animation: pulse 1.6s ease-in-out infinite;
}
@keyframes pulse {
0%,100% { opacity: 1; transform: scale(1); }
50% { opacity: .5; transform: scale(.7); }
}
/* ── Headline ─────────────────────────────────────────────── */
.headline {
text-align: center;
max-width: 780px;
animation: fadeDown .7s ease .1s both;
}
.headline h1 {
font-size: clamp(2.4rem, 6vw, 4rem);
font-weight: 900;
line-height: 1.08;
letter-spacing: -.02em;
background: linear-gradient(135deg, #fff 20%, #c4b5fd 50%, #67e8f9 80%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 20px;
}
.headline p {
color: #94a3b8;
font-size: 1.05rem;
line-height: 1.65;
max-width: 580px;
margin: 0 auto 48px;
}
/* ── Cards grid ───────────────────────────────────────────── */
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 22px;
width: 100%;
max-width: 900px;
margin-bottom: 40px;
animation: fadeUp .8s ease .2s both;
}
.card {
position: relative;
overflow: hidden;
border-radius: 28px;
border: 1px solid var(--border);
background: var(--panel);
backdrop-filter: blur(24px);
padding: 36px 32px 32px;
display: flex;
flex-direction: column;
gap: 0;
cursor: pointer;
transition: transform .24s cubic-bezier(.23,1,.32,1), box-shadow .24s, border-color .24s;
text-decoration: none;
color: inherit;
}
.card::before {
content: "";
position: absolute;
inset: 0;
opacity: 0;
transition: opacity .24s;
border-radius: inherit;
}
.card-spbu::before { background: radial-gradient(circle at 80% 0%, rgba(249,115,22,.22), transparent 60%); }
.card-poverty::before { background: radial-gradient(circle at 80% 0%, rgba(124,58,237,.25), transparent 60%); }
.card:hover {
transform: translateY(-8px) scale(1.012);
box-shadow: var(--shadow);
border-color: rgba(255,255,255,.22);
}
.card:hover::before { opacity: 1; }
.card:active { transform: translateY(-4px) scale(1.006); }
/* Card inner glow line */
.card-glow {
position: absolute;
top: 0; left: 24px; right: 24px;
height: 1px;
border-radius: 999px;
opacity: .7;
transition: opacity .24s;
}
.card:hover .card-glow { opacity: 1; }
.card-spbu .card-glow { background: linear-gradient(90deg, transparent, #f97316, transparent); }
.card-poverty .card-glow { background: linear-gradient(90deg, transparent, #a78bfa, transparent); }
.card-icon {
width: 58px; height: 58px;
border-radius: 18px;
display: grid;
place-items: center;
font-size: 1.6rem;
margin-bottom: 22px;
flex-shrink: 0;
border: 1px solid rgba(255,255,255,.12);
}
.card-spbu .card-icon { background: linear-gradient(135deg, rgba(249,115,22,.3), rgba(251,146,60,.15)); }
.card-poverty .card-icon { background: linear-gradient(135deg, rgba(124,58,237,.3), rgba(6,182,212,.15)); }
.card-tag {
display: inline-flex;
padding: 4px 10px;
border-radius: 999px;
font-size: .68rem;
font-weight: 800;
letter-spacing: .06em;
text-transform: uppercase;
margin-bottom: 10px;
width: fit-content;
}
.card-spbu .card-tag { background: rgba(249,115,22,.18); color: #fb923c; border: 1px solid rgba(249,115,22,.3); }
.card-poverty .card-tag { background: rgba(124,58,237,.18); color: #a78bfa; border: 1px solid rgba(124,58,237,.3); }
.card h2 {
font-size: 1.42rem;
font-weight: 800;
letter-spacing: -.01em;
margin-bottom: 10px;
line-height: 1.2;
}
.card p {
color: #94a3b8;
font-size: .88rem;
line-height: 1.6;
flex: 1;
margin-bottom: 26px;
}
.card-features {
display: flex;
flex-direction: column;
gap: 7px;
margin-bottom: 28px;
}
.feat {
display: flex;
align-items: center;
gap: 9px;
font-size: .81rem;
color: #cbd5e1;
}
.feat-dot {
width: 6px; height: 6px;
border-radius: 50%;
flex-shrink: 0;
}
.card-spbu .feat-dot { background: #f97316; box-shadow: 0 0 6px #f97316; }
.card-poverty .feat-dot { background: #a78bfa; box-shadow: 0 0 6px #a78bfa; }
.card-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 9px;
width: 100%;
padding: 13px;
border-radius: 16px;
font-weight: 800;
font-size: .9rem;
border: 0;
cursor: pointer;
transition: filter .18s, transform .18s;
letter-spacing: -.005em;
color: white;
}
.card:hover .card-btn { filter: brightness(1.12); transform: scale(1.02); }
.card-spbu .card-btn { background: linear-gradient(135deg, #ea580c, #f97316); box-shadow: 0 10px 28px rgba(249,115,22,.35); }
.card-poverty .card-btn { background: linear-gradient(135deg, #6d28d9, #7c3aed, #06b6d4); box-shadow: 0 10px 28px rgba(124,58,237,.35); }
.arrow {
transition: transform .2s;
font-style: normal;
font-size: .9rem;
}
.card:hover .arrow { transform: translateX(4px); }
/* ── Sub links ────────────────────────────────────────────── */
.sub-links {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: center;
animation: fadeUp .9s ease .35s both;
}
.sub-link {
display: flex;
align-items: center;
gap: 7px;
padding: 9px 16px;
border-radius: 12px;
border: 1px solid var(--border);
background: var(--panel);
backdrop-filter: blur(12px);
font-size: .8rem;
font-weight: 700;
color: #94a3b8;
transition: color .16s, border-color .16s, background .16s;
}
.sub-link:hover { color: #f1f5f9; border-color: rgba(255,255,255,.25); background: rgba(255,255,255,.1); }
/* ── Footer ───────────────────────────────────────────────── */
.foot {
position: relative;
z-index: 1;
text-align: center;
padding: 24px;
color: #475569;
font-size: .78rem;
}
.foot strong { color: #64748b; }
/* ── Stats strip ──────────────────────────────────────────── */
.stats-strip {
display: flex;
gap: 0;
border: 1px solid var(--border);
background: var(--panel);
backdrop-filter: blur(16px);
border-radius: 20px;
overflow: hidden;
width: 100%;
max-width: 900px;
margin-bottom: 30px;
animation: fadeUp .85s ease .28s both;
}
.stat-item {
flex: 1;
padding: 18px 16px;
text-align: center;
border-right: 1px solid var(--border);
}
.stat-item:last-child { border-right: 0; }
.stat-item strong {
display: block;
font-size: 1.4rem;
font-weight: 900;
background: linear-gradient(135deg, #c4b5fd, #67e8f9);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
line-height: 1;
margin-bottom: 4px;
}
.stat-item span {
display: block;
font-size: .72rem;
color: #64748b;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .05em;
}
/* ── Animations ───────────────────────────────────────────── */
@keyframes fadeDown {
from { opacity: 0; transform: translateY(-20px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes fadeUp {
from { opacity: 0; transform: translateY(24px); }
to { opacity: 1; transform: translateY(0); }
}
/* ── Responsive ───────────────────────────────────────────── */
@media (max-width: 680px) {
.cards { grid-template-columns: 1fr; }
.stats-strip { flex-direction: column; }
.stat-item { border-right: 0; border-bottom: 1px solid var(--border); }
.stat-item:last-child { border-bottom: 0; }
.wrapper { padding: 40px 16px 60px; }
}
</style>
</head>
<body>
<div class="bg"></div>
<div class="orb orb-1"></div>
<div class="orb orb-2"></div>
<div class="orb orb-3"></div>
<div class="wrapper">
<!-- Badge -->
<div class="badge">
<span class="dot"></span>
Sistem Informasi Geografis — Kota Pontianak
</div>
<!-- Headline -->
<div class="headline">
<h1>Portal WebGIS<br>Kota Pontianak</h1>
<p>Platform pemetaan interaktif berbasis web untuk pengelolaan data spasial SPBU dan distribusi bantuan sosial gereja secara transparan dan tepat sasaran.</p>
</div>
<!-- Stats strip -->
<div class="stats-strip" id="statsStrip">
<div class="stat-item"><strong id="statSpbu">9</strong><span>SPBU Terpetakan</span></div>
<div class="stat-item"><strong id="statGereja"></strong><span>Gereja Terdaftar</span></div>
<div class="stat-item"><strong id="statKeluarga"></strong><span>Keluarga Dipetakan</span></div>
<div class="stat-item"><strong id="statPengajuan"></strong><span>Pengajuan Bantuan</span></div>
</div>
<!-- Main cards -->
<div class="cards">
<!-- Card 1: SPBU -->
<a class="card card-spbu" href="/index.html" id="cardSpbu">
<div class="card-glow"></div>
<div class="card-icon"></div>
<span class="card-tag">WebGIS SPBU</span>
<h2>Peta Stasiun Pengisian Bahan Bakar</h2>
<p>Sistem informasi geografis untuk pemetaan dan manajemen SPBU di Kota Pontianak. Pantau status operasional, jenis BBM, dan jam operasional secara real-time.</p>
<div class="card-features">
<div class="feat"><span class="feat-dot"></span>Peta interaktif lokasi SPBU</div>
<div class="feat"><span class="feat-dot"></span>Manajemen data jalan & bangunan</div>
<div class="feat"><span class="feat-dot"></span>Status buka/tutup real-time</div>
<div class="feat"><span class="feat-dot"></span>Filter jenis BBM tersedia</div>
</div>
<div class="card-btn">Buka WebGIS SPBU <i class="arrow"></i></div>
</a>
<!-- Card 2: Poverty Map -->
<a class="card card-poverty" href="/poverty-map.html" id="cardPoverty">
<div class="card-glow"></div>
<div class="card-icon">🗺️</div>
<span class="card-tag">Bantuan Sosial</span>
<h2>Peta Distribusi Bantuan Sosial Gereja</h2>
<p>WebGIS untuk memetakan dan mendistribusikan bantuan sosial dari gereja kepada masyarakat yang membutuhkan, berbasis analisis SAW, buffer, dan routing OSM.</p>
<div class="card-features">
<div class="feat"><span class="feat-dot"></span>Analisis prioritas SAW otomatis</div>
<div class="feat"><span class="feat-dot"></span>Buffer analysis 1 / 3 / 5 km</div>
<div class="feat"><span class="feat-dot"></span>Nearest church & routing OSM</div>
<div class="feat"><span class="feat-dot"></span>Heatmap persebaran kemiskinan</div>
</div>
<div class="card-btn">Buka Peta Bantuan Sosial <i class="arrow"></i></div>
</a>
</div>
<!-- Sub links -->
<div class="sub-links">
<a class="sub-link" href="/pengajuan.html">📋 Ajukan Bantuan</a>
<a class="sub-link" href="/admin-poverty.html">⚙️ Admin Poverty</a>
<a class="sub-link" href="/admin.html">🛠️ Admin SPBU</a>
</div>
</div>
<!-- Footer -->
<footer class="foot">
<strong>WebGIS Kota Pontianak</strong> &nbsp;·&nbsp; Final Project Sistem Informasi Geografis &nbsp;·&nbsp; 2026
</footer>
<script>
// Load live stats from API
async function loadStats() {
try {
const [spbu, poverty] = await Promise.all([
fetch("/api/spbu").then(r => r.json()).catch(() => null),
fetch("/api/poverty/dashboard").then(r => r.json()).catch(() => null),
]);
if (spbu?.success) {
document.getElementById("statSpbu").textContent = spbu.data?.length ?? "—";
}
if (poverty?.success) {
const d = poverty.data || {};
document.getElementById("statKeluarga").textContent = d.total_keluarga ?? "—";
}
// Load pengajuan count
const pengajuan = await fetch("/api/poverty/pengajuan").then(r => r.json()).catch(() => null);
if (pengajuan?.success) {
document.getElementById("statPengajuan").textContent = pengajuan.data?.length ?? "—";
}
} catch (_) { /* ignore */ }
}
// Fetch ibadah count separately
async function loadIbadahCount() {
try {
const res = await fetch("/api/poverty/ibadah").then(r => r.json()).catch(() => null);
if (res?.success) {
document.getElementById("statGereja").textContent = res.data?.length ?? "—";
}
} catch (_) { /* ignore */ }
}
loadStats();
loadIbadahCount();
</script>
</body>
</html>