Files

972 lines
29 KiB
PHP

<?php
if (!empty($_GET['q'])) {
switch ($_GET['q']) {
case 'info':
phpinfo();
exit;
break;
}
}
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WebGIS Portal — Kota Pontianak</title>
<meta name="description" content="Portal Sistem Informasi Geografis (WebGIS) Kota Pontianak — Pusat akses semua project WebGIS yang dibangun untuk pengelolaan data spasial wilayah." />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<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.1/css/all.min.css" />
<style>
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root {
--bg: #060b14;
--bg-2: #0c1424;
--bg-card: rgba(255, 255, 255, 0.04);
--bg-card-h: rgba(255, 255, 255, 0.08);
--border: rgba(255, 255, 255, 0.08);
--border-h: rgba(255, 255, 255, 0.18);
--text-1: #f0f6ff;
--text-2: #8b9ab8;
--text-3: #4a5568;
--accent-1: #3b82f6;
--accent-2: #8b5cf6;
--accent-3: #06b6d4;
--green: #10b981;
--orange: #f59e0b;
--pink: #ec4899;
--radius: 16px;
--radius-sm: 10px;
--shadow: 0 8px 40px rgba(0, 0, 0, 0.5);
--shadow-lg: 0 20px 80px rgba(0, 0, 0, 0.7);
}
html {
scroll-behavior: smooth;
}
body {
font-family: 'Inter', sans-serif;
background: var(--bg);
color: var(--text-1);
min-height: 100vh;
overflow-x: hidden;
}
/* ── CANVAS BG ─────────────────────────────────────── */
#bg-canvas {
position: fixed;
inset: 0;
z-index: 0;
pointer-events: none;
}
.grid-overlay {
position: fixed;
inset: 0;
z-index: 0;
pointer-events: none;
background-image:
linear-gradient(rgba(59, 130, 246, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(59, 130, 246, 0.03) 1px, transparent 1px);
background-size: 60px 60px;
}
/* ── WRAPPER ───────────────────────────────────────── */
.wrapper {
position: relative;
z-index: 1;
max-width: 1100px;
margin: 0 auto;
padding: 0 24px;
}
/* ── NAVBAR ────────────────────────────────────────── */
nav {
position: sticky;
top: 0;
z-index: 100;
padding: 0 24px;
background: rgba(6, 11, 20, 0.85);
backdrop-filter: blur(20px);
border-bottom: 1px solid var(--border);
}
.nav-inner {
max-width: 1100px;
margin: 0 auto;
height: 64px;
display: flex;
align-items: center;
justify-content: space-between;
}
.nav-brand {
display: flex;
align-items: center;
gap: 10px;
font-family: 'Space Grotesk', sans-serif;
font-weight: 700;
font-size: 1.05rem;
color: var(--text-1);
text-decoration: none;
}
.nav-logo {
width: 36px;
height: 36px;
background: linear-gradient(135deg, var(--accent-1), var(--accent-2));
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1rem;
flex-shrink: 0;
box-shadow: 0 0 20px rgba(59, 130, 246, 0.4);
}
.nav-links {
display: flex;
align-items: center;
gap: 6px;
}
.nav-links a {
padding: 7px 14px;
border-radius: var(--radius-sm);
font-size: 0.82rem;
font-weight: 500;
color: var(--text-2);
text-decoration: none;
transition: all 0.2s;
}
.nav-links a:hover {
color: var(--text-1);
background: var(--bg-card);
}
.nav-badge {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 5px 12px;
background: rgba(59, 130, 246, 0.12);
border: 1px solid rgba(59, 130, 246, 0.25);
border-radius: 20px;
font-size: 0.73rem;
font-weight: 600;
color: var(--accent-1);
}
.dot-live {
width: 7px;
height: 7px;
background: var(--green);
border-radius: 50%;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%,
100% {
opacity: 1;
transform: scale(1)
}
50% {
opacity: .5;
transform: scale(.7)
}
}
/* ── HERO ──────────────────────────────────────────── */
.hero {
padding: 100px 0 70px;
text-align: center;
}
.hero-eyebrow {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 6px 16px;
border-radius: 20px;
background: rgba(139, 92, 246, 0.12);
border: 1px solid rgba(139, 92, 246, 0.25);
font-size: 0.75rem;
font-weight: 600;
color: var(--accent-2);
letter-spacing: 0.06em;
text-transform: uppercase;
margin-bottom: 24px;
animation: fadeUp 0.6s ease both;
}
.hero-title {
font-family: 'Space Grotesk', sans-serif;
font-size: clamp(2.2rem, 5vw, 3.8rem);
font-weight: 800;
line-height: 1.1;
letter-spacing: -0.02em;
margin-bottom: 20px;
animation: fadeUp 0.6s ease 0.1s both;
}
.hero-title .gradient-text {
background: linear-gradient(135deg, var(--accent-1) 0%, var(--accent-2) 50%, var(--pink) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero-sub {
font-size: 1.05rem;
color: var(--text-2);
line-height: 1.7;
max-width: 560px;
margin: 0 auto 40px;
animation: fadeUp 0.6s ease 0.2s both;
}
.hero-stats {
display: flex;
justify-content: center;
gap: 40px;
margin-top: 50px;
padding-top: 40px;
border-top: 1px solid var(--border);
animation: fadeUp 0.6s ease 0.35s both;
}
.hero-stat {
text-align: center;
}
.hero-stat-num {
font-family: 'Space Grotesk', sans-serif;
font-size: 2rem;
font-weight: 700;
background: linear-gradient(135deg, var(--accent-1), var(--accent-3));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero-stat-label {
font-size: 0.75rem;
color: var(--text-3);
text-transform: uppercase;
letter-spacing: 0.06em;
margin-top: 4px;
font-weight: 500;
}
@keyframes fadeUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* ── SECTION HEADER ────────────────────────────────── */
.section-header {
margin-bottom: 36px;
animation: fadeUp 0.6s ease 0.3s both;
}
.section-label {
font-size: 0.72rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--accent-1);
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 8px;
}
.section-label::before {
content: '';
width: 20px;
height: 2px;
background: var(--accent-1);
border-radius: 2px;
}
.section-title {
font-family: 'Space Grotesk', sans-serif;
font-size: 1.8rem;
font-weight: 700;
color: var(--text-1);
}
/* ── PROJECT CARDS ─────────────────────────────────── */
.projects-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 28px;
margin-bottom: 80px;
}
.project-card {
position: relative;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 30px;
display: flex;
flex-direction: column;
transition: all 0.3s ease;
overflow: hidden;
animation: fadeUp 0.6s ease both;
}
.project-card::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(135deg, var(--card-color-1, rgba(59, 130, 246, 0.07)), var(--card-color-2, rgba(139, 92, 246, 0.04)));
opacity: 0;
transition: opacity 0.3s;
border-radius: inherit;
}
.project-card:hover {
border-color: var(--border-h);
transform: translateY(-6px);
box-shadow: var(--shadow-lg);
background: var(--bg-card-h);
}
.project-card:hover::before {
opacity: 1;
}
/* accent glow line on top */
.project-card::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background: var(--card-accent, linear-gradient(90deg, var(--accent-1), var(--accent-2)));
transform: scaleX(0);
transform-origin: left;
transition: transform 0.4s ease;
}
.project-card:hover::after {
transform: scaleX(1);
}
.card-icon-wrap {
width: 58px;
height: 58px;
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
margin-bottom: 22px;
flex-shrink: 0;
position: relative;
}
.card-icon-wrap::after {
content: '';
position: absolute;
inset: -1px;
border-radius: 17px;
border: 1px solid rgba(255, 255, 255, 0.12);
}
.card-number {
position: absolute;
top: 20px;
right: 24px;
font-family: 'Space Grotesk', sans-serif;
font-size: 3.5rem;
font-weight: 800;
color: rgba(255, 255, 255, 0.04);
line-height: 1;
user-select: none;
}
.card-tag-row {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-bottom: 16px;
}
.tag {
padding: 3px 10px;
border-radius: 20px;
font-size: 0.65rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.card-title {
font-family: 'Space Grotesk', sans-serif;
font-size: 1.25rem;
font-weight: 700;
color: var(--text-1);
margin-bottom: 10px;
line-height: 1.3;
}
.card-desc {
font-size: 0.84rem;
color: var(--text-2);
line-height: 1.7;
margin-bottom: 24px;
flex: 1;
}
.card-features {
display: flex;
flex-direction: column;
gap: 7px;
margin-bottom: 26px;
padding: 14px;
background: rgba(255, 255, 255, 0.03);
border-radius: var(--radius-sm);
border: 1px solid var(--border);
}
.card-feature {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.77rem;
color: var(--text-2);
}
.card-feature i {
width: 14px;
text-align: center;
font-size: 0.72rem;
}
.card-footer {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: auto;
}
.card-url {
font-size: 0.71rem;
color: var(--text-3);
font-family: 'Courier New', monospace;
display: flex;
align-items: center;
gap: 5px;
}
.btn-open {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 20px;
border-radius: var(--radius-sm);
font-size: 0.82rem;
font-weight: 600;
color: #fff;
text-decoration: none;
transition: all 0.2s;
border: none;
cursor: pointer;
font-family: inherit;
position: relative;
z-index: 10;
}
.btn-open:hover {
filter: brightness(1.15);
transform: translateX(3px);
}
.btn-open i {
font-size: 0.72rem;
transition: transform 0.2s;
}
.btn-open:hover i {
transform: translateX(4px);
}
/* ── CARD THEMES ───────────────────────────────────── */
.card-blue {
--card-color-1: rgba(59, 130, 246, 0.1);
--card-color-2: rgba(6, 182, 212, 0.05);
--card-accent: linear-gradient(90deg, #3b82f6, #06b6d4);
}
.card-purple {
--card-color-1: rgba(139, 92, 246, 0.1);
--card-color-2: rgba(236, 72, 153, 0.05);
--card-accent: linear-gradient(90deg, #8b5cf6, #ec4899);
}
/* ── TECH STACK ────────────────────────────────────── */
.tech-section {
padding: 50px 0;
border-top: 1px solid var(--border);
margin-bottom: 60px;
}
.tech-grid {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-top: 24px;
}
.tech-chip {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 8px;
font-size: 0.78rem;
font-weight: 500;
color: var(--text-2);
transition: all 0.2s;
}
.tech-chip:hover {
border-color: var(--border-h);
color: var(--text-1);
background: var(--bg-card-h);
}
.tech-chip i {
font-size: 0.85rem;
}
/* ── FOOTER ────────────────────────────────────────── */
footer {
border-top: 1px solid var(--border);
padding: 32px 24px;
}
.footer-inner {
max-width: 1100px;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
}
.footer-brand {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.83rem;
font-weight: 600;
color: var(--text-2);
}
.footer-info {
font-size: 0.73rem;
color: var(--text-3);
}
.footer-info span {
color: var(--text-2);
}
/* ── SCROLL BAR ────────────────────────────────────── */
.scroll-indicator {
position: fixed;
top: 0;
left: 0;
height: 3px;
background: linear-gradient(90deg, var(--accent-1), var(--accent-2), var(--pink));
z-index: 9999;
transition: width 0.1s;
width: 0%;
}
/* ── RESPONSIVE ────────────────────────────────────── */
@media(max-width: 720px) {
.hero {
padding: 70px 0 50px;
}
.hero-stats {
gap: 24px;
}
.projects-grid {
grid-template-columns: 1fr;
}
.nav-links {
display: none;
}
.footer-inner {
flex-direction: column;
text-align: center;
}
}
</style>
</head>
<body>
<div class="scroll-indicator" id="scroll-bar"></div>
<canvas id="bg-canvas"></canvas>
<div class="grid-overlay"></div>
<!-- ── NAV ────────────────────────────────────────────── -->
<nav>
<div class="nav-inner">
<a class="nav-brand" href="#">
<div class="nav-logo"><i class="fas fa-globe" style="color:#fff"></i></div>
WebGIS Portal
</a>
<div class="nav-links">
<a href="#projects">Projects</a>
<a href="#stack">Tech Stack</a>
<a href="/?q=info" target="_blank">PHP Info</a>
</div>
<div class="nav-badge">
<span class="dot-live"></span>
2 Projects Online
</div>
</div>
</nav>
<!-- ── HERO ───────────────────────────────────────────── -->
<section class="hero">
<div class="wrapper">
<div class="hero-eyebrow">
<i class="fas fa-map-location-dot"></i>
Sistem Informasi Geografis
</div>
<h1 class="hero-title">
Portal <span class="gradient-text">WebGIS</span><br />Kota Pontianak
</h1>
<p class="hero-sub">
Pusat akses seluruh project Sistem Informasi Geografis berbasis web untuk pengelolaan data spasial wilayah Kota Pontianak.
</p>
<div class="hero-stats">
<div class="hero-stat">
<div class="hero-stat-num">2</div>
<div class="hero-stat-label">Project Aktif</div>
</div>
<div class="hero-stat">
<div class="hero-stat-num">6+</div>
<div class="hero-stat-label">Layer Data</div>
</div>
<div class="hero-stat">
<div class="hero-stat-num">PHP</div>
<div class="hero-stat-label"><?php echo PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION; ?></div>
</div>
<div class="hero-stat">
<div class="hero-stat-num">GIS</div>
<div class="hero-stat-label">LeafletJS</div>
</div>
</div>
</div>
</section>
<!-- ── PROJECTS ────────────────────────────────────────── -->
<section id="projects">
<div class="wrapper">
<div class="section-header">
<div class="section-label">Daftar Project</div>
<h2 class="section-title">Semua WebGIS Project</h2>
</div>
<div class="projects-grid">
<!-- ══ CARD 1: WebGIS Layer ══════════════════════════ -->
<div class="project-card card-blue" style="animation-delay:0.1s">
<span class="card-number">01</span>
<div class="card-icon-wrap" style="background:linear-gradient(135deg,rgba(59,130,246,0.2),rgba(6,182,212,0.2))">
<i class="fas fa-layer-group" style="color:#3b82f6"></i>
</div>
<div class="card-tag-row">
<span class="tag" style="background:rgba(59,130,246,0.15);color:#60a5fa;border:1px solid rgba(59,130,246,0.3)">
<i class="fas fa-road"></i> Jalan
</span>
<span class="tag" style="background:rgba(139,92,246,0.15);color:#a78bfa;border:1px solid rgba(139,92,246,0.3)">
<i class="fas fa-draw-polygon"></i> Parsil
</span>
<span class="tag" style="background:rgba(16,185,129,0.15);color:#34d399;border:1px solid rgba(16,185,129,0.3)">
<i class="fas fa-gas-pump"></i> SPBU
</span>
</div>
<h3 class="card-title">WebGIS Layer — Jalan, Parsil & SPBU</h3>
<p class="card-desc">
Sistem informasi geografis untuk manajemen data jalan (nasional, provinsi, kabupaten), parsil tanah beserta status kepemilikan, dan sebaran SPBU di Kota Pontianak.
</p>
<div class="card-features">
<div class="card-feature">
<i class="fas fa-check-circle" style="color:#3b82f6"></i>
<span>Gambar polyline & polygon langsung di peta</span>
</div>
<div class="card-feature">
<i class="fas fa-check-circle" style="color:#3b82f6"></i>
<span>CRUD data jalan, parsil tanah & SPBU</span>
</div>
<div class="card-feature">
<i class="fas fa-check-circle" style="color:#3b82f6"></i>
<span>Kalkulasi panjang & luas otomatis (LeafletJS)</span>
</div>
<div class="card-feature">
<i class="fas fa-check-circle" style="color:#3b82f6"></i>
<span>Layer switcher: Street & Satellite view</span>
</div>
</div>
<div class="card-footer">
<div class="card-url">
<i class="fas fa-link"></i>
/Web GIS Layer/
</div>
<a href="./Web%20GIS%20Layer/index.html"
class="btn-open"
style="background:linear-gradient(135deg,#3b82f6,#06b6d4)"
onclick="window.location.href=this.href; return false;">
Buka Project <i class="fas fa-arrow-right"></i>
</a>
</div>
</div>
<!-- ══ CARD 2: WebGIS Poverty Mapping ════════════════ -->
<div class="project-card card-purple" style="animation-delay:0.2s">
<span class="card-number">02</span>
<div class="card-icon-wrap" style="background:linear-gradient(135deg,rgba(139,92,246,0.2),rgba(236,72,153,0.2))">
<i class="fas fa-house-chimney-crack" style="color:#a855f7"></i>
</div>
<div class="card-tag-row">
<span class="tag" style="background:rgba(139,92,246,0.15);color:#a78bfa;border:1px solid rgba(139,92,246,0.3)">
<i class="fas fa-users"></i> Kemiskinan
</span>
<span class="tag" style="background:rgba(236,72,153,0.15);color:#f472b6;border:1px solid rgba(236,72,153,0.3)">
<i class="fas fa-mosque"></i> Rumah Ibadah
</span>
<span class="tag" style="background:rgba(245,158,11,0.15);color:#fbbf24;border:1px solid rgba(245,158,11,0.3)">
<i class="fas fa-hand-holding-heart"></i> Bantuan
</span>
</div>
<h3 class="card-title">WebGIS Poverty Mapping & Rumah Ibadah</h3>
<p class="card-desc">
Pemetaan keluarga miskin, histori penyaluran bantuan sosial, dan lokasi rumah ibadah berbasis peta interaktif. Dilengkapi dashboard analitik dan laporan spasial.
</p>
<div class="card-features">
<div class="card-feature">
<i class="fas fa-check-circle" style="color:#a855f7"></i>
<span>Pemetaan & manajemen data keluarga miskin</span>
</div>
<div class="card-feature">
<i class="fas fa-check-circle" style="color:#a855f7"></i>
<span>Histori & tracking bantuan sosial</span>
</div>
<div class="card-feature">
<i class="fas fa-check-circle" style="color:#a855f7"></i>
<span>Lokasi rumah ibadah (masjid, gereja, dll.)</span>
</div>
<div class="card-feature">
<i class="fas fa-check-circle" style="color:#a855f7"></i>
<span>Dashboard statistik & laporan lengkap</span>
</div>
</div>
<div class="card-footer">
<div class="card-url">
<i class="fas fa-link"></i>
/Web GIS Rumah Ibadah/
</div>
<a href="./Web%20GIS%20Rumah%20Ibadah/index.html"
class="btn-open"
style="background:linear-gradient(135deg,#8b5cf6,#ec4899)"
onclick="window.location.href=this.href; return false;">
Buka Project <i class="fas fa-arrow-right"></i>
</a>
</div>
</div>
</div><!-- /.projects-grid -->
<!-- ── TECH STACK ──────────────────────────────────── -->
<section id="stack" class="tech-section">
<div class="section-label">Teknologi</div>
<h2 class="section-title" style="margin-bottom:0">Tech Stack yang Digunakan</h2>
<div class="tech-grid">
<div class="tech-chip"><i class="fab fa-html5" style="color:#e34f26"></i> HTML5 & CSS3</div>
<div class="tech-chip"><i class="fab fa-js" style="color:#f7df1e"></i> JavaScript</div>
<div class="tech-chip"><i class="fab fa-php" style="color:#777bb4"></i> PHP <?php echo phpversion(); ?></div>
<div class="tech-chip"><i class="fas fa-database" style="color:#4479a1"></i> MySQL / MariaDB</div>
<div class="tech-chip"><i class="fas fa-map" style="color:#3b82f6"></i> Leaflet.js 1.9.4</div>
<div class="tech-chip"><i class="fas fa-pencil-ruler" style="color:#06b6d4"></i> Leaflet.Draw</div>
<div class="tech-chip"><i class="fas fa-ruler" style="color:#10b981"></i> Leaflet.GeometryUtil</div>
<div class="tech-chip"><i class="fas fa-globe" style="color:#f59e0b"></i> OpenStreetMap</div>
<div class="tech-chip"><i class="fas fa-satellite" style="color:#8b5cf6"></i> Esri Satellite</div>
<div class="tech-chip"><i class="fab fa-font-awesome" style="color:#528dd7"></i> Font Awesome 6</div>
<div class="tech-chip"><i class="fas fa-server" style="color:#ec4899"></i> Laragon</div>
<div class="tech-chip"><i class="fas fa-code-branch" style="color:#22d3ee"></i> GeoJSON Format</div>
</div>
</section>
</div><!-- /.wrapper -->
</section>
<!-- ── FOOTER ──────────────────────────────────────────── -->
<footer>
<div class="footer-inner">
<div class="footer-brand">
<div class="nav-logo" style="width:28px;height:28px;font-size:.8rem">
<i class="fas fa-globe" style="color:#fff"></i>
</div>
WebGIS Portal — Kota Pontianak
<div class="footer-info">
Dibangun dengan <span style="color:#ec4899">♥</span> menggunakan LeafletJS & PHP
</div>
</div>
</footer>
<script>
/* ── ANIMATED PARTICLE CANVAS ─────────────────────────── */
(function() {
const canvas = document.getElementById('bg-canvas');
const ctx = canvas.getContext('2d');
let W, H, particles = [];
function resize() {
W = canvas.width = window.innerWidth;
H = canvas.height = window.innerHeight;
}
resize();
window.addEventListener('resize', resize);
class Particle {
constructor() {
this.reset();
}
reset() {
this.x = Math.random() * W;
this.y = Math.random() * H;
this.r = Math.random() * 1.5 + 0.3;
this.vx = (Math.random() - 0.5) * 0.25;
this.vy = (Math.random() - 0.5) * 0.25;
this.a = Math.random() * 0.5 + 0.1;
const p = ['59,130,246', '139,92,246', '6,182,212', '16,185,129'];
this.color = p[Math.floor(Math.random() * p.length)];
}
update() {
this.x += this.vx;
this.y += this.vy;
if (this.x < 0 || this.x > W || this.y < 0 || this.y > H) this.reset();
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2);
ctx.fillStyle = `rgba(${this.color},${this.a})`;
ctx.fill();
}
}
for (let i = 0; i < 120; i++) particles.push(new Particle());
function drawLines() {
for (let i = 0; i < particles.length; i++)
for (let j = i + 1; j < particles.length; j++) {
const dx = particles[i].x - particles[j].x,
dy = particles[i].y - particles[j].y;
const d = Math.sqrt(dx * dx + dy * dy);
if (d < 100) {
ctx.beginPath();
ctx.moveTo(particles[i].x, particles[i].y);
ctx.lineTo(particles[j].x, particles[j].y);
ctx.strokeStyle = `rgba(59,130,246,${0.06*(1-d/100)})`;
ctx.lineWidth = 0.5;
ctx.stroke();
}
}
}
function animate() {
ctx.clearRect(0, 0, W, H);
particles.forEach(p => {
p.update();
p.draw();
});
drawLines();
requestAnimationFrame(animate);
}
animate();
})();
/* ── SCROLL PROGRESS ──────────────────────────────────── */
window.addEventListener('scroll', () => {
const st = document.documentElement.scrollTop;
const sh = document.documentElement.scrollHeight - document.documentElement.clientHeight;
document.getElementById('scroll-bar').style.width = (st / sh * 100) + '%';
});
/* ── CARD TILT ────────────────────────────────────────── */
document.querySelectorAll('.project-card').forEach(card => {
card.addEventListener('mousemove', e => {
const r = card.getBoundingClientRect();
const x = ((e.clientX - r.left) / r.width - 0.5) * 8;
const y = ((e.clientY - r.top) / r.height - 0.5) * 8;
card.style.transform = `translateY(-6px) rotateX(${-y}deg) rotateY(${x}deg)`;
});
card.addEventListener('mouseleave', () => {
card.style.transform = '';
});
});
/* ── INTERSECTION OBSERVER ────────────────────────────── */
const obs = new IntersectionObserver(entries => {
entries.forEach(e => {
if (e.isIntersecting) e.target.style.animationPlayState = 'running';
});
}, {
threshold: 0.1
});
document.querySelectorAll('.project-card,.tech-chip').forEach(el => {
el.style.animationPlayState = 'paused';
obs.observe(el);
});
</script>
</body>
</html>