807 lines
28 KiB
Plaintext
807 lines
28 KiB
Plaintext
<div class="container max-w-6xl px-4 mx-auto my-12">
|
|
<div class="flex flex-col md:flex-row justify-between items-start md:items-start mb-12 gap-4">
|
|
<div class="w-full md:w-1/2">
|
|
<h2 class="text-2xl md:text-3xl font-bold text-gray-800">Informasi Lingkungan dan</br> Kebersihan Realtime</h2>
|
|
</div>
|
|
<div class="w-full md:w-1/2">
|
|
<div class="flex flex-col gap-2">
|
|
<div class="inline-flex flex-wrap md:flex-nowrap mt-2 md:mt-0 bg-amber-100 rounded-full px-3 py-1 items-center hover:bg-amber-200 transition-all duration-300 w-fit">
|
|
<i class="h-4 w-4 md:h-5 md:w-5 text-amber-600 mr-2" data-lucide="calendar"></i>
|
|
<span class="font-medium text-sm md:text-base">Senin, 10 Februari 2025</span>
|
|
</div>
|
|
<div>
|
|
<p class="text-gray-600 text-balance text-sm md:text-base">
|
|
Update terbaru seputar kapasitas, teknologi, dan program keberlanjutan TPST Bantar Gebang dalam pengelolaan sampah Jakarta serta kualitas udara di Provinsi DKI Jakarta.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="animate-on-scroll">
|
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
<!-- Jumlah Sampah Card -->
|
|
<div class="info-card relative bg-gradient-to-br from-orange-400 via-orange-500 to-orange-600 text-white p-6 rounded-xl overflow-hidden h-[140px] group transition-all duration-500 cursor-pointer transform hover:scale-[1.02] hover:shadow-lg card-3d" data-color="orange" data-type="trash">
|
|
<!-- Tooltip -->
|
|
<div class="tooltip absolute -top-12 left-1/2 -translate-x-1/2 bg-black/80 text-white px-3 py-1.5 rounded-md text-sm whitespace-nowrap opacity-0 group-hover:opacity-100 transition-all duration-300 pointer-events-none z-20 backdrop-blur-sm">
|
|
<span>Klik untuk detail informasi</span>
|
|
<div class="tooltip-arrow absolute -bottom-1 left-1/2 -translate-x-1/2 w-2 h-2 bg-black/80 rotate-45"></div>
|
|
</div>
|
|
|
|
<div class="particle-container"></div>
|
|
<div class="absolute inset-0 bg-gradient-to-r from-white/20 via-white/10 to-transparent animate-spotlight"></div>
|
|
<div class="card-glare absolute inset-0 opacity-0 transition-opacity"></div>
|
|
<div class="relative z-10 flex flex-col justify-between h-full">
|
|
<div class="flex justify-between items-start">
|
|
<div>
|
|
<h3 class="text-lg md:text-xl leading-tight card-title">Jumlah Sampah<br>Masuk TPST<br>Bantar Gebang</h3>
|
|
</div>
|
|
<div class="icon-container relative">
|
|
<i class="icon-main h-8 w-8 md:h-10 md:w-10" data-lucide="trash-2"></i>
|
|
<div class="icon-blob absolute inset-0 bg-white rounded-full scale-0 opacity-0"></div>
|
|
</div>
|
|
</div>
|
|
<div class="text-left card-content-info opacity-0 pointer-events-none">
|
|
<div class="flex items-end gap-2">
|
|
<div class="text-5xl md:text-7xl font-bold rolling-number" data-target="673">0</div>
|
|
<div class="text-lg md:text-sm font-normal">Ton</div>
|
|
</div>
|
|
<div class="mt-3 inline-flex items-center bg-orange-700 bg-opacity-50 rounded-md px-3 py-1 trend-indicator">
|
|
<i class="h-4 w-4 mr-1" data-lucide="arrow-down"></i>
|
|
<span class="text-sm">2.5%</span>
|
|
<span class="ml-1 text-sm">dari kemarin</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Jumlah Truk Card -->
|
|
<div class="info-card relative bg-gradient-to-br from-yellow-300 via-yellow-400 to-yellow-500 text-white rounded-xl p-6 h-[140px] overflow-hidden group transition-all duration-500 cursor-pointer transform hover:scale-[1.02] hover:shadow-lg card-3d" data-color="yellow" data-type="truck">
|
|
<!-- Tooltip -->
|
|
<div class="tooltip absolute -top-12 left-1/2 -translate-x-1/2 bg-black/80 text-white px-3 py-1.5 rounded-md text-sm whitespace-nowrap opacity-0 group-hover:opacity-100 transition-all duration-300 pointer-events-none z-20 backdrop-blur-sm">
|
|
<span>Klik untuk detail informasi</span>
|
|
<div class="tooltip-arrow absolute -bottom-1 left-1/2 -translate-x-1/2 w-2 h-2 bg-black/80 rotate-45"></div>
|
|
</div>
|
|
|
|
<div class="particle-container"></div>
|
|
<div class="absolute inset-0 bg-gradient-to-r from-white/20 via-white/10 to-transparent animate-spotlight-diagonal"></div>
|
|
<div class="card-glare absolute inset-0 opacity-0 transition-opacity"></div>
|
|
<div class="relative z-10 flex flex-col justify-between h-full">
|
|
<div class="flex justify-between items-start">
|
|
<div>
|
|
<h3 class="text-lg md:text-xl leading-tight card-title">Jumlah Truk<br>Masuk TPST<br>Bantar Gebang</h3>
|
|
</div>
|
|
<div class="icon-container relative">
|
|
<i class="icon-main h-8 w-8 md:h-10 md:w-10" data-lucide="truck"></i>
|
|
<div class="icon-blob absolute inset-0 bg-white rounded-full scale-0 opacity-0"></div>
|
|
</div>
|
|
</div>
|
|
<div class="text-left card-content-info opacity-0 pointer-events-none">
|
|
<div class="flex items-end gap-2">
|
|
<div class="text-5xl md:text-7xl font-bold rolling-number" data-target="673">0</div>
|
|
<div class="text-lg md:text-sm font-normal">Unit</div>
|
|
</div>
|
|
<div class="mt-3 inline-flex items-center bg-yellow-700 bg-opacity-50 rounded-md px-3 py-1 trend-indicator">
|
|
<i class="h-4 w-4 mr-1" data-lucide="arrow-down"></i>
|
|
<span class="text-sm">2.5%</span>
|
|
<span class="ml-1 text-sm">dari kemarin</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Kualitas Udara Terbaik -->
|
|
<div class="info-card relative bg-gradient-to-br from-green-500 via-green-600 to-green-700 text-white p-6 rounded-xl overflow-hidden h-[140px] group transition-all duration-500 cursor-pointer transform hover:scale-[1.02] hover:shadow-lg card-3d" data-color="green" data-type="wind">
|
|
<!-- Tooltip -->
|
|
<div class="tooltip absolute -top-12 left-1/2 -translate-x-1/2 bg-black/80 text-white px-3 py-1.5 rounded-md text-sm whitespace-nowrap opacity-0 group-hover:opacity-100 transition-all duration-300 pointer-events-none z-20 backdrop-blur-sm">
|
|
<span>Klik untuk detail informasi</span>
|
|
<div class="tooltip-arrow absolute -bottom-1 left-1/2 -translate-x-1/2 w-2 h-2 bg-black/80 rotate-45"></div>
|
|
</div>
|
|
|
|
<div class="particle-container"></div>
|
|
<div class="absolute inset-0 bg-gradient-to-r from-white/20 via-white/10 to-transparent animate-spotlight-slow"></div>
|
|
<div class="card-glare absolute inset-0 opacity-0 transition-opacity"></div>
|
|
<div class="relative z-10 flex flex-col justify-between h-full">
|
|
<div class="flex justify-between items-start">
|
|
<div>
|
|
<h3 class="text-lg md:text-xl leading-tight card-title">Kualitas Udara<br>Terbaik</h3>
|
|
</div>
|
|
<div class="icon-container relative">
|
|
<i class="icon-main h-8 w-8 md:h-10 md:w-10" data-lucide="wind"></i>
|
|
<div class="icon-blob absolute inset-0 bg-white rounded-full scale-0 opacity-0"></div>
|
|
</div>
|
|
</div>
|
|
<div class="text-left card-content-info opacity-0 pointer-events-none">
|
|
<div class="flex items-end gap-2">
|
|
<div class="text-5xl md:text-7xl font-bold rolling-number" data-target="8">0</div>
|
|
<div class="text-lg md:text-sm font-normal">ISPU</div>
|
|
</div>
|
|
<div class="mt-3 inline-flex items-center bg-green-700 bg-opacity-50 rounded-md px-3 py-1 trend-indicator">
|
|
<i class="h-4 w-4 mr-1" data-lucide="arrow-up"></i>
|
|
<span class="text-sm">2.5 μg/</span>
|
|
<span class="ml-1 text-sm">PM2</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Kualitas Udara Terburuk -->
|
|
<div class="info-card relative bg-gradient-to-br from-red-500 via-red-600 to-red-700 text-white p-6 rounded-xl overflow-hidden h-[140px] group transition-all duration-500 cursor-pointer transform hover:scale-[1.02] hover:shadow-lg card-3d" data-color="red" data-type="tornado">
|
|
<!-- Tooltip -->
|
|
<div class="tooltip absolute -top-12 left-1/2 -translate-x-1/2 bg-black/80 text-white px-3 py-1.5 rounded-md text-sm whitespace-nowrap opacity-0 group-hover:opacity-100 transition-all duration-300 pointer-events-none z-20 backdrop-blur-sm">
|
|
<span>Klik untuk detail informasi</span>
|
|
<div class="tooltip-arrow absolute -bottom-1 left-1/2 -translate-x-1/2 w-2 h-2 bg-black/80 rotate-45"></div>
|
|
</div>
|
|
|
|
<div class="particle-container"></div>
|
|
<div class="absolute inset-0 bg-gradient-to-r from-white/20 via-white/10 to-transparent animate-spotlight-reverse"></div>
|
|
<div class="card-glare absolute inset-0 opacity-0 transition-opacity"></div>
|
|
<div class="relative z-10 flex flex-col justify-between h-full">
|
|
<div class="flex justify-between items-start">
|
|
<div>
|
|
<h3 class="text-lg md:text-xl leading-tight card-title">Kualitas Udara<br>Terburuk</h3>
|
|
</div>
|
|
<div class="icon-container relative">
|
|
<i class="icon-main h-8 w-8 md:h-10 md:w-10" data-lucide="tornado"></i>
|
|
<div class="icon-blob absolute inset-0 bg-white rounded-full scale-0 opacity-0"></div>
|
|
</div>
|
|
</div>
|
|
<div class="text-left card-content-info opacity-0 pointer-events-none">
|
|
<div class="flex items-end gap-2">
|
|
<div class="text-5xl md:text-7xl font-bold rolling-number" data-target="8">0</div>
|
|
<div class="text-lg md:text-sm font-normal">ISPU</div>
|
|
</div>
|
|
<div class="mt-3 inline-flex items-center bg-red-700 bg-opacity-50 rounded-md px-3 py-1 trend-indicator">
|
|
<i class="h-4 w-4 mr-1" data-lucide="arrow-down"></i>
|
|
<span class="text-sm">2.5 μg/</span>
|
|
<span class="ml-1 text-sm">PM2</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<register-block dynamic-section="css" key="cssInformasi">
|
|
<link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
|
|
<style>
|
|
@@keyframes spotlight {
|
|
0% {
|
|
transform: translateX(-100%) skew(-20deg);
|
|
}
|
|
100% {
|
|
transform: translateX(200%) skew(-20deg);
|
|
}
|
|
}
|
|
|
|
@@keyframes spotlight-slow {
|
|
0% {
|
|
transform: translateX(-100%) skew(-15deg);
|
|
}
|
|
100% {
|
|
transform: translateX(200%) skew(-15deg);
|
|
}
|
|
}
|
|
|
|
@@keyframes spotlight-reverse {
|
|
0% {
|
|
transform: translateX(200%) skew(20deg);
|
|
}
|
|
100% {
|
|
transform: translateX(-100%) skew(20deg);
|
|
}
|
|
}
|
|
|
|
@@keyframes spotlight-diagonal {
|
|
0% {
|
|
transform: translate(-100%, -50%) skew(-25deg);
|
|
}
|
|
100% {
|
|
transform: translate(200%, 50%) skew(-25deg);
|
|
}
|
|
}
|
|
|
|
.animate-spotlight {
|
|
animation: spotlight 3s ease-in-out infinite;
|
|
}
|
|
|
|
.animate-spotlight-slow {
|
|
animation: spotlight-slow 4s ease-in-out infinite;
|
|
}
|
|
|
|
.animate-spotlight-reverse {
|
|
animation: spotlight-reverse 3.5s ease-in-out infinite;
|
|
}
|
|
|
|
.animate-spotlight-diagonal {
|
|
animation: spotlight-diagonal 4.5s ease-in-out infinite;
|
|
}
|
|
|
|
.info-card {
|
|
transition: all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
|
transform-style: preserve-3d;
|
|
backface-visibility: hidden;
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.info-card:hover .card-glare {
|
|
opacity: 0.1;
|
|
}
|
|
|
|
.info-card.expanded {
|
|
height: 240px;
|
|
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.2), 0 10px 10px -5px rgba(0, 0, 0, 0.1);
|
|
transform: translateY(-5px);
|
|
z-index: 2;
|
|
}
|
|
|
|
.card-glare {
|
|
background: linear-gradient(
|
|
135deg,
|
|
rgba(255, 255, 255, 0.35) 0%,
|
|
rgba(255, 255, 255, 0) 60%
|
|
);
|
|
transition: opacity 0.3s ease;
|
|
}
|
|
|
|
.particle {
|
|
position: absolute;
|
|
border-radius: 50%;
|
|
background-color: rgba(255, 255, 255, 0.5);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.trend-indicator {
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.info-card.expanded .trend-indicator {
|
|
transform: translateY(0);
|
|
opacity: 1;
|
|
}
|
|
|
|
.icon-container {
|
|
transition: transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
}
|
|
|
|
.info-card.expanded .icon-container {
|
|
transform: scale(1.2) translateY(-5px);
|
|
}
|
|
|
|
.icon-blob {
|
|
transition: all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
}
|
|
|
|
.info-card.expanded .icon-blob {
|
|
transform: scale(2);
|
|
opacity: 0.15;
|
|
}
|
|
|
|
.info-card:hover .icon-blob {
|
|
transform: scale(1.5);
|
|
opacity: 0.1;
|
|
}
|
|
|
|
@@media (prefers-reduced-motion: no-preference) {
|
|
.rolling-number {
|
|
position: relative;
|
|
display: inline-block;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.rolling-number::after {
|
|
content: "";
|
|
position: absolute;
|
|
left: 0;
|
|
right: 0;
|
|
height: 2px;
|
|
bottom: 5px;
|
|
background: rgba(255, 255, 255, 0.3);
|
|
transform: scaleX(0);
|
|
transition: transform 1s ease;
|
|
transform-origin: left;
|
|
}
|
|
|
|
.info-card.expanded .rolling-number::after {
|
|
transform: scaleX(1);
|
|
}
|
|
}
|
|
|
|
/* Custom Card Effects */
|
|
.info-card[data-type="trash"].expanded {
|
|
background-image: linear-gradient(to bottom right, #f97316, #ea580c, #c2410c);
|
|
}
|
|
|
|
.info-card[data-type="wind"].expanded {
|
|
background-image: linear-gradient(to bottom right, #22c55e, #16a34a, #15803d);
|
|
}
|
|
|
|
.info-card[data-type="tornado"].expanded {
|
|
background-image: linear-gradient(to bottom right, #ef4444, #dc2626, #b91c1c);
|
|
}
|
|
|
|
.info-card[data-type="truck"].expanded {
|
|
background-image: linear-gradient(to bottom right, #facc15, #eab308, #ca8a04);
|
|
}
|
|
|
|
/* Tooltip Styling */
|
|
.tooltip {
|
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.2);
|
|
transform: translateY(10px);
|
|
transition: all 0.3s ease;
|
|
z-index: 50;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.info-card:hover .tooltip {
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.info-card.expanded .tooltip {
|
|
opacity: 0 !important;
|
|
visibility: hidden;
|
|
}
|
|
|
|
.tooltip-arrow {
|
|
filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.15));
|
|
}
|
|
|
|
/* Responsive tooltip */
|
|
@@media (max-width: 768px) {
|
|
.tooltip {
|
|
width: 90%;
|
|
max-width: 200px;
|
|
left: 50%;
|
|
white-space: normal;
|
|
text-align: center;
|
|
}
|
|
}
|
|
|
|
/* Pulse animation for tooltip visibility */
|
|
@@keyframes pulseScale {
|
|
0%, 100% { transform: scale(1); }
|
|
50% { transform: scale(1.05); }
|
|
}
|
|
|
|
.info-card:not(.expanded):hover .icon-container {
|
|
animation: pulseScale 1.5s infinite;
|
|
}
|
|
</style>
|
|
</register-block>
|
|
|
|
<register-block dynamic-section="scripts" key="jsInformasi">
|
|
<script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js"></script>
|
|
|
|
<script>
|
|
AOS.init();
|
|
gsap.registerPlugin(ScrollTrigger);
|
|
</script>
|
|
|
|
<script>
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
// Rolling numbers animation
|
|
const rollingNumbers = document.querySelectorAll('.rolling-number');
|
|
const infoCards = document.querySelectorAll('.info-card');
|
|
|
|
// Add 3D card effect
|
|
infoCards.forEach(card => {
|
|
card.addEventListener('mousemove', function(e) {
|
|
if (!this.classList.contains('expanded')) {
|
|
const rect = this.getBoundingClientRect();
|
|
const x = e.clientX - rect.left;
|
|
const y = e.clientY - rect.top;
|
|
|
|
const centerX = rect.width / 2;
|
|
const centerY = rect.height / 2;
|
|
|
|
const rotateY = (x - centerX) / 20;
|
|
const rotateX = (centerY - y) / 20;
|
|
|
|
gsap.to(this, {
|
|
rotateY: rotateY,
|
|
rotateX: rotateX,
|
|
duration: 0.5,
|
|
ease: "power1.out",
|
|
});
|
|
|
|
// Move glare with cursor
|
|
const glare = this.querySelector('.card-glare');
|
|
gsap.to(glare, {
|
|
opacity: 0.15,
|
|
top: `${y}px`,
|
|
left: `${x}px`,
|
|
duration: 0.3
|
|
});
|
|
}
|
|
});
|
|
|
|
card.addEventListener('mouseleave', function() {
|
|
if (!this.classList.contains('expanded')) {
|
|
gsap.to(this, {
|
|
rotateY: 0,
|
|
rotateX: 0,
|
|
duration: 0.5,
|
|
ease: "elastic.out(1, 0.3)"
|
|
});
|
|
|
|
const glare = this.querySelector('.card-glare');
|
|
gsap.to(glare, {
|
|
opacity: 0,
|
|
duration: 0.3
|
|
});
|
|
}
|
|
});
|
|
|
|
// Create particles for each card
|
|
const particleContainer = card.querySelector('.particle-container');
|
|
const cardType = card.getAttribute('data-type');
|
|
createParticles(particleContainer, cardType);
|
|
});
|
|
|
|
function createParticles(container, type) {
|
|
const particleCount = 12;
|
|
for (let i = 0; i < particleCount; i++) {
|
|
const particle = document.createElement('div');
|
|
particle.classList.add('particle');
|
|
|
|
// Set size (varied)
|
|
const size = Math.random() * 4 + 2;
|
|
particle.style.width = `${size}px`;
|
|
particle.style.height = `${size}px`;
|
|
|
|
// Set initial position
|
|
const posX = Math.random() * 100;
|
|
const posY = Math.random() * 100;
|
|
particle.style.left = `${posX}%`;
|
|
particle.style.top = `${posY}%`;
|
|
|
|
// Set opacity
|
|
particle.style.opacity = Math.random() * 0.5 + 0.1;
|
|
|
|
// Add to container
|
|
container.appendChild(particle);
|
|
}
|
|
}
|
|
|
|
function animateParticles(card) {
|
|
const particles = card.querySelectorAll('.particle');
|
|
const cardType = card.getAttribute('data-type');
|
|
let tl = gsap.timeline();
|
|
|
|
particles.forEach(particle => {
|
|
const duration = Math.random() * 3 + 2;
|
|
const delay = Math.random() * 2;
|
|
|
|
let animation;
|
|
|
|
switch(cardType) {
|
|
case 'trash':
|
|
// Upward floating particles
|
|
animation = {
|
|
y: `-=${Math.random() * 30 + 20}`,
|
|
x: `+=${(Math.random() - 0.5) * 20}`,
|
|
opacity: 0,
|
|
duration: duration,
|
|
delay: delay,
|
|
ease: "power1.out"
|
|
};
|
|
break;
|
|
case 'wind':
|
|
// Sideways flowing particles
|
|
animation = {
|
|
x: `+=${Math.random() * 50 + 30}`,
|
|
y: `+=${(Math.random() - 0.5) * 20}`,
|
|
opacity: 0,
|
|
duration: duration,
|
|
delay: delay,
|
|
ease: "power1.inOut"
|
|
};
|
|
break;
|
|
case 'tornado':
|
|
// Swirling particles
|
|
const angle = Math.random() * 360;
|
|
const radius = Math.random() * 30 + 10;
|
|
animation = {
|
|
motionPath: {
|
|
path: `M0,0 C${radius/2},${-radius} ${radius},${-radius/2} ${radius},0 C${radius},${radius/2} ${radius/2},${radius} 0,0`,
|
|
curviness: 1.5
|
|
},
|
|
opacity: 0,
|
|
duration: duration,
|
|
delay: delay,
|
|
ease: "power1.inOut"
|
|
};
|
|
break;
|
|
case 'truck':
|
|
// Horizontal line particles
|
|
animation = {
|
|
x: `+=${Math.random() * 40 + 20}`,
|
|
opacity: 0,
|
|
duration: duration,
|
|
delay: delay,
|
|
ease: "power1.inOut"
|
|
};
|
|
break;
|
|
default:
|
|
animation = {
|
|
y: `-=${Math.random() * 30 + 20}`,
|
|
opacity: 0,
|
|
duration: duration,
|
|
delay: delay,
|
|
ease: "power1.out"
|
|
};
|
|
}
|
|
|
|
tl.fromTo(particle,
|
|
{ opacity: 0, x: 0, y: 0, scale: 0 },
|
|
{ ...animation, scale: 1, repeat: -1 },
|
|
0
|
|
);
|
|
});
|
|
|
|
return tl;
|
|
}
|
|
|
|
// Scroll-triggered card expansion
|
|
infoCards.forEach((card, index) => {
|
|
ScrollTrigger.create({
|
|
trigger: card,
|
|
start: "top 85%",
|
|
end: "bottom 15%",
|
|
onEnter: () => {
|
|
// Slower delay for smoother expansion - increased from 0.2 to 0.5
|
|
gsap.delayedCall(index * 0.5, () => {
|
|
expandCard(card);
|
|
});
|
|
},
|
|
onLeave: () => {
|
|
// Collapse when scrolling down past the card
|
|
gsap.delayedCall(index * 0.1, () => {
|
|
collapseCard(card);
|
|
});
|
|
},
|
|
onEnterBack: () => {
|
|
// Re-expand when scrolling back up into view with slower timing
|
|
gsap.delayedCall(index * 0.3, () => {
|
|
expandCard(card);
|
|
});
|
|
},
|
|
onLeaveBack: () => {
|
|
// Collapse when scrolling up past the card
|
|
gsap.delayedCall(index * 0.1, () => {
|
|
collapseCard(card);
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
function expandCard(card) {
|
|
if (card.classList.contains('expanded')) return;
|
|
|
|
const content = card.querySelector('.card-content-info');
|
|
const iconContainer = card.querySelector('.icon-container');
|
|
const iconBlob = card.querySelector('.icon-blob');
|
|
|
|
// Mark as expanded
|
|
card.classList.add('expanded');
|
|
|
|
// Initialize particle animation property if not exists
|
|
if (!card._particleAnimation) {
|
|
card._particleAnimation = null;
|
|
}
|
|
|
|
// Slower, more smooth expansion animation
|
|
gsap.to(card, {
|
|
height: 240,
|
|
rotateX: 0,
|
|
rotateY: 0,
|
|
duration: 0.8, // Increased from 0.5 to 0.8
|
|
ease: "power2.out" // Changed to smoother easing
|
|
});
|
|
|
|
// Animate icon with bounce - slower timing
|
|
gsap.to(iconContainer, {
|
|
scale: 1.2,
|
|
y: -5,
|
|
duration: 0.8, // Increased from 0.5 to 0.8
|
|
ease: "back.out(1.2)" // Less aggressive bounce
|
|
});
|
|
|
|
// Animate icon blob - slower
|
|
gsap.to(iconBlob, {
|
|
scale: 2,
|
|
opacity: 0.15,
|
|
duration: 0.7, // Increased from 0.5 to 0.7
|
|
ease: "power2.out"
|
|
});
|
|
|
|
// Fade in content with longer delay and stagger
|
|
const contentElements = content.children;
|
|
gsap.to(content, {
|
|
opacity: 1,
|
|
duration: 0.2, // Increased from 0.1 to 0.2
|
|
delay: 0.4 // Increased from 0.2 to 0.4
|
|
});
|
|
|
|
gsap.fromTo(contentElements,
|
|
{ y: 20, opacity: 0 }, // Increased y from 15 to 20
|
|
{
|
|
y: 0,
|
|
opacity: 1,
|
|
duration: 0.6, // Increased from 0.4 to 0.6
|
|
stagger: 0.15, // Increased from 0.1 to 0.15
|
|
delay: 0.4, // Increased from 0.2 to 0.4
|
|
ease: "power2.out" // Smoother easing
|
|
}
|
|
);
|
|
|
|
// Start particle animation for this card only
|
|
card._particleAnimation = animateParticles(card);
|
|
|
|
// Activate rolling numbers if they're in this card with delay
|
|
setTimeout(() => {
|
|
const cardNumbers = card.querySelectorAll('.rolling-number');
|
|
cardNumbers.forEach(number => {
|
|
const target = parseInt(number.getAttribute("data-target"));
|
|
let current = 0;
|
|
const increment = target / 90; // Slower rolling - increased from 60 to 90
|
|
|
|
const updateNumber = () => {
|
|
current += increment;
|
|
if (current < target) {
|
|
number.textContent = Math.floor(current);
|
|
requestAnimationFrame(updateNumber);
|
|
} else {
|
|
number.textContent = target;
|
|
}
|
|
};
|
|
|
|
updateNumber();
|
|
});
|
|
}, 500); // Delay rolling numbers by 500ms
|
|
}
|
|
|
|
function collapseCard(card) {
|
|
if (!card.classList.contains('expanded')) return;
|
|
|
|
const content = card.querySelector('.card-content-info');
|
|
const iconContainer = card.querySelector('.icon-container');
|
|
const iconBlob = card.querySelector('.icon-blob');
|
|
|
|
// Smooth collapse animation
|
|
gsap.to(card, {
|
|
height: 140,
|
|
rotateX: 0,
|
|
rotateY: 0,
|
|
clearProps: "transform",
|
|
duration: 0.6, // Increased from 0.5 to 0.6
|
|
ease: "power2.inOut", // Smoother easing
|
|
onComplete: () => {
|
|
card.classList.remove('expanded');
|
|
}
|
|
});
|
|
|
|
// Reset icon animation - slower
|
|
gsap.to(iconContainer, {
|
|
scale: 1,
|
|
y: 0,
|
|
duration: 0.6, // Increased from 0.5 to 0.6
|
|
ease: "power2.inOut"
|
|
});
|
|
|
|
// Reset icon blob - slower
|
|
gsap.to(iconBlob, {
|
|
scale: 0,
|
|
opacity: 0,
|
|
duration: 0.4, // Increased from 0.3 to 0.4
|
|
ease: "power2.inOut"
|
|
});
|
|
|
|
// Fade out content - faster but smoother
|
|
gsap.to(content, {
|
|
opacity: 0,
|
|
duration: 0.3,
|
|
ease: "power2.inOut"
|
|
});
|
|
|
|
// Stop particle animation if it exists
|
|
if (card._particleAnimation) {
|
|
card._particleAnimation.kill();
|
|
card._particleAnimation = null;
|
|
}
|
|
|
|
// Reset rolling numbers
|
|
const cardNumbers = card.querySelectorAll('.rolling-number');
|
|
cardNumbers.forEach(number => {
|
|
number.textContent = '0';
|
|
});
|
|
}
|
|
|
|
// Observer for rolling numbers (keep existing)
|
|
const observer = new IntersectionObserver((entries) => {
|
|
entries.forEach((entry) => {
|
|
if (entry.isIntersecting) {
|
|
const element = entry.target;
|
|
const target = parseInt(element.getAttribute("data-target"));
|
|
let current = 0;
|
|
const increment = target / 120;
|
|
|
|
const updateNumber = () => {
|
|
current += increment;
|
|
if (current < target) {
|
|
element.textContent = Math.floor(current);
|
|
requestAnimationFrame(updateNumber);
|
|
} else {
|
|
element.textContent = target;
|
|
}
|
|
};
|
|
|
|
updateNumber();
|
|
observer.unobserve(element);
|
|
}
|
|
});
|
|
});
|
|
|
|
rollingNumbers.forEach((number) => {
|
|
observer.observe(number);
|
|
});
|
|
|
|
// Modified card click handlers - now only for collapsing
|
|
infoCards.forEach(card => {
|
|
card.addEventListener('click', function() {
|
|
// Only allow collapsing if already expanded
|
|
if (this.classList.contains('expanded')) {
|
|
collapseCard(this);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Add tooltip functionality (keep existing)
|
|
infoCards.forEach(card => {
|
|
// Set aria attributes for accessibility
|
|
card.setAttribute('aria-label', 'Kartu informasi akan terbuka otomatis saat scroll');
|
|
card.setAttribute('role', 'region');
|
|
card.setAttribute('tabindex', '0');
|
|
|
|
// Support keyboard interaction for collapsing
|
|
card.addEventListener('keydown', function(e) {
|
|
if ((e.key === 'Enter' || e.key === ' ') && this.classList.contains('expanded')) {
|
|
e.preventDefault();
|
|
collapseCard(this);
|
|
}
|
|
});
|
|
|
|
// Update tooltip text
|
|
const tooltip = card.querySelector('.tooltip span');
|
|
if (tooltip) {
|
|
tooltip.textContent = 'Klik untuk tutup detail';
|
|
}
|
|
|
|
// Hide tooltip when card is expanded
|
|
card.addEventListener('click', function() {
|
|
const tooltip = this.querySelector('.tooltip');
|
|
if (tooltip) {
|
|
if (this.classList.contains('expanded')) {
|
|
tooltip.style.opacity = '0';
|
|
tooltip.style.visibility = 'hidden';
|
|
} else {
|
|
setTimeout(() => {
|
|
if (!this.classList.contains('expanded')) {
|
|
tooltip.style.opacity = '';
|
|
tooltip.style.visibility = '';
|
|
}
|
|
}, 500);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
</register-block> |