diff --git a/src/App.jsx b/src/App.jsx index b6a4f66..f639a94 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -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 ( - + @@ -463,14 +455,15 @@ function IconPin() { } function IconRoad() { return ( - - + + + ); } function IconUsers() { return ( - + @@ -480,7 +473,7 @@ function IconUsers() { } function IconLayers() { return ( - + @@ -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 (
- {/* ── Hero ── */} + {/* ── Hero Split ── */}
-
-
- Tugas WebGIS · Pontianak + + {/* LEFT: heading + floating cards */} +
+
+
+ Tugas WebGIS · Pontianak +
+ +
+ Peta{" "} + interaktif +
+ Kota{" "} + + Pontianak + + + + +
+ berbasis{" "} + GIS +
+ + {/* Floating project cards */} +
+ {projects.map(p => ( + + ))} +
+ + {/* Coordinate strip */} +
+ 0°01′55″ S +
+ 109°20′58″ E +
+ Kalimantan Barat +
-

- Peta Kota
Pontianak -

+ {/* RIGHT: editorial copy + CTA */} +
+
Pemetaan Digital
-

- Empat proyek pemetaan interaktif berbasis Leaflet.js — dari SPBU hingga kepadatan penduduk. -

+
+ Jelajahi data{" "} + spasial +
+ Pontianak dengan{" "} + presisi +
-
- 0°01′55″S -
- 109°20′58″E -
- Kalimantan Barat +

+ Empat proyek WebGIS yang menggabungkan data titik, vektor, dan analisis koropleth — langsung di browser, tanpa instalasi. +

+ + + + {/* Stats */} +
+
+
4
+
Proyek Aktif
+
+
+
6
+
Kecamatan
+
+
+
5+
+
Layer Peta
+
+
- {/* ── Cards ── */} -
- {projects.map((p, i) => ( - - ))} -
- - {/* ── Divider + Footer ── */} -
-
- Leaflet.js · OpenStreetMap · MySQL -
-
- -
- Leaflet.js -
- OpenStreetMap -
- MySQL -
- React -
); } -function ProjectCard({ project: p, index, onSelect }) { - const [hovered, setHovered] = useState(false); +function FloatCard({ project: p, onSelect }) { const { Icon } = p; return (
onSelect(p.id)} - onMouseEnter={() => setHovered(true)} - onMouseLeave={() => setHovered(false)} - style={{ - borderColor: hovered ? p.accent + "44" : "rgba(0,0,0,0.08)", - }} > - {/* Icon */}
- - - +
- -
{p.title}
-
- {p.subtitle} +
{p.tag}
+
{p.title}
+
{p.subtitle}
+
+ + +
-
{p.description}
- - {/* Arrow */} - - -
); }