210 lines
7.7 KiB
JavaScript
210 lines
7.7 KiB
JavaScript
/* ==========================================
|
|
2048 VISUAL EFFECTS - ANIMATION SYSTEM
|
|
==========================================
|
|
fungsi utama:
|
|
1. triggerComboEffect() - Efek saat tile merge
|
|
2. showComboPopup() - Popup combo text (x2, x3, x4+)
|
|
3. createParticleBurst() - Ledakan partikel dari tile
|
|
4. createScorePopup() - Score yang terbang ke atas
|
|
5. getTileColor() - Warna sesuai nilai tile
|
|
========================================== */
|
|
|
|
/* ==========================================
|
|
TRIGGER COMBO EFFECT - Main Visual Handler
|
|
==========================================
|
|
Dipanggil dari move functions di 2048_Logic.js
|
|
========================================== */
|
|
function triggerComboEffect(mergedCells, comboCount) {
|
|
// Guard: kalau nggak ada tile yang merge, skip
|
|
if (mergedCells.length === 0) return;
|
|
|
|
// Loop setiap tile yang di-merge
|
|
mergedCells.forEach(cell => {
|
|
const tile = document.getElementById(`${cell.r}-${cell.c}`);
|
|
if (!tile) return;
|
|
|
|
// Efek 1: Animasi "merge" (scale + glow)
|
|
tile.classList.add('merge');
|
|
setTimeout(() => tile.classList.remove('merge'), 300);
|
|
|
|
// Efek 2: Ledakan partikel
|
|
createParticleBurst(tile);
|
|
|
|
// Efek 3: Box shadow glow
|
|
tile.style.boxShadow = '0 0 40px currentColor';
|
|
setTimeout(() => {
|
|
tile.style.boxShadow = ''; // Reset setelah 300ms
|
|
}, 300);
|
|
|
|
// Efek 4: Score popup yang terbang ke atas
|
|
const rect = tile.getBoundingClientRect();
|
|
const centerX = rect.left + rect.width / 2;
|
|
const centerY = rect.top + rect.height / 2;
|
|
const tileValue = parseInt(tile.textContent);
|
|
createScorePopup(centerX, centerY, tileValue);
|
|
});
|
|
|
|
// Efek 5: Combo popup kalau merge ≥2 tile sekaligus
|
|
if (comboCount >= 2) {
|
|
showComboPopup(comboCount);
|
|
}
|
|
}
|
|
|
|
/* ==========================================
|
|
COMBO POPUP - Text "COMBO x2!", "AMAZING x3!", dst
|
|
========================================== */
|
|
function showComboPopup(comboCount) {
|
|
const board = document.getElementById('board');
|
|
if (!board) return;
|
|
|
|
// Hitung posisi tengah board
|
|
const rect = board.getBoundingClientRect();
|
|
const centerX = rect.left + rect.width / 2;
|
|
const centerY = rect.top + rect.height / 2;
|
|
|
|
// Buat element popup
|
|
const popup = document.createElement('div');
|
|
popup.className = 'combo-popup';
|
|
popup.style.left = centerX + 'px';
|
|
popup.style.top = centerY + 'px';
|
|
popup.style.position = 'fixed';
|
|
popup.style.fontWeight = '900';
|
|
popup.style.pointerEvents = 'none'; // Nggak bisa diklik
|
|
popup.style.zIndex = '9999'; // Di depan semua
|
|
popup.style.transform = 'translate(-50%, -50%)'; // Center alignment
|
|
popup.style.textTransform = 'uppercase';
|
|
popup.style.letterSpacing = '3px';
|
|
|
|
// Styling berbeda sesuai combo level
|
|
if (comboCount === 2) {
|
|
// COMBO x2 - Hijau neon
|
|
popup.textContent = 'COMBO x2!';
|
|
popup.style.fontSize = '36px';
|
|
popup.style.color = '#00ff99';
|
|
popup.style.textShadow = '0 0 30px rgba(0, 255, 153, 1), 0 0 50px rgba(0, 255, 153, 0.5)';
|
|
|
|
} else if (comboCount === 3) {
|
|
// AMAZING x3 - Pink magenta
|
|
popup.textContent = 'AMAZING x3!';
|
|
popup.style.fontSize = '42px';
|
|
popup.style.color = '#ff00ff';
|
|
popup.style.textShadow = '0 0 35px rgba(255, 0, 255, 1), 0 0 60px rgba(255, 0, 255, 0.6)';
|
|
|
|
} else if (comboCount >= 4) {
|
|
// PERFECT x4+ - Gold
|
|
popup.textContent = 'PERFECT x' + comboCount + '!';
|
|
popup.style.fontSize = '48px';
|
|
popup.style.color = '#ffd700';
|
|
popup.style.textShadow = '0 0 40px rgba(255, 215, 0, 1), 0 0 70px rgba(255, 215, 0, 0.7)';
|
|
}
|
|
|
|
document.body.appendChild(popup);
|
|
|
|
// Animasi: muncul → bounce → hilang
|
|
popup.animate([
|
|
{ transform: 'translate(-50%, -50%) scale(0.3) rotate(-10deg)', opacity: 0 },
|
|
{ transform: 'translate(-50%, -50%) scale(1.3) rotate(5deg)', opacity: 1, offset: 0.3 },
|
|
{ transform: 'translate(-50%, -50%) scale(1.1) rotate(-2deg)', opacity: 1, offset: 0.6 },
|
|
{ transform: 'translate(-50%, -50%) scale(0.8) rotate(0deg)', opacity: 0 }
|
|
], {
|
|
duration: 1200,
|
|
easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)' // Bounce effect
|
|
}).onfinish = () => popup.remove(); // Auto cleanup
|
|
}
|
|
|
|
/* ==========================================
|
|
PARTICLE BURST - Ledakan partikel dari tile
|
|
========================================== */
|
|
function createParticleBurst(tileElement) {
|
|
// Ambil posisi tengah tile
|
|
const rect = tileElement.getBoundingClientRect();
|
|
const centerX = rect.left + rect.width / 2;
|
|
const centerY = rect.top + rect.height / 2;
|
|
|
|
// Ambil warna tile sesuai nilainya
|
|
const tileValue = parseInt(tileElement.textContent);
|
|
const tileColor = getTileColor(tileValue);
|
|
|
|
// Jumlah partikel random (8-12)
|
|
const particleCount = 8 + Math.floor(Math.random() * 5);
|
|
|
|
// Buat partikel dalam lingkaran (360°)
|
|
for (let i = 0; i < particleCount; i++) {
|
|
const particle = document.createElement('div');
|
|
particle.className = 'merge-particle';
|
|
particle.style.left = centerX + 'px';
|
|
particle.style.top = centerY + 'px';
|
|
particle.style.background = tileColor; // Warna sama dengan tile
|
|
|
|
document.body.appendChild(particle);
|
|
|
|
// Hitung sudut & kecepatan untuk ledakan melingkar
|
|
const angle = (Math.PI * 2 * i) / particleCount + (Math.random() - 0.5) * 0.5;
|
|
const velocity = 60 + Math.random() * 40; // 60-100px
|
|
const tx = Math.cos(angle) * velocity; // Posisi X
|
|
const ty = Math.sin(angle) * velocity; // Posisi Y
|
|
|
|
// Animasi: meledak keluar sambil mengecil
|
|
particle.animate([
|
|
{ transform: 'translate(0, 0) scale(1)', opacity: 1 },
|
|
{ transform: `translate(${tx}px, ${ty}px) scale(0)`, opacity: 0 }
|
|
], {
|
|
duration: 500 + Math.random() * 200, // 500-700ms
|
|
easing: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)' // Smooth ease-out
|
|
}).onfinish = () => particle.remove(); // Auto cleanup
|
|
}
|
|
}
|
|
|
|
/* ==========================================
|
|
SCORE POPUP - Angka score yang terbang ke atas
|
|
========================================== */
|
|
function createScorePopup(x, y, score) {
|
|
const popup = document.createElement('div');
|
|
popup.className = 'score-popup';
|
|
popup.textContent = '+' + score; // Contoh: "+16", "+32"
|
|
popup.style.left = x + 'px';
|
|
popup.style.top = y + 'px';
|
|
popup.style.position = 'fixed';
|
|
popup.style.fontSize = '24px';
|
|
popup.style.fontWeight = '900';
|
|
popup.style.color = '#ffd700'; // Gold
|
|
popup.style.textShadow = '0 0 20px rgba(255, 215, 0, 0.8)';
|
|
popup.style.pointerEvents = 'none';
|
|
popup.style.zIndex = '9999';
|
|
popup.style.transform = 'translate(-50%, -50%)';
|
|
|
|
document.body.appendChild(popup);
|
|
|
|
// Animasi: terbang ke atas sambil fade out
|
|
popup.animate([
|
|
{ transform: 'translate(-50%, -50%) scale(0.5)', opacity: 0 },
|
|
{ transform: 'translate(-50%, -70px) scale(1.2)', opacity: 1, offset: 0.3 },
|
|
{ transform: 'translate(-50%, -120px) scale(1)', opacity: 0 }
|
|
], {
|
|
duration: 1000,
|
|
easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)' // Bounce ease
|
|
}).onfinish = () => popup.remove();
|
|
}
|
|
|
|
/* ==========================================
|
|
GET TILE COLOR - Warna sesuai nilai tile
|
|
==========================================
|
|
Dipakai untuk partikel biar warnanya match
|
|
========================================== */
|
|
function getTileColor(value) {
|
|
const colors = {
|
|
2: '#00eaff', // Cyan
|
|
4: '#00ff99', // Green
|
|
8: '#ff00ff', // Magenta
|
|
16: '#ff0066', // Pink
|
|
32: '#ffaa00', // Orange
|
|
64: '#ff0000', // Red
|
|
128: '#5f00ff', // Purple
|
|
256: '#00ffea', // Cyan bright
|
|
512: '#ff00aa', // Pink bright
|
|
1024: '#00ffaa', // Green bright
|
|
2048: '#ffd700' // Gold (winning tile!)
|
|
};
|
|
// Return warna sesuai value, default cyan kalau nggak ada
|
|
return colors[value] || '#00eaff';
|
|
} |