ui update
This commit is contained in:
+489
-275
@@ -428,34 +428,26 @@ function useToast() {
|
||||
return [toast, show];
|
||||
}
|
||||
|
||||
// ─── Landing Page (redesigned) ────────────────────────────────────────────────
|
||||
// ─── Landing Page — Editorial "Puls" style ────────────────────────────────────
|
||||
const LP = {
|
||||
bg: "#F5F6F8",
|
||||
bgHero: "#FFFFFF",
|
||||
bg: "#F7F6F3",
|
||||
surface: "#FFFFFF",
|
||||
surfaceHover: "#F0F4FF",
|
||||
border: "rgba(0,0,0,0.08)",
|
||||
label: "#0F1724",
|
||||
labelSub: "#5A6474",
|
||||
labelMuted: "#9BA5B4",
|
||||
accent: "#2563EB",
|
||||
accentLight: "rgba(37,99,235,0.08)",
|
||||
accentBorder: "rgba(37,99,235,0.2)",
|
||||
amber: "#D97706",
|
||||
amberLight: "rgba(217,119,6,0.08)",
|
||||
blue: "#2563EB",
|
||||
blueLight: "rgba(37,99,235,0.08)",
|
||||
red: "#DC2626",
|
||||
redLight: "rgba(220,38,38,0.08)",
|
||||
green: "#059669",
|
||||
greenLight: "rgba(5,150,105,0.08)",
|
||||
separator: "rgba(0,0,0,0.06)",
|
||||
label: "#0A0A0A",
|
||||
labelSub: "#5C5C5C",
|
||||
labelMuted: "#A0A0A0",
|
||||
accent: "#E8334A", // coral-red accent
|
||||
accentWarm: "#FF6B35", // warm orange for variety
|
||||
border: "rgba(0,0,0,0.09)",
|
||||
g1: ["#FF9500", "#FF6B00"],
|
||||
g2: ["#007AFF", "#0040DD"],
|
||||
g3: ["#E8334A", "#B01E31"],
|
||||
g4: ["#34C759", "#248A3D"],
|
||||
};
|
||||
|
||||
// SVG icon components for each project
|
||||
function IconPin() {
|
||||
return (
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/>
|
||||
<circle cx="12" cy="10" r="3"/>
|
||||
</svg>
|
||||
@@ -463,14 +455,15 @@ function IconPin() {
|
||||
}
|
||||
function IconRoad() {
|
||||
return (
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M3 17l3-10 3 4 3-8 3 4 3-10"/>
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z"/>
|
||||
<path d="M13 13l6 6"/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
function IconUsers() {
|
||||
return (
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
|
||||
<circle cx="9" cy="7" r="4"/>
|
||||
<path d="M23 21v-2a4 4 0 0 0-3-3.87"/>
|
||||
@@ -480,7 +473,7 @@ function IconUsers() {
|
||||
}
|
||||
function IconLayers() {
|
||||
return (
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
||||
<polygon points="12 2 2 7 12 12 22 7 12 2"/>
|
||||
<polyline points="2 17 12 22 22 17"/>
|
||||
<polyline points="2 12 12 17 22 12"/>
|
||||
@@ -495,17 +488,19 @@ const projects = [
|
||||
title: "SPBU Map",
|
||||
subtitle: "Point Layer",
|
||||
description: "Tambah, hapus, dan geser marker SPBU. Data tersimpan ke MySQL.",
|
||||
accent: LP.amber,
|
||||
accentLight: LP.amberLight,
|
||||
gradient: LP.g1,
|
||||
gradId: "g1",
|
||||
tag: "Titik",
|
||||
},
|
||||
{
|
||||
id: "p02",
|
||||
Icon: IconRoad,
|
||||
title: "WebGIS Jalan & Tanah",
|
||||
title: "Jalan & Tanah",
|
||||
subtitle: "Vector Edit",
|
||||
description: "Gambar dan edit polyline jalan serta polygon parsil tanah. CRUD penuh.",
|
||||
accent: LP.blue,
|
||||
accentLight: LP.blueLight,
|
||||
gradient: LP.g2,
|
||||
gradId: "g2",
|
||||
tag: "Vektor",
|
||||
},
|
||||
{
|
||||
id: "p03",
|
||||
@@ -513,349 +508,568 @@ const projects = [
|
||||
title: "Kepadatan Penduduk",
|
||||
subtitle: "Choropleth",
|
||||
description: "Koropleth kepadatan per kecamatan dengan tiga kelas warna. Data BPS.",
|
||||
accent: LP.red,
|
||||
accentLight: LP.redLight,
|
||||
gradient: LP.g3,
|
||||
gradId: "g3",
|
||||
tag: "Analisis",
|
||||
},
|
||||
{
|
||||
id: "p04",
|
||||
Icon: IconLayers,
|
||||
title: "SPBU + Layer Toggle",
|
||||
title: "SPBU Layer Toggle",
|
||||
subtitle: "Layer Control",
|
||||
description: "Kontrol layer toggle untuk memisahkan SPBU berdasarkan jam operasi.",
|
||||
accent: LP.green,
|
||||
accentLight: LP.greenLight,
|
||||
gradient: LP.g4,
|
||||
gradId: "g4",
|
||||
tag: "Layer",
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
const landingCss = `
|
||||
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700;800&family=DM+Sans:wght@300;400;500;600&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Inter:wght@300;400;500;600;700&display=swap');
|
||||
|
||||
@keyframes lpFadeUp {
|
||||
from { opacity: 0; transform: translateY(12px); }
|
||||
from { opacity: 0; transform: translateY(16px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
@keyframes lpFadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
@keyframes lpSlideLeft {
|
||||
from { opacity: 0; transform: translateX(20px); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
}
|
||||
|
||||
.lp-root {
|
||||
min-height: 100vh;
|
||||
background: #F5F6F8;
|
||||
font-family: 'DM Sans', -apple-system, sans-serif;
|
||||
background: #F7F6F3;
|
||||
font-family: 'Inter', -apple-system, 'Helvetica Neue', sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
overflow-x: hidden;
|
||||
color: #0A0A0A;
|
||||
}
|
||||
|
||||
/* ── hero ── */
|
||||
/* ─── NAV ─── */
|
||||
.lp-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 28px;
|
||||
height: 56px;
|
||||
position: sticky; top: 0; z-index: 200;
|
||||
background: rgba(247,246,243,0.88);
|
||||
backdrop-filter: blur(20px) saturate(160%);
|
||||
-webkit-backdrop-filter: blur(20px) saturate(160%);
|
||||
border-bottom: 1px solid rgba(0,0,0,0.07);
|
||||
}
|
||||
.lp-nav-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.lp-nav-item {
|
||||
padding: 6px 14px;
|
||||
border-radius: 100px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #5C5C5C;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s, color 0.15s;
|
||||
border: none;
|
||||
background: transparent;
|
||||
font-family: inherit;
|
||||
}
|
||||
.lp-nav-item:hover { background: rgba(0,0,0,0.05); color: #0A0A0A; }
|
||||
.lp-nav-item.active {
|
||||
background: #0A0A0A;
|
||||
color: #F7F6F3;
|
||||
}
|
||||
.lp-nav-logo {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.3px;
|
||||
color: #0A0A0A;
|
||||
}
|
||||
.lp-nav-cta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 7px 16px;
|
||||
background: #0A0A0A;
|
||||
color: #F7F6F3;
|
||||
border-radius: 100px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
font-family: inherit;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
.lp-nav-cta:hover { opacity: 0.82; }
|
||||
|
||||
/* ─── HERO SPLIT ─── */
|
||||
.lp-hero {
|
||||
background: #FFFFFF;
|
||||
border-bottom: 1px solid rgba(0,0,0,0.06);
|
||||
padding: 72px 24px 60px;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
min-height: 100vh;
|
||||
max-height: 900px;
|
||||
}
|
||||
|
||||
/* LEFT PANEL */
|
||||
.lp-hero-left {
|
||||
position: relative;
|
||||
padding: 40px 28px 32px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
border-right: 1px solid rgba(0,0,0,0.07);
|
||||
overflow: hidden;
|
||||
animation: lpFadeUp 0.5s 0.05s both;
|
||||
}
|
||||
|
||||
/* Subtle dot grid — static, no animation */
|
||||
.lp-hero::before {
|
||||
/* Subtle grid lines — cartographic feel */
|
||||
.lp-hero-left::before {
|
||||
content: '';
|
||||
position: absolute; inset: 0;
|
||||
background-image: radial-gradient(circle, rgba(37,99,235,0.08) 1px, transparent 1px);
|
||||
background-size: 28px 28px;
|
||||
pointer-events: none;
|
||||
}
|
||||
/* Soft vignette over dot grid */
|
||||
.lp-hero::after {
|
||||
content: '';
|
||||
position: absolute; inset: 0;
|
||||
background: radial-gradient(ellipse 70% 80% at 50% 50%, rgba(255,255,255,0.0) 40%, rgba(255,255,255,0.95) 100%);
|
||||
background-image:
|
||||
linear-gradient(rgba(0,0,0,0.04) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(0,0,0,0.04) 1px, transparent 1px);
|
||||
background-size: 40px 40px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* All hero content sits above pseudo-elements */
|
||||
.lp-hero > * { position: relative; z-index: 1; }
|
||||
|
||||
/* ── eyebrow ── */
|
||||
.lp-eyebrow {
|
||||
.lp-left-eyebrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
background: rgba(37,99,235,0.06);
|
||||
border: 1px solid rgba(37,99,235,0.18);
|
||||
background: #FFFFFF;
|
||||
border: 1px solid rgba(0,0,0,0.10);
|
||||
border-radius: 100px;
|
||||
padding: 5px 14px;
|
||||
padding: 5px 12px 5px 8px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.11em;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
color: #2563EB;
|
||||
margin-bottom: 22px;
|
||||
animation: lpFadeUp 0.45s 0.05s both;
|
||||
color: #5C5C5C;
|
||||
width: fit-content;
|
||||
margin-bottom: 28px;
|
||||
position: relative; z-index: 1;
|
||||
}
|
||||
.lp-eyebrow-dot {
|
||||
.lp-eyebrow-pip {
|
||||
width: 6px; height: 6px;
|
||||
border-radius: 50%;
|
||||
background: #2563EB;
|
||||
background: #E8334A;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ── headline ── */
|
||||
.lp-headline {
|
||||
font-family: 'Playfair Display', Georgia, serif;
|
||||
font-size: clamp(36px, 7vw, 64px);
|
||||
font-weight: 800;
|
||||
line-height: 1.05;
|
||||
letter-spacing: -0.02em;
|
||||
color: #0F1724;
|
||||
margin-bottom: 16px;
|
||||
animation: lpFadeUp 0.45s 0.12s both;
|
||||
}
|
||||
.lp-headline em {
|
||||
font-style: normal;
|
||||
color: #2563EB;
|
||||
}
|
||||
|
||||
/* ── subhead ── */
|
||||
.lp-sub {
|
||||
font-size: 15px;
|
||||
.lp-left-heading {
|
||||
font-family: 'Instrument Serif', Georgia, serif;
|
||||
font-size: clamp(38px, 5.5vw, 58px);
|
||||
font-weight: 400;
|
||||
color: #5A6474;
|
||||
max-width: 360px;
|
||||
line-height: 1.65;
|
||||
margin-bottom: 28px;
|
||||
animation: lpFadeUp 0.45s 0.18s both;
|
||||
line-height: 1.06;
|
||||
letter-spacing: -0.02em;
|
||||
color: #0A0A0A;
|
||||
margin-bottom: 0;
|
||||
position: relative; z-index: 1;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* ── coordinates strip ── */
|
||||
.lp-coords {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
align-items: center;
|
||||
animation: lpFadeUp 0.45s 0.24s both;
|
||||
.lp-left-heading .accent {
|
||||
color: #E8334A;
|
||||
font-style: italic;
|
||||
}
|
||||
.lp-coord-item {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.06em;
|
||||
color: #9BA5B4;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.lp-coord-sep {
|
||||
width: 1px; height: 10px;
|
||||
background: rgba(0,0,0,0.12);
|
||||
}
|
||||
|
||||
/* ── cards ── */
|
||||
.lp-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
max-width: 660px;
|
||||
margin: 0 auto;
|
||||
padding: 32px 20px 0;
|
||||
animation: lpFadeUp 0.45s 0.28s both;
|
||||
}
|
||||
@media (max-width: 500px) {
|
||||
.lp-cards { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
.lp-card {
|
||||
.lp-left-heading .circle-wrap {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid rgba(0,0,0,0.08);
|
||||
}
|
||||
.lp-left-heading .circle-wrap svg {
|
||||
position: absolute;
|
||||
top: 50%; left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: calc(100% + 16px);
|
||||
height: calc(100% + 12px);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Floating project mini-cards */
|
||||
.lp-floats {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
margin-top: 28px;
|
||||
position: relative; z-index: 1;
|
||||
}
|
||||
.lp-float-card {
|
||||
background: rgba(255,255,255,0.9);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(0,0,0,0.09);
|
||||
border-radius: 14px;
|
||||
padding: 20px 18px 42px;
|
||||
padding: 14px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.18s, transform 0.18s, box-shadow 0.18s, background 0.18s;
|
||||
text-align: left;
|
||||
transition: transform 0.18s cubic-bezier(0.34,1.56,0.64,1), box-shadow 0.18s;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.lp-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 28px rgba(0,0,0,0.08);
|
||||
.lp-float-card:hover {
|
||||
transform: translateY(-3px) scale(1.015);
|
||||
box-shadow: 0 12px 32px rgba(0,0,0,0.10);
|
||||
}
|
||||
|
||||
.lp-card-icon-wrap {
|
||||
width: 40px; height: 40px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 14px;
|
||||
transition: background 0.18s;
|
||||
}
|
||||
|
||||
.lp-card-title {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #0F1724;
|
||||
letter-spacing: -0.01em;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.lp-card-subtitle {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.09em;
|
||||
margin-bottom: 9px;
|
||||
transition: color 0.18s;
|
||||
}
|
||||
.lp-card-desc {
|
||||
font-size: 12.5px;
|
||||
color: #5A6474;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
/* Arrow — bottom-right, slides in on hover */
|
||||
.lp-card-arrow {
|
||||
.lp-float-card:active { transform: scale(0.97); }
|
||||
.lp-float-arrow {
|
||||
position: absolute;
|
||||
bottom: 16px; right: 16px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.18s, transform 0.18s;
|
||||
transform: translateX(-6px);
|
||||
top: 10px; right: 10px;
|
||||
width: 22px; height: 22px;
|
||||
border-radius: 50%;
|
||||
background: #0A0A0A;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.lp-card:hover .lp-card-arrow {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
.lp-float-tag {
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: #A0A0A0;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.lp-float-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #0A0A0A;
|
||||
letter-spacing: -0.2px;
|
||||
line-height: 1.3;
|
||||
padding-right: 24px;
|
||||
}
|
||||
.lp-float-desc {
|
||||
font-size: 11px;
|
||||
color: #5C5C5C;
|
||||
line-height: 1.45;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.lp-float-icon {
|
||||
width: 32px; height: 32px;
|
||||
border-radius: 9px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
margin-bottom: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ── divider ── */
|
||||
.lp-divider {
|
||||
width: 100%;
|
||||
max-width: 660px;
|
||||
margin: 28px auto 0;
|
||||
padding: 0 20px;
|
||||
/* Coords bar bottom-left */
|
||||
.lp-coords-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
gap: 12px;
|
||||
margin-top: 20px;
|
||||
position: relative; z-index: 1;
|
||||
}
|
||||
.lp-divider-line {
|
||||
flex: 1; height: 1px;
|
||||
background: rgba(0,0,0,0.07);
|
||||
}
|
||||
.lp-divider-text {
|
||||
.lp-coord-chip {
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: #9BA5B4;
|
||||
white-space: nowrap;
|
||||
letter-spacing: 0.05em;
|
||||
color: #A0A0A0;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.lp-coord-div {
|
||||
width: 1px; height: 8px;
|
||||
background: rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
/* ── footer ── */
|
||||
.lp-footer {
|
||||
/* RIGHT PANEL */
|
||||
.lp-hero-right {
|
||||
padding: 48px 36px 40px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
animation: lpSlideLeft 0.5s 0.15s both;
|
||||
}
|
||||
|
||||
.lp-right-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border: 1px solid rgba(0,0,0,0.14);
|
||||
border-radius: 100px;
|
||||
padding: 4px 12px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.04em;
|
||||
color: #5C5C5C;
|
||||
width: fit-content;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.lp-right-heading {
|
||||
font-family: 'Instrument Serif', Georgia, serif;
|
||||
font-size: clamp(34px, 4.5vw, 52px);
|
||||
font-weight: 400;
|
||||
line-height: 1.08;
|
||||
letter-spacing: -0.02em;
|
||||
color: #0A0A0A;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.lp-right-heading .accent { color: #E8334A; font-style: italic; }
|
||||
.lp-right-heading .circle-word {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
padding: 0 6px;
|
||||
}
|
||||
.lp-right-heading .circle-word::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: -1px 0;
|
||||
border: 1.5px solid #0A0A0A;
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
.lp-right-body {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #5C5C5C;
|
||||
line-height: 1.65;
|
||||
max-width: 340px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.lp-right-cta {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: #E8334A;
|
||||
color: #FFFFFF;
|
||||
border-radius: 100px;
|
||||
padding: 12px 22px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
font-family: inherit;
|
||||
width: fit-content;
|
||||
transition: opacity 0.15s, transform 0.15s;
|
||||
letter-spacing: -0.1px;
|
||||
}
|
||||
.lp-right-cta:hover { opacity: 0.88; transform: translateY(-1px); }
|
||||
.lp-right-cta:active { transform: scale(0.97); }
|
||||
.lp-cta-arrow {
|
||||
width: 22px; height: 22px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255,255,255,0.22);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
|
||||
/* Stats below CTA */
|
||||
.lp-right-stats {
|
||||
display: flex;
|
||||
gap: 28px;
|
||||
margin-top: 40px;
|
||||
padding-top: 28px;
|
||||
border-top: 1px solid rgba(0,0,0,0.08);
|
||||
}
|
||||
.lp-stat-item {}
|
||||
.lp-stat-num {
|
||||
font-family: 'Instrument Serif', Georgia, serif;
|
||||
font-size: 32px;
|
||||
font-weight: 400;
|
||||
letter-spacing: -0.03em;
|
||||
color: #0A0A0A;
|
||||
line-height: 1;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
.lp-stat-num .accent { color: #E8334A; }
|
||||
.lp-stat-label {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
color: #A0A0A0;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
/* ─── BOTTOM TICKER ─── */
|
||||
.lp-ticker {
|
||||
border-top: 1px solid rgba(0,0,0,0.09);
|
||||
background: #FFFFFF;
|
||||
padding: 28px 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
padding: 20px 20px 40px;
|
||||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
animation: lpFadeIn 0.5s 0.35s both;
|
||||
}
|
||||
.lp-footer-item {
|
||||
font-size: 11px;
|
||||
color: #9BA5B4;
|
||||
letter-spacing: 0.03em;
|
||||
.lp-ticker-main {
|
||||
font-family: 'Instrument Serif', Georgia, serif;
|
||||
font-size: clamp(26px, 3.5vw, 42px);
|
||||
font-weight: 400;
|
||||
letter-spacing: -0.02em;
|
||||
color: #0A0A0A;
|
||||
line-height: 1.1;
|
||||
}
|
||||
.lp-footer-dot {
|
||||
width: 3px; height: 3px;
|
||||
border-radius: 50%;
|
||||
background: #CBD2DC;
|
||||
.lp-ticker-main .accent { color: #E8334A; font-style: italic; }
|
||||
.lp-ticker-main .circle-num {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
padding: 0 6px;
|
||||
}
|
||||
.lp-ticker-main .circle-num::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: -2px 0;
|
||||
border: 1.5px solid #0A0A0A;
|
||||
border-radius: 100px;
|
||||
}
|
||||
.lp-ticker-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.lp-ticker-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border: 1px solid rgba(0,0,0,0.14);
|
||||
border-radius: 100px;
|
||||
padding: 4px 12px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
color: #5C5C5C;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.lp-ticker-sub {
|
||||
font-size: 12px;
|
||||
color: #A0A0A0;
|
||||
text-align: right;
|
||||
max-width: 180px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* ─── RESPONSIVE ─── */
|
||||
@media (max-width: 680px) {
|
||||
.lp-hero { grid-template-columns: 1fr; max-height: none; }
|
||||
.lp-hero-left { border-right: none; border-bottom: 1px solid rgba(0,0,0,0.07); min-height: auto; }
|
||||
.lp-hero-right { padding: 32px 24px; }
|
||||
.lp-nav { padding: 0 16px; }
|
||||
.lp-ticker { flex-direction: column; align-items: flex-start; padding: 24px; }
|
||||
.lp-ticker-right { align-items: flex-start; }
|
||||
}
|
||||
`;
|
||||
|
||||
function LandingPage({ onSelect }) {
|
||||
const [hoveredCard, setHoveredCard] = useState(null);
|
||||
|
||||
return (
|
||||
<div className="lp-root">
|
||||
<style>{landingCss}</style>
|
||||
|
||||
{/* ── Hero ── */}
|
||||
{/* ── Hero Split ── */}
|
||||
<div className="lp-hero">
|
||||
<div className="lp-eyebrow">
|
||||
<div className="lp-eyebrow-dot" />
|
||||
Tugas WebGIS · Pontianak
|
||||
|
||||
{/* LEFT: heading + floating cards */}
|
||||
<div className="lp-hero-left">
|
||||
<div className="lp-left-eyebrow">
|
||||
<div className="lp-eyebrow-pip" />
|
||||
Tugas WebGIS · Pontianak
|
||||
</div>
|
||||
|
||||
<div className="lp-left-heading">
|
||||
Peta{" "}
|
||||
<span className="accent">interaktif</span>
|
||||
<br />
|
||||
Kota{" "}
|
||||
<span className="circle-wrap">
|
||||
Pontianak
|
||||
<svg viewBox="0 0 1 1" fill="none" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none">
|
||||
<ellipse cx="0.5" cy="0.5" rx="0.48" ry="0.48" stroke="#0A0A0A" strokeWidth="0.04" fill="none"/>
|
||||
</svg>
|
||||
</span>
|
||||
<br />
|
||||
berbasis{" "}
|
||||
<span className="accent">GIS</span>
|
||||
</div>
|
||||
|
||||
{/* Floating project cards */}
|
||||
<div className="lp-floats">
|
||||
{projects.map(p => (
|
||||
<FloatCard key={p.id} project={p} onSelect={onSelect} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Coordinate strip */}
|
||||
<div className="lp-coords-bar">
|
||||
<span className="lp-coord-chip">0°01′55″ S</span>
|
||||
<div className="lp-coord-div" />
|
||||
<span className="lp-coord-chip">109°20′58″ E</span>
|
||||
<div className="lp-coord-div" />
|
||||
<span className="lp-coord-chip">Kalimantan Barat</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1 className="lp-headline">
|
||||
Peta Kota<br /><em>Pontianak</em>
|
||||
</h1>
|
||||
{/* RIGHT: editorial copy + CTA */}
|
||||
<div className="lp-hero-right">
|
||||
<div className="lp-right-tag">Pemetaan Digital</div>
|
||||
|
||||
<p className="lp-sub">
|
||||
Empat proyek pemetaan interaktif berbasis Leaflet.js — dari SPBU hingga kepadatan penduduk.
|
||||
</p>
|
||||
<div className="lp-right-heading">
|
||||
Jelajahi data{" "}
|
||||
<span className="accent">spasial</span>
|
||||
<br />
|
||||
Pontianak dengan{" "}
|
||||
<span className="circle-word">presisi</span>
|
||||
</div>
|
||||
|
||||
<div className="lp-coords">
|
||||
<span className="lp-coord-item">0°01′55″S</span>
|
||||
<div className="lp-coord-sep" />
|
||||
<span className="lp-coord-item">109°20′58″E</span>
|
||||
<div className="lp-coord-sep" />
|
||||
<span className="lp-coord-item">Kalimantan Barat</span>
|
||||
<p className="lp-right-body">
|
||||
Empat proyek WebGIS yang menggabungkan data titik, vektor, dan analisis koropleth — langsung di browser, tanpa instalasi.
|
||||
</p>
|
||||
|
||||
<button className="lp-right-cta" onClick={() => onSelect("p01")}>
|
||||
Jelajahi Proyek
|
||||
<div className="lp-cta-arrow">
|
||||
<svg width="10" height="10" viewBox="0 0 12 12" fill="none">
|
||||
<path d="M2 10L10 2M10 2H4M10 2v6" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="lp-right-stats">
|
||||
<div className="lp-stat-item">
|
||||
<div className="lp-stat-num"><span className="accent">4</span></div>
|
||||
<div className="lp-stat-label">Proyek Aktif</div>
|
||||
</div>
|
||||
<div className="lp-stat-item">
|
||||
<div className="lp-stat-num">6</div>
|
||||
<div className="lp-stat-label">Kecamatan</div>
|
||||
</div>
|
||||
<div className="lp-stat-item">
|
||||
<div className="lp-stat-num">5+</div>
|
||||
<div className="lp-stat-label">Layer Peta</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── Cards ── */}
|
||||
<div className="lp-cards">
|
||||
{projects.map((p, i) => (
|
||||
<ProjectCard key={p.id} project={p} index={i} onSelect={onSelect} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* ── Divider + Footer ── */}
|
||||
<div className="lp-divider">
|
||||
<div className="lp-divider-line" />
|
||||
<span className="lp-divider-text">Leaflet.js · OpenStreetMap · MySQL</span>
|
||||
<div className="lp-divider-line" />
|
||||
</div>
|
||||
|
||||
<div className="lp-footer">
|
||||
<span className="lp-footer-item">Leaflet.js</span>
|
||||
<div className="lp-footer-dot" />
|
||||
<span className="lp-footer-item">OpenStreetMap</span>
|
||||
<div className="lp-footer-dot" />
|
||||
<span className="lp-footer-item">MySQL</span>
|
||||
<div className="lp-footer-dot" />
|
||||
<span className="lp-footer-item">React</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ProjectCard({ project: p, index, onSelect }) {
|
||||
const [hovered, setHovered] = useState(false);
|
||||
function FloatCard({ project: p, onSelect }) {
|
||||
const { Icon } = p;
|
||||
return (
|
||||
<div
|
||||
className="lp-card"
|
||||
className="lp-float-card"
|
||||
onClick={() => onSelect(p.id)}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
style={{
|
||||
borderColor: hovered ? p.accent + "44" : "rgba(0,0,0,0.08)",
|
||||
}}
|
||||
>
|
||||
{/* Icon */}
|
||||
<div
|
||||
className="lp-card-icon-wrap"
|
||||
style={{ background: hovered ? p.accentLight : "rgba(0,0,0,0.04)" }}
|
||||
className="lp-float-icon"
|
||||
style={{ background: `linear-gradient(145deg, ${p.gradient[0]}, ${p.gradient[1]})` }}
|
||||
>
|
||||
<span style={{ color: hovered ? p.accent : "#5A6474", transition: "color 0.18s" }}>
|
||||
<Icon />
|
||||
</span>
|
||||
<span style={{ color: "#fff" }}><Icon /></span>
|
||||
</div>
|
||||
|
||||
<div className="lp-card-title">{p.title}</div>
|
||||
<div
|
||||
className="lp-card-subtitle"
|
||||
style={{ color: hovered ? p.accent : LP.labelMuted }}
|
||||
>
|
||||
{p.subtitle}
|
||||
<div className="lp-float-tag">{p.tag}</div>
|
||||
<div className="lp-float-title">{p.title}</div>
|
||||
<div className="lp-float-desc">{p.subtitle}</div>
|
||||
<div className="lp-float-arrow">
|
||||
<svg width="9" height="9" viewBox="0 0 12 12" fill="none">
|
||||
<path d="M2 10L10 2M10 2H4M10 2v6" stroke="white" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="lp-card-desc">{p.description}</div>
|
||||
|
||||
{/* Arrow */}
|
||||
<svg className="lp-card-arrow" width="14" height="14" viewBox="0 0 24 24"
|
||||
fill="none" stroke={p.accent} strokeWidth="2.5">
|
||||
<path d="M5 12h14M12 5l7 7-7 7"/>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user