feat: retheme to professional Deep Eco-Green and add cinematic scrolling waste accumulation story
Browse files- frontend/app.js +81 -0
- frontend/index.html +55 -0
- frontend/style.css +37 -15
frontend/app.js
CHANGED
|
@@ -1054,4 +1054,85 @@ window.sortWaste = sortWaste;
|
|
| 1054 |
// Initialize the game automatically
|
| 1055 |
document.addEventListener("DOMContentLoaded", () => {
|
| 1056 |
loadNextWasteItem();
|
|
|
|
| 1057 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1054 |
// Initialize the game automatically
|
| 1055 |
document.addEventListener("DOMContentLoaded", () => {
|
| 1056 |
loadNextWasteItem();
|
| 1057 |
+
initCrisisStoryScroller();
|
| 1058 |
});
|
| 1059 |
+
|
| 1060 |
+
// ==========================================
|
| 1061 |
+
// CINEMATIC CRISIS STORYTELLING SCROLLER
|
| 1062 |
+
// ==========================================
|
| 1063 |
+
function initCrisisStoryScroller() {
|
| 1064 |
+
const storyCards = document.querySelectorAll(".story-card");
|
| 1065 |
+
const wasteFill = document.getElementById("waste-pile-fill");
|
| 1066 |
+
const tonsVal = document.getElementById("simulated-tons-val");
|
| 1067 |
+
|
| 1068 |
+
if (!storyCards.length || !wasteFill || !tonsVal) return;
|
| 1069 |
+
|
| 1070 |
+
// Use IntersectionObserver to detect which card is currently active/visible in the center of screen
|
| 1071 |
+
const observerOptions = {
|
| 1072 |
+
root: null,
|
| 1073 |
+
rootMargin: "-25% 0px -40% 0px", // Focus on the middle of the screen
|
| 1074 |
+
threshold: 0.1
|
| 1075 |
+
};
|
| 1076 |
+
|
| 1077 |
+
const observer = new IntersectionObserver((entries) => {
|
| 1078 |
+
entries.forEach(entry => {
|
| 1079 |
+
if (entry.isIntersecting) {
|
| 1080 |
+
// Highlight active card
|
| 1081 |
+
storyCards.forEach(c => {
|
| 1082 |
+
c.style.borderColor = "var(--border-color)";
|
| 1083 |
+
c.style.background = "rgba(8,18,14,0.6)";
|
| 1084 |
+
c.style.boxShadow = "none";
|
| 1085 |
+
});
|
| 1086 |
+
entry.target.style.borderColor = "var(--cyan)";
|
| 1087 |
+
entry.target.style.background = "rgba(16, 185, 129, 0.08)";
|
| 1088 |
+
entry.target.style.boxShadow = "0 0 15px rgba(16, 185, 129, 0.1)";
|
| 1089 |
+
|
| 1090 |
+
// Get parameters
|
| 1091 |
+
const targetHeight = entry.target.getAttribute("data-height");
|
| 1092 |
+
const targetTons = entry.target.getAttribute("data-tons");
|
| 1093 |
+
|
| 1094 |
+
// Set fill height and text counter
|
| 1095 |
+
wasteFill.style.height = `${targetHeight}%`;
|
| 1096 |
+
|
| 1097 |
+
// If it is the last critical card, change color to red
|
| 1098 |
+
if (targetHeight === "95") {
|
| 1099 |
+
wasteFill.style.background = "linear-gradient(180deg, var(--red) 0%, #7f1d1d 100%)";
|
| 1100 |
+
wasteFill.style.boxShadow = "0 0 20px rgba(239, 68, 68, 0.4)";
|
| 1101 |
+
} else {
|
| 1102 |
+
wasteFill.style.background = "linear-gradient(180deg, var(--yellow) 0%, #78350F 100%)";
|
| 1103 |
+
wasteFill.style.boxShadow = "0 0 20px rgba(245, 158, 11, 0.3)";
|
| 1104 |
+
}
|
| 1105 |
+
|
| 1106 |
+
// Animate tons text value counter
|
| 1107 |
+
animateTonsCounter(parseInt(tonsVal.textContent.replace(/,/g, "")), parseInt(targetTons));
|
| 1108 |
+
}
|
| 1109 |
+
});
|
| 1110 |
+
}, observerOptions);
|
| 1111 |
+
|
| 1112 |
+
storyCards.forEach(card => observer.observe(card));
|
| 1113 |
+
}
|
| 1114 |
+
|
| 1115 |
+
function animateTonsCounter(start, end) {
|
| 1116 |
+
const tonsVal = document.getElementById("simulated-tons-val");
|
| 1117 |
+
if (!tonsVal) return;
|
| 1118 |
+
|
| 1119 |
+
const duration = 800; // ms
|
| 1120 |
+
const startTime = performance.now();
|
| 1121 |
+
|
| 1122 |
+
function update(now) {
|
| 1123 |
+
const elapsed = now - startTime;
|
| 1124 |
+
const progress = Math.min(elapsed / duration, 1);
|
| 1125 |
+
|
| 1126 |
+
// Ease out quadratic
|
| 1127 |
+
const easeProgress = progress * (2 - progress);
|
| 1128 |
+
const current = Math.round(start + (end - start) * easeProgress);
|
| 1129 |
+
|
| 1130 |
+
tonsVal.textContent = `${current.toLocaleString('en-US')} Tons`;
|
| 1131 |
+
|
| 1132 |
+
if (progress < 1) {
|
| 1133 |
+
requestAnimationFrame(update);
|
| 1134 |
+
}
|
| 1135 |
+
}
|
| 1136 |
+
|
| 1137 |
+
requestAnimationFrame(update);
|
| 1138 |
+
}
|
frontend/index.html
CHANGED
|
@@ -196,6 +196,61 @@
|
|
| 196 |
</div>
|
| 197 |
</section>
|
| 198 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
<!-- Sumber Data & Akuntabilitas -->
|
| 200 |
<section class="container data-sources-section">
|
| 201 |
<h2 class="section-title">TRANSPARANSI SUMBER DATA</h2>
|
|
|
|
| 196 |
</div>
|
| 197 |
</section>
|
| 198 |
|
| 199 |
+
<!-- Cinematic Waste Crisis Scroll Section -->
|
| 200 |
+
<section class="container crisis-story-section" style="margin-top: 4rem; display: grid; grid-template-columns: 1.2fr 0.8fr; gap: 3rem; position: relative;">
|
| 201 |
+
|
| 202 |
+
<!-- Sticky Left Panel (Waste Tower Visualizer) -->
|
| 203 |
+
<div class="sticky-visualizer-panel" style="position: sticky; top: 120px; height: 450px; display: flex; flex-direction: column; justify-content: center; align-items: center; background: rgba(5, 12, 10, 0.4); border: 1px solid var(--border-color); border-radius: 16px; padding: 2rem; overflow: hidden; box-shadow: 0 0 30px rgba(16, 185, 129, 0.05);">
|
| 204 |
+
<h3 style="font-family: var(--font-display); font-size: 1.1rem; color: #FFF; margin-bottom: 1.5rem; letter-spacing: 1px; text-transform: uppercase;">JAKARTA DAILY ACCUMULATION TOWER</h3>
|
| 205 |
+
|
| 206 |
+
<!-- The Wireframe Tower -->
|
| 207 |
+
<div class="waste-tower-outline" style="position: relative; width: 140px; height: 260px; border: 2px dashed rgba(16, 185, 129, 0.3); border-radius: 12px 12px 0 0; background: rgba(255,255,255,0.01); overflow: hidden; display: flex; align-items: flex-end;">
|
| 208 |
+
<!-- Raising Waste Fill -->
|
| 209 |
+
<div id="waste-pile-fill" style="width: 100%; height: 10%; background: linear-gradient(180deg, var(--yellow) 0%, #78350F 100%); opacity: 0.85; transition: height 0.6s cubic-bezier(0.25, 0.8, 0.25, 1); position: relative; box-shadow: 0 0 20px rgba(245, 158, 11, 0.3);">
|
| 210 |
+
<!-- Bubbles/Particles inside fill -->
|
| 211 |
+
<div class="waste-steam" style="position: absolute; top: -10px; left: 0; width: 100%; height: 12px; background: rgba(245, 158, 11, 0.6); filter: blur(4px); animation: steam-pulse 1.5s infinite alternate; pointer-events: none;"></div>
|
| 212 |
+
</div>
|
| 213 |
+
|
| 214 |
+
<!-- Height Markers -->
|
| 215 |
+
<div style="position: absolute; right: 10px; top: 10px; font-family: var(--font-mono); font-size: 10px; color: rgba(255,255,255,0.3); pointer-events: none;">CRITICAL</div>
|
| 216 |
+
<div style="position: absolute; right: 10px; top: 120px; font-family: var(--font-mono); font-size: 10px; color: rgba(255,255,255,0.3); pointer-events: none;">WARNING</div>
|
| 217 |
+
<div style="position: absolute; right: 10px; top: 230px; font-family: var(--font-mono); font-size: 10px; color: rgba(255,255,255,0.3); pointer-events: none;">NORMAL</div>
|
| 218 |
+
</div>
|
| 219 |
+
|
| 220 |
+
<!-- Live Ticking Indicator -->
|
| 221 |
+
<div class="tower-status" style="margin-top: 1.5rem; text-align: center;">
|
| 222 |
+
<div style="font-family: var(--font-mono); font-size: 0.8rem; color: var(--text-muted); text-transform: uppercase;">Simulated Tonnage</div>
|
| 223 |
+
<div style="font-family: var(--font-mono); font-size: 1.8rem; color: var(--yellow); font-weight: bold; text-shadow: 0 0 10px rgba(245,158,11,0.2);" id="simulated-tons-val">905 Tons</div>
|
| 224 |
+
</div>
|
| 225 |
+
</div>
|
| 226 |
+
|
| 227 |
+
<!-- Scrolling Narrative Cards -->
|
| 228 |
+
<div class="story-scroll-cards" style="display: flex; flex-direction: column; gap: 6rem; padding-bottom: 8rem;">
|
| 229 |
+
|
| 230 |
+
<!-- Card 1 -->
|
| 231 |
+
<div class="story-card panel" data-height="25" data-tons="2264" style="padding: 2.2rem; min-height: 250px; display: flex; flex-direction: column; justify-content: center; transition: all 0.3s; background: rgba(8,18,14,0.6); border:1px solid var(--border-color); border-radius:12px;">
|
| 232 |
+
<span style="font-family: var(--font-mono); font-size: 0.75rem; color: var(--green); letter-spacing: 2px; text-transform: uppercase; margin-bottom: 0.5rem; display: block;">STAGE 01: MORNING LOADS (09:00 AM)</span>
|
| 233 |
+
<h3 style="font-family: var(--font-display); font-size: 1.3rem; color: #FFF; margin-bottom: 0.8rem;">2.264 TON SAMPAH TERKUMPUL</h3>
|
| 234 |
+
<p style="font-size: 0.9rem; color: var(--text-muted); line-height: 1.6;">Memasuki jam kerja awal, tumpukan sampah dari pasar tradisional dan wilayah residensial se-DKI Jakarta mulai berdatangan di TPS. Kecepatan pengangkutan sangat krusial agar TPS lokal tidak mengalami kelumpuhan penumpukan.</p>
|
| 235 |
+
</div>
|
| 236 |
+
|
| 237 |
+
<!-- Card 2 -->
|
| 238 |
+
<div class="story-card panel" data-height="55" data-tons="4982" style="padding: 2.2rem; min-height: 250px; display: flex; flex-direction: column; justify-content: center; transition: all 0.3s; background: rgba(8,18,14,0.6); border:1px solid var(--border-color); border-radius:12px;">
|
| 239 |
+
<span style="font-family: var(--font-mono); font-size: 0.75rem; color: var(--green); letter-spacing: 2px; text-transform: uppercase; margin-bottom: 0.5rem; display: block;">STAGE 02: MID-DAY PEAK (03:00 PM)</span>
|
| 240 |
+
<h3 style="font-family: var(--font-display); font-size: 1.3rem; color: #FFF; margin-bottom: 0.8rem;">4.982 TON SAMPAH TERKUMPUL</h3>
|
| 241 |
+
<p style="font-size: 0.9rem; color: var(--text-muted); line-height: 1.6;">Di siang hari, aktivitas logistik pengiriman sampah ke TPST Bantargebang memuncak. Kepadatan lalu lintas jalan tol Jakarta-Bekasi dan antrean panjang truk di gerbang timbang Bantargebang mulai memicu bottleneck operasional pengangkutan.</p>
|
| 242 |
+
</div>
|
| 243 |
+
|
| 244 |
+
<!-- Card 3 -->
|
| 245 |
+
<div class="story-card panel" data-height="95" data-tons="9059" style="padding: 2.2rem; min-height: 250px; display: flex; flex-direction: column; justify-content: center; transition: all 0.3s; background: rgba(8,18,14,0.6); border:1px solid var(--border-color); border-radius:12px;">
|
| 246 |
+
<span style="font-family: var(--font-mono); font-size: 0.75rem; color: var(--red); letter-spacing: 2px; text-transform: uppercase; margin-bottom: 0.5rem; display: block;">STAGE 03: EOD OVERLOAD (11:59 PM)</span>
|
| 247 |
+
<h3 style="font-family: var(--font-display); font-size: 1.3rem; color: #FFF; margin-bottom: 0.8rem;">9.059 TON SAMPAH HARIAN</h3>
|
| 248 |
+
<p style="font-size: 0.9rem; color: var(--text-muted); line-height: 1.6;">Sebelum hari berakhir, total timbulan sampah DKI mencapai kapasitas maksimalnya. TPST Bantargebang yang kini melebihi daya tampung aman (ketinggian gunungan sampah di atas 50 meter) berada pada status siaga kritis. Di sinilah Aeterna AI memitigasi krisis tersebut dengan analisis logistik prediktif.</p>
|
| 249 |
+
</div>
|
| 250 |
+
|
| 251 |
+
</div>
|
| 252 |
+
</section>
|
| 253 |
+
|
| 254 |
<!-- Sumber Data & Akuntabilitas -->
|
| 255 |
<section class="container data-sources-section">
|
| 256 |
<h2 class="section-title">TRANSPARANSI SUMBER DATA</h2>
|
frontend/style.css
CHANGED
|
@@ -1,22 +1,22 @@
|
|
| 1 |
/* CSS Variables for Theming */
|
| 2 |
:root {
|
| 3 |
-
--bg-void: #
|
| 4 |
-
--bg-panel: rgba(
|
| 5 |
-
--border-color: rgba(
|
| 6 |
-
--border-hover: rgba(
|
| 7 |
|
| 8 |
-
--text-main: #
|
| 9 |
-
--text-muted: #
|
| 10 |
|
| 11 |
-
/*
|
| 12 |
-
--cyan: #
|
| 13 |
-
--cyan-glow: rgba(
|
| 14 |
-
--green: #
|
| 15 |
-
--green-glow: rgba(
|
| 16 |
-
--yellow: #
|
| 17 |
-
--yellow-glow: rgba(
|
| 18 |
-
--red: #
|
| 19 |
-
--red-glow: rgba(
|
| 20 |
|
| 21 |
/* Fonts */
|
| 22 |
--font-display: 'Outfit', 'Space Grotesk', system-ui, sans-serif;
|
|
@@ -1573,5 +1573,27 @@ body::after {
|
|
| 1573 |
}
|
| 1574 |
}
|
| 1575 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1576 |
|
| 1577 |
|
|
|
|
| 1 |
/* CSS Variables for Theming */
|
| 2 |
:root {
|
| 3 |
+
--bg-void: #030806;
|
| 4 |
+
--bg-panel: rgba(8, 18, 14, 0.85);
|
| 5 |
+
--border-color: rgba(16, 185, 129, 0.15);
|
| 6 |
+
--border-hover: rgba(16, 185, 129, 0.4);
|
| 7 |
|
| 8 |
+
--text-main: #E2E8F0;
|
| 9 |
+
--text-muted: #94A3B8;
|
| 10 |
|
| 11 |
+
/* Professional Eco-Urban Colors */
|
| 12 |
+
--cyan: #10B981; /* Eco Emerald Green (Primary) */
|
| 13 |
+
--cyan-glow: rgba(16, 185, 129, 0.4);
|
| 14 |
+
--green: #34D399; /* Bright Mint Green */
|
| 15 |
+
--green-glow: rgba(52, 211, 153, 0.3);
|
| 16 |
+
--yellow: #F59E0B; /* Amber Orange (Warning) */
|
| 17 |
+
--yellow-glow: rgba(245, 158, 11, 0.3);
|
| 18 |
+
--red: #EF4444; /* Alert Coral Red (Critical) */
|
| 19 |
+
--red-glow: rgba(239, 68, 68, 0.4);
|
| 20 |
|
| 21 |
/* Fonts */
|
| 22 |
--font-display: 'Outfit', 'Space Grotesk', system-ui, sans-serif;
|
|
|
|
| 1573 |
}
|
| 1574 |
}
|
| 1575 |
|
| 1576 |
+
@keyframes steam-pulse {
|
| 1577 |
+
0% { opacity: 0.3; transform: scaleY(0.9); }
|
| 1578 |
+
100% { opacity: 0.75; transform: scaleY(1.1); }
|
| 1579 |
+
}
|
| 1580 |
+
|
| 1581 |
+
@media (max-width: 992px) {
|
| 1582 |
+
.crisis-story-section {
|
| 1583 |
+
grid-template-columns: 1fr !important;
|
| 1584 |
+
gap: 2rem !important;
|
| 1585 |
+
}
|
| 1586 |
+
.sticky-visualizer-panel {
|
| 1587 |
+
position: relative !important;
|
| 1588 |
+
top: 0 !important;
|
| 1589 |
+
height: auto !important;
|
| 1590 |
+
padding: 1.5rem !important;
|
| 1591 |
+
}
|
| 1592 |
+
.story-scroll-cards {
|
| 1593 |
+
gap: 3rem !important;
|
| 1594 |
+
padding-bottom: 2rem !important;
|
| 1595 |
+
}
|
| 1596 |
+
}
|
| 1597 |
+
|
| 1598 |
|
| 1599 |
|