/* ========================================== 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'; }