Files
webgis-sig/index.html
T
Syariffullah 48f20aabed initialize
2026-06-11 13:11:51 +07:00

1099 lines
32 KiB
HTML

<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WebGIS — Sistem Informasi Geografis | Portofolio Tugas</title>
<meta name="description" content="Portofolio proyek WebGIS mata kuliah Sistem Informasi Geografis. Kumpulan aplikasi pemetaan interaktif berbasis web menggunakan Leaflet.js, PHP, dan MySQL." />
<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:wght@300;400;500;600;700;800;900&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" />
<style>
/* ===== CSS RESET & BASE ===== */
*, *::before, *::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--clr-bg: #f0f4f8;
--clr-surface: #e8eef5;
--clr-card: #ffffff;
--clr-border: rgba(99, 102, 241, 0.15);
--clr-primary: #4f46e5;
--clr-secondary: #7c3aed;
--clr-accent: #059669;
--clr-warn: #ea580c;
--clr-text: #1e293b;
--clr-muted: #64748b;
--clr-white: #ffffff;
--grad-primary: linear-gradient(135deg, #4f46e5, #7c3aed);
--grad-card: linear-gradient(145deg, #ffffff, #f8fafc);
--shadow-glow: 0 0 40px rgba(79, 70, 229, 0.10);
--shadow-card: 0 4px 24px rgba(15, 23, 42, 0.08);
--radius: 16px;
--radius-sm: 10px;
--transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
}
html { scroll-behavior: smooth; }
body {
font-family: 'Inter', sans-serif;
background-color: var(--clr-bg);
color: var(--clr-text);
line-height: 1.6;
overflow-x: hidden;
}
/* ===== SCROLLBAR ===== */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: var(--clr-bg); }
::-webkit-scrollbar-thumb { background: var(--clr-primary); border-radius: 3px; }
/* ===== CANVAS BG ===== */
#bg-canvas {
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
z-index: 0;
pointer-events: none;
opacity: 0.5;
}
/* ===== LAYOUT ===== */
.wrapper {
position: relative;
z-index: 1;
max-width: 1200px;
margin: 0 auto;
padding: 0 24px;
}
/* ===== NAVBAR ===== */
.navbar {
position: fixed;
top: 0; left: 0; right: 0;
z-index: 100;
padding: 16px 0;
background: rgba(240, 244, 248, 0.75);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-bottom: 1px solid rgba(99, 102, 241, 0.12);
transition: var(--transition);
}
.navbar.scrolled {
padding: 12px 0;
background: rgba(240, 244, 248, 0.97);
box-shadow: 0 2px 20px rgba(15, 23, 42, 0.08);
}
.nav-inner {
max-width: 1200px;
margin: 0 auto;
padding: 0 24px;
display: flex;
align-items: center;
justify-content: space-between;
}
.nav-logo {
display: flex;
align-items: center;
gap: 10px;
text-decoration: none;
}
.nav-logo-icon {
width: 38px;
height: 38px;
border-radius: 10px;
background: var(--grad-primary);
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
color: #fff;
box-shadow: 0 0 16px rgba(56, 189, 248, 0.4);
flex-shrink: 0;
}
.nav-logo-text {
font-family: 'Space Grotesk', sans-serif;
font-weight: 700;
font-size: 1.1rem;
color: var(--clr-text);
line-height: 1.2;
}
.nav-logo-text span {
display: block;
font-size: 0.7rem;
font-weight: 400;
color: var(--clr-primary);
letter-spacing: 0.08em;
text-transform: uppercase;
}
.nav-badge {
font-size: 0.75rem;
padding: 5px 14px;
border-radius: 50px;
background: rgba(79, 70, 229, 0.08);
border: 1px solid rgba(79, 70, 229, 0.25);
color: var(--clr-primary);
font-weight: 500;
letter-spacing: 0.02em;
}
/* ===== HERO ===== */
.hero {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
padding: 120px 24px 80px;
position: relative;
overflow: hidden;
}
.hero::before {
content: '';
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
width: 900px;
height: 900px;
background: radial-gradient(circle, rgba(79,70,229,0.07) 0%, rgba(124,58,237,0.04) 40%, transparent 70%);
pointer-events: none;
}
.hero-badge {
display: inline-flex;
align-items: center;
gap: 8px;
font-size: 0.8rem;
padding: 7px 18px;
border-radius: 50px;
background: rgba(79, 70, 229, 0.08);
border: 1px solid rgba(79, 70, 229, 0.22);
color: var(--clr-primary);
font-weight: 500;
letter-spacing: 0.06em;
text-transform: uppercase;
margin-bottom: 28px;
animation: fadeSlideDown 0.8s ease both;
}
.hero-badge i { font-size: 0.75rem; animation: pulse 2s infinite; }
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
.hero-title {
font-family: 'Space Grotesk', sans-serif;
font-size: clamp(2.4rem, 6vw, 4.2rem);
font-weight: 800;
line-height: 1.1;
letter-spacing: -0.03em;
color: var(--clr-text);
margin-bottom: 20px;
animation: fadeSlideDown 0.8s 0.1s ease both;
}
.hero-title .gradient-text {
background: var(--grad-primary);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero-sub {
font-size: 1.1rem;
color: var(--clr-muted);
max-width: 560px;
margin: 0 auto 40px;
line-height: 1.7;
animation: fadeSlideDown 0.8s 0.2s ease both;
}
.hero-stats {
display: flex;
align-items: center;
justify-content: center;
gap: 40px;
margin-bottom: 56px;
animation: fadeSlideDown 0.8s 0.3s ease both;
}
.hero-stat {
text-align: center;
}
.hero-stat-num {
font-family: 'Space Grotesk', sans-serif;
font-size: 2rem;
font-weight: 700;
background: var(--grad-primary);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
line-height: 1;
margin-bottom: 4px;
}
.hero-stat-label {
font-size: 0.78rem;
color: var(--clr-muted);
text-transform: uppercase;
letter-spacing: 0.06em;
}
.hero-divider {
width: 1px;
height: 40px;
background: var(--clr-border);
}
.hero-cta {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 14px 32px;
border-radius: 50px;
background: var(--grad-primary);
color: #fff;
font-weight: 600;
font-size: 0.95rem;
text-decoration: none;
box-shadow: 0 8px 24px rgba(79, 70, 229, 0.35);
transition: var(--transition);
animation: fadeSlideDown 0.8s 0.4s ease both;
}
.hero-cta:hover {
transform: translateY(-2px);
box-shadow: 0 12px 36px rgba(79, 70, 229, 0.5);
}
/* ===== SCROLL INDICATOR ===== */
.scroll-indicator {
position: absolute;
bottom: 32px;
left: 50%;
transform: translateX(-50%);
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
color: var(--clr-muted);
font-size: 0.75rem;
letter-spacing: 0.06em;
text-transform: uppercase;
animation: fadeSlideDown 0.8s 0.8s ease both;
}
.scroll-indicator .mouse {
width: 22px;
height: 34px;
border: 2px solid rgba(79,70,229,0.3);
border-radius: 11px;
display: flex;
justify-content: center;
padding-top: 6px;
}
.scroll-indicator .wheel {
width: 3px;
height: 6px;
background: var(--clr-primary);
border-radius: 2px;
animation: scrollDown 1.6s infinite;
}
@keyframes scrollDown {
0% { opacity: 1; transform: translateY(0); }
100% { opacity: 0; transform: translateY(10px); }
}
/* ===== SECTION ===== */
.section {
padding: 100px 0;
}
.section-label {
font-size: 0.78rem;
font-weight: 600;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--clr-primary);
margin-bottom: 12px;
}
.section-title {
font-family: 'Space Grotesk', sans-serif;
font-size: clamp(1.8rem, 4vw, 2.6rem);
font-weight: 700;
color: var(--clr-text);
line-height: 1.2;
letter-spacing: -0.02em;
margin-bottom: 16px;
}
.section-desc {
font-size: 1rem;
color: var(--clr-muted);
max-width: 520px;
line-height: 1.7;
}
.section-header {
display: flex;
align-items: flex-end;
justify-content: space-between;
margin-bottom: 56px;
gap: 24px;
flex-wrap: wrap;
}
/* ===== PROJECT CARDS ===== */
.projects-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
gap: 28px;
}
.project-card {
position: relative;
background: var(--grad-card);
border: 1px solid var(--clr-border);
border-radius: var(--radius);
overflow: hidden;
text-decoration: none;
color: var(--clr-text);
transition: var(--transition);
display: flex;
flex-direction: column;
box-shadow: var(--shadow-card);
animation: fadeSlideUp 0.6s ease both;
}
.project-card:hover {
transform: translateY(-6px);
border-color: rgba(79, 70, 229, 0.35);
box-shadow: 0 16px 48px rgba(15, 23, 42, 0.14), 0 0 0 1px rgba(79,70,229,0.1);
}
.project-card:hover .card-arrow {
transform: translate(4px, -4px);
opacity: 1;
}
.project-card:hover .card-icon {
transform: scale(1.08);
}
.card-header {
padding: 28px 28px 20px;
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
}
.card-icon {
width: 56px;
height: 56px;
border-radius: 14px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.4rem;
transition: var(--transition);
flex-shrink: 0;
}
.card-num {
font-family: 'Space Grotesk', sans-serif;
font-size: 0.75rem;
font-weight: 600;
color: var(--clr-muted);
letter-spacing: 0.08em;
background: rgba(15,23,42,0.04);
border: 1px solid rgba(15,23,42,0.1);
border-radius: 50px;
padding: 4px 12px;
}
.card-arrow {
position: absolute;
top: 24px;
right: 24px;
color: var(--clr-primary);
font-size: 1rem;
opacity: 0.5;
transition: var(--transition);
}
.card-body {
padding: 0 28px 28px;
flex: 1;
display: flex;
flex-direction: column;
}
.card-title {
font-family: 'Space Grotesk', sans-serif;
font-size: 1.25rem;
font-weight: 700;
color: var(--clr-text);
margin-bottom: 10px;
letter-spacing: -0.01em;
line-height: 1.3;
}
.card-desc {
font-size: 0.88rem;
color: var(--clr-muted);
line-height: 1.65;
margin-bottom: 20px;
flex: 1;
}
.card-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 24px;
}
.tag {
font-size: 0.72rem;
font-weight: 500;
padding: 4px 12px;
border-radius: 50px;
letter-spacing: 0.02em;
}
.tag-blue { background: #eff6ff; color: #1d4ed8; border: 1px solid #bfdbfe; }
.tag-purple { background: #f5f3ff; color: #6d28d9; border: 1px solid #ddd6fe; }
.tag-green { background: #ecfdf5; color: #065f46; border: 1px solid #a7f3d0; }
.tag-orange { background: #fff7ed; color: #9a3412; border: 1px solid #fed7aa; }
.tag-pink { background: #fdf2f8; color: #9d174d; border: 1px solid #fbcfe8; }
.card-footer {
border-top: 1px solid var(--clr-border);
padding: 18px 28px;
display: flex;
align-items: center;
justify-content: space-between;
}
.card-link-btn {
display: inline-flex;
align-items: center;
gap: 8px;
font-size: 0.85rem;
font-weight: 600;
color: var(--clr-primary);
transition: var(--transition);
}
.project-card:hover .card-link-btn {
gap: 12px;
}
.card-status {
display: flex;
align-items: center;
gap: 6px;
font-size: 0.75rem;
}
.status-dot {
width: 7px;
height: 7px;
border-radius: 50%;
animation: statusPulse 2s infinite;
}
.status-dot.active { background: var(--clr-accent); box-shadow: 0 0 8px var(--clr-accent); }
.status-dot.pending { background: var(--clr-warn); box-shadow: 0 0 8px var(--clr-warn); }
.status-dot.inactive { background: var(--clr-muted); }
@keyframes statusPulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
/* ===== CARD ACCENT LINES ===== */
.card-glow-line {
position: absolute;
top: 0; left: 0; right: 0;
height: 2px;
border-radius: var(--radius) var(--radius) 0 0;
}
/* ===== COMING SOON CARD ===== */
.card-overlay {
position: absolute;
inset: 0;
background: rgba(240,244,248,0.75);
backdrop-filter: blur(3px);
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--radius);
}
.coming-soon-badge {
font-size: 0.8rem;
font-weight: 600;
padding: 8px 20px;
border-radius: 50px;
background: #fff7ed;
border: 1px solid #fed7aa;
color: var(--clr-warn);
letter-spacing: 0.06em;
text-transform: uppercase;
box-shadow: 0 2px 8px rgba(234,88,12,0.15);
}
/* ===== TECH STACK SECTION ===== */
.tech-strip {
padding: 60px 0;
border-top: 1px solid var(--clr-border);
border-bottom: 1px solid var(--clr-border);
}
.tech-strip-label {
text-align: center;
font-size: 0.75rem;
font-weight: 500;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--clr-muted);
margin-bottom: 32px;
}
.tech-items {
display: flex;
align-items: center;
justify-content: center;
gap: 40px;
flex-wrap: wrap;
}
.tech-item {
display: flex;
align-items: center;
gap: 10px;
color: var(--clr-muted);
font-size: 0.9rem;
font-weight: 500;
transition: var(--transition);
}
.tech-item:hover { color: var(--clr-text); }
.tech-item i {
font-size: 1.4rem;
transition: var(--transition);
}
/* ===== FOOTER ===== */
.footer {
padding: 60px 0 40px;
border-top: 1px solid var(--clr-border);
}
.footer-inner {
text-align: center;
}
.footer-logo {
font-family: 'Space Grotesk', sans-serif;
font-size: 1.4rem;
font-weight: 700;
color: var(--clr-text);
margin-bottom: 12px;
}
.footer-logo span {
background: var(--grad-primary);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.footer-course {
font-size: 0.85rem;
color: var(--clr-muted);
margin-bottom: 32px;
}
.footer-divider {
width: 60px;
height: 1px;
background: var(--clr-border);
margin: 32px auto;
}
.footer-copy {
font-size: 0.8rem;
color: var(--clr-muted);
}
.footer-copy a {
color: var(--clr-primary);
text-decoration: none;
}
/* ===== ANIMATIONS ===== */
@keyframes fadeSlideDown {
from { opacity: 0; transform: translateY(-20px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes fadeSlideUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
.reveal {
opacity: 0;
transform: translateY(30px);
transition: opacity 0.7s ease, transform 0.7s ease;
}
.reveal.visible {
opacity: 1;
transform: translateY(0);
}
/* ===== RESPONSIVE ===== */
@media (max-width: 640px) {
.hero-stats { gap: 24px; }
.projects-grid { grid-template-columns: 1fr; }
.section-header { flex-direction: column; align-items: flex-start; }
}
</style>
</head>
<body>
<!-- ===== CANVAS BACKGROUND ===== -->
<canvas id="bg-canvas"></canvas>
<!-- ===== NAVBAR ===== -->
<nav class="navbar" id="navbar">
<div class="nav-inner">
<a href="#" class="nav-logo" id="logo-link">
<div class="nav-logo-icon"><i class="fa-solid fa-map-location-dot"></i></div>
<div class="nav-logo-text">
WebGIS
<span>Sistem Informasi Geografis</span>
</div>
</a>
<div class="nav-badge"><i class="fa-solid fa-graduation-cap"></i> &nbsp;Portofolio Tugas</div>
</div>
</nav>
<!-- ===== HERO ===== -->
<section class="hero" id="home">
<div class="wrapper">
<div class="hero-badge" id="hero-badge">
<i class="fa-solid fa-circle"></i>
Sistem Informasi Geografis
</div>
<h1 class="hero-title" id="hero-title">
Portofolio Proyek<br />
<span class="gradient-text">WebGIS Interaktif</span>
</h1>
<p class="hero-sub" id="hero-sub">
Kumpulan tugas pemetaan berbasis web menggunakan teknologi geospasial modern —
dari peta jalan, persil, hingga visualisasi data sosial.
</p>
<div class="hero-stats" id="hero-stats">
<div class="hero-stat">
<div class="hero-stat-num" id="stat-projects">4</div>
<div class="hero-stat-label">Proyek</div>
</div>
<div class="hero-divider"></div>
<div class="hero-stat">
<div class="hero-stat-num">GIS</div>
<div class="hero-stat-label">Mata Kuliah</div>
</div>
<div class="hero-divider"></div>
<div class="hero-stat">
<div class="hero-stat-num">Web</div>
<div class="hero-stat-label">Platform</div>
</div>
</div>
<a href="#projects" class="hero-cta" id="hero-cta">
<i class="fa-solid fa-layer-group"></i>
Lihat Semua Proyek
</a>
<div class="scroll-indicator">
<div class="mouse"><div class="wheel"></div></div>
<span>Scroll</span>
</div>
</div>
</section>
<!-- ===== TECH STRIP ===== -->
<div class="tech-strip">
<div class="wrapper">
<p class="tech-strip-label">Teknologi yang Digunakan</p>
<div class="tech-items">
<div class="tech-item">
<i class="fa-solid fa-map" style="color:#4f46e5"></i>
Leaflet.js
</div>
<div class="tech-item">
<i class="fa-brands fa-php" style="color:#7c3aed"></i>
PHP
</div>
<div class="tech-item">
<i class="fa-solid fa-database" style="color:#059669"></i>
MySQL
</div>
<div class="tech-item">
<i class="fa-brands fa-html5" style="color:#ea580c"></i>
HTML / CSS / JS
</div>
<div class="tech-item">
<i class="fa-solid fa-globe" style="color:#db2777"></i>
GeoJSON
</div>
<div class="tech-item">
<i class="fa-solid fa-server" style="color:#b45309"></i>
XAMPP
</div>
</div>
</div>
</div>
<!-- ===== PROJECTS ===== -->
<section class="section" id="projects">
<div class="wrapper">
<div class="section-header reveal">
<div>
<p class="section-label"><i class="fa-solid fa-folder-open"></i> &nbsp;Daftar Proyek</p>
<h2 class="section-title">Semua Tugas WebGIS</h2>
<p class="section-desc">Klik pada kartu proyek untuk membuka aplikasi pemetaan interaktif.</p>
</div>
</div>
<div class="projects-grid">
<!-- ===== PROJECT 1: POVERTY ===== -->
<a href="/webgis/poverty/index.php" class="project-card" id="card-poverty" style="animation-delay: 0.05s;">
<div class="card-glow-line" style="background: linear-gradient(90deg, #38bdf8, #818cf8);"></div>
<div class="card-arrow"><i class="fa-solid fa-arrow-up-right"></i></div>
<div class="card-header">
<div class="card-icon" style="background: rgba(56,189,248,0.12); border: 1px solid rgba(56,189,248,0.2);">
🏠
</div>
<span class="card-num">01</span>
</div>
<div class="card-body">
<h3 class="card-title">Pemetaan Kemiskinan<br/>Kota Pontianak</h3>
<p class="card-desc">
Aplikasi WebGIS untuk memetakan sebaran penduduk miskin, rumah ibadah, dan log bantuan sosial di Kota Pontianak. Dilengkapi manajemen data spasial berbasis peran.
</p>
<div class="card-tags">
<span class="tag tag-blue">Leaflet.js</span>
<span class="tag tag-purple">PHP</span>
<span class="tag tag-green">MySQL Spatial</span>
<span class="tag tag-orange">GeoJSON</span>
<span class="tag tag-pink">Role-based</span>
</div>
</div>
<div class="card-footer">
<span class="card-link-btn">
Buka Proyek <i class="fa-solid fa-arrow-right"></i>
</span>
<div class="card-status">
<div class="status-dot active"></div>
<span style="color: var(--clr-muted); font-size: 0.75rem;">Aktif</span>
</div>
</div>
</a>
<!-- ===== PROJECT 2: JALAN ===== -->
<a href="/webgis/jalan/" class="project-card" id="card-jalan" style="animation-delay: 0.1s; cursor: default;" onclick="return false;">
<div class="card-glow-line" style="background: linear-gradient(90deg, #818cf8, #f472b6);"></div>
<div class="card-header">
<div class="card-icon" style="background: rgba(129,140,248,0.12); border: 1px solid rgba(129,140,248,0.2);">
🛣️
</div>
<span class="card-num">02</span>
</div>
<div class="card-body">
<h3 class="card-title">Pemetaan Jaringan<br/>Jalan</h3>
<p class="card-desc">
Visualisasi dan analisis jaringan jalan menggunakan data spasial. Mencakup klasifikasi jalan, kondisi, dan data atribut terkait infrastruktur jalan.
</p>
<div class="card-tags">
<span class="tag tag-purple">WebGIS</span>
<span class="tag tag-blue">Shapefile</span>
<span class="tag tag-green">Road Network</span>
</div>
</div>
<div class="card-footer">
<span class="card-link-btn" style="color: var(--clr-muted);">
Segera Hadir <i class="fa-solid fa-clock"></i>
</span>
<div class="card-status">
<div class="status-dot pending"></div>
<span style="color: var(--clr-muted); font-size: 0.75rem;">Segera</span>
</div>
</div>
<div class="card-overlay">
<div class="coming-soon-badge">🚧 &nbsp;Coming Soon</div>
</div>
</a>
<!-- ===== PROJECT 3: PARSIL ===== -->
<a href="/webgis/parsil/" class="project-card" id="card-parsil" style="animation-delay: 0.15s; cursor: default;" onclick="return false;">
<div class="card-glow-line" style="background: linear-gradient(90deg, #34d399, #38bdf8);"></div>
<div class="card-header">
<div class="card-icon" style="background: rgba(52,211,153,0.12); border: 1px solid rgba(52,211,153,0.2);">
🗺️
</div>
<span class="card-num">03</span>
</div>
<div class="card-body">
<h3 class="card-title">Pemetaan Bidang<br/>Tanah (Parsil)</h3>
<p class="card-desc">
Sistem informasi geografis untuk pemetaan bidang tanah, kepemilikan lahan, dan informasi pertanahan berbasis web yang terintegrasi dengan data spasial.
</p>
<div class="card-tags">
<span class="tag tag-green">Land Parcel</span>
<span class="tag tag-blue">Polygon</span>
<span class="tag tag-orange">Cadastral</span>
</div>
</div>
<div class="card-footer">
<span class="card-link-btn" style="color: var(--clr-muted);">
Segera Hadir <i class="fa-solid fa-clock"></i>
</span>
<div class="card-status">
<div class="status-dot pending"></div>
<span style="color: var(--clr-muted); font-size: 0.75rem;">Segera</span>
</div>
</div>
<div class="card-overlay">
<div class="coming-soon-badge">🚧 &nbsp;Coming Soon</div>
</div>
</a>
<!-- ===== PROJECT 4: SPBU ===== -->
<a href="/webgis/spbu/" class="project-card" id="card-spbu" style="animation-delay: 0.2s; cursor: default;" onclick="return false;">
<div class="card-glow-line" style="background: linear-gradient(90deg, #fb923c, #facc15);"></div>
<div class="card-header">
<div class="card-icon" style="background: rgba(251,146,60,0.12); border: 1px solid rgba(251,146,60,0.2);">
</div>
<span class="card-num">04</span>
</div>
<div class="card-body">
<h3 class="card-title">Pemetaan Lokasi<br/>SPBU</h3>
<p class="card-desc">
Peta interaktif untuk menampilkan sebaran Stasiun Pengisian Bahan Bakar Umum (SPBU) beserta informasi detail, analisis jangkauan, dan fasilitas yang tersedia.
</p>
<div class="card-tags">
<span class="tag tag-orange">POI Mapping</span>
<span class="tag tag-blue">Leaflet.js</span>
<span class="tag tag-pink">Radius Analysis</span>
</div>
</div>
<div class="card-footer">
<span class="card-link-btn" style="color: var(--clr-muted);">
Segera Hadir <i class="fa-solid fa-clock"></i>
</span>
<div class="card-status">
<div class="status-dot pending"></div>
<span style="color: var(--clr-muted); font-size: 0.75rem;">Segera</span>
</div>
</div>
<div class="card-overlay">
<div class="coming-soon-badge">🚧 &nbsp;Coming Soon</div>
</div>
</a>
</div>
</div>
</section>
<!-- ===== FOOTER ===== -->
<footer class="footer">
<div class="wrapper">
<div class="footer-inner">
<div class="footer-logo">Web<span>GIS</span></div>
<div class="footer-course">
Mata Kuliah Sistem Informasi Geografis &nbsp;·&nbsp; Portofolio Tugas Mahasiswa
</div>
<div style="display: flex; align-items: center; justify-content: center; gap: 16px; flex-wrap: wrap;">
<span style="font-size:0.8rem; color: var(--clr-muted);">
<i class="fa-solid fa-map-location-dot" style="color: var(--clr-primary);"></i>
&nbsp;Leaflet.js
</span>
<span style="color: var(--clr-border);">|</span>
<span style="font-size:0.8rem; color: var(--clr-muted);">
<i class="fa-solid fa-database" style="color: var(--clr-accent);"></i>
&nbsp;MySQL Spatial
</span>
<span style="color: var(--clr-border);">|</span>
<span style="font-size:0.8rem; color: var(--clr-muted);">
<i class="fa-brands fa-php" style="color: var(--clr-secondary);"></i>
&nbsp;PHP / XAMPP
</span>
</div>
<div class="footer-divider"></div>
<p class="footer-copy">
&copy; 2025 WebGIS Portfolio &nbsp;·&nbsp; Sistem Informasi Geografis
</p>
</div>
</div>
</footer>
<!-- ===== SCRIPTS ===== -->
<script>
/* ----- CANVAS PARTICLE / GRID BG ----- */
(function () {
const canvas = document.getElementById('bg-canvas');
const ctx = canvas.getContext('2d');
let w, h, animId;
const DOTS = [];
const LINES = [];
const COUNT = 60;
function resize() {
w = canvas.width = window.innerWidth;
h = canvas.height = window.innerHeight;
}
resize();
window.addEventListener('resize', resize);
// Grid lines
function drawGrid() {
ctx.strokeStyle = 'rgba(79,70,229,0.06)';
ctx.lineWidth = 1;
const step = 60;
for (let x = 0; x < w; x += step) {
ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, h); ctx.stroke();
}
for (let y = 0; y < h; y += step) {
ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(w, y); ctx.stroke();
}
}
// Particles
for (let i = 0; i < COUNT; i++) {
DOTS.push({
x: Math.random() * window.innerWidth,
y: Math.random() * window.innerHeight,
vx: (Math.random() - 0.5) * 0.3,
vy: (Math.random() - 0.5) * 0.3,
r: Math.random() * 2 + 0.5,
a: Math.random()
});
}
function draw() {
ctx.clearRect(0, 0, w, h);
drawGrid();
// Move and draw dots
DOTS.forEach(d => {
d.x += d.vx; d.y += d.vy;
if (d.x < 0) d.x = w;
if (d.x > w) d.x = 0;
if (d.y < 0) d.y = h;
if (d.y > h) d.y = 0;
ctx.beginPath();
ctx.arc(d.x, d.y, d.r, 0, Math.PI * 2);
ctx.fillStyle = `rgba(79,70,229,${d.a * 0.4})`;
ctx.fill();
});
// Connect nearby dots
for (let i = 0; i < DOTS.length; i++) {
for (let j = i + 1; j < DOTS.length; j++) {
const dx = DOTS[i].x - DOTS[j].x;
const dy = DOTS[i].y - DOTS[j].y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 120) {
ctx.beginPath();
ctx.moveTo(DOTS[i].x, DOTS[i].y);
ctx.lineTo(DOTS[j].x, DOTS[j].y);
ctx.strokeStyle = `rgba(79,70,229,${0.12 * (1 - dist / 120)})`;
ctx.lineWidth = 0.8;
ctx.stroke();
}
}
}
animId = requestAnimationFrame(draw);
}
draw();
})();
/* ----- NAVBAR SCROLL ----- */
const navbar = document.getElementById('navbar');
window.addEventListener('scroll', () => {
navbar.classList.toggle('scrolled', window.scrollY > 40);
});
/* ----- SCROLL REVEAL ----- */
const revealEls = document.querySelectorAll('.reveal');
const revealObs = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) {
e.target.classList.add('visible');
revealObs.unobserve(e.target);
}
});
}, { threshold: 0.15 });
revealEls.forEach(el => revealObs.observe(el));
/* ----- CARD REVEAL ----- */
const cardEls = document.querySelectorAll('.project-card');
const cardObs = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) {
e.target.style.opacity = '1';
e.target.style.transform = 'translateY(0)';
cardObs.unobserve(e.target);
}
});
}, { threshold: 0.1 });
cardEls.forEach(card => {
card.style.opacity = '0';
card.style.transform = 'translateY(30px)';
card.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
cardObs.observe(card);
});
/* ----- SMOOTH HERO CTA ----- */
document.getElementById('hero-cta').addEventListener('click', (e) => {
e.preventDefault();
document.getElementById('projects').scrollIntoView({ behavior: 'smooth' });
});
</script>
</body>
</html>