This commit is contained in:
Evelyn Sucitro 2025-12-03 13:46:36 +07:00
parent bf2778dfd7
commit 5a27928bca
11 changed files with 354 additions and 247 deletions

277
2048.css
View File

@ -534,16 +534,56 @@ h1 {
} }
/* Tile Colors */ /* Tile Colors */
.tile-2 { background: #00eaff55; box-shadow: 0 0 12px #00eaff; } .tile-2 {
.tile-4 { background: #00ff9955; box-shadow: 0 0 12px #00ff99; } background: #00eaff55;
.tile-8 { background: #ff00ff55; box-shadow: 0 0 12px #ff00ff; } box-shadow: 0 0 12px #00eaff;
.tile-16 { background: #ff006655; box-shadow: 0 0 12px #ff0066; } }
.tile-32 { background: #ffaa0055; box-shadow: 0 0 12px #ffaa00; }
.tile-64 { background: #ff000055; box-shadow: 0 0 12px #ff0000; } .tile-4 {
.tile-128 { background: #5f00ff55; box-shadow: 0 0 12px #5f00ff; } background: #00ff9955;
.tile-256 { background: #00ffea55; box-shadow: 0 0 12px #00ffea; } box-shadow: 0 0 12px #00ff99;
.tile-512 { background: #ff00aa55; box-shadow: 0 0 12px #ff00aa; } }
.tile-1024 { background: #00ffaa55; box-shadow: 0 0 12px #00ffaa; }
.tile-8 {
background: #ff00ff55;
box-shadow: 0 0 12px #ff00ff;
}
.tile-16 {
background: #ff006655;
box-shadow: 0 0 12px #ff0066;
}
.tile-32 {
background: #ffaa0055;
box-shadow: 0 0 12px #ffaa00;
}
.tile-64 {
background: #ff000055;
box-shadow: 0 0 12px #ff0000;
}
.tile-128 {
background: #5f00ff55;
box-shadow: 0 0 12px #5f00ff;
}
.tile-256 {
background: #00ffea55;
box-shadow: 0 0 12px #00ffea;
}
.tile-512 {
background: #ff00aa55;
box-shadow: 0 0 12px #ff00aa;
}
.tile-1024 {
background: #00ffaa55;
box-shadow: 0 0 12px #00ffaa;
}
.tile-2048 { .tile-2048 {
background: #ffd70066; background: #ffd70066;
box-shadow: 0 0 18px #ffd700; box-shadow: 0 0 18px #ffd700;
@ -881,10 +921,6 @@ h1 {
/* ========================== /* ==========================
GAME OVER MODAL - WITH ICON BUTTONS GAME OVER MODAL - WITH ICON BUTTONS
========================== */ ========================== */
/* ==========================
GAME OVER MODAL - REVISED VERSION
Copy bagian ini dan replace yang lama di 2048.css
========================== */
.game-over-overlay { .game-over-overlay {
position: fixed; position: fixed;
@ -1237,219 +1273,6 @@ h1 {
height: clamp(28px, 6vw, 34px); height: clamp(28px, 6vw, 34px);
} }
} }
/* ==========================
BACKGROUND EFFECTS
========================== */
.particles {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 0;
background:
radial-gradient(circle at 20% 30%, rgba(0,234,255,0.2), transparent 48%),
radial-gradient(circle at 80% 70%, rgba(255,0,90,0.2), transparent 48%),
radial-gradient(circle at 50% 50%, rgba(140,0,255,0.16), transparent 58%),
radial-gradient(circle at 10% 85%, rgba(0,255,200,0.14), transparent 58%),
radial-gradient(circle at 90% 15%, rgba(255,0,255,0.17), transparent 48%);
filter: blur(60px) brightness(125%) saturate(135%);
animation: particlesFloat 20s ease-in-out infinite alternate;
}
@keyframes particlesFloat {
0% { transform: translateY(0px) translateX(0px); }
50% { transform: translateY(-30px) translateX(12px); }
100% { transform: translateY(-45px) translateX(-18px); }
}
/* Floating Particles from Bottom */
.floating-particles {
position: fixed;
inset: 0;
overflow: hidden;
pointer-events: none;
z-index: 0; /* Di bawah semua elemen game */
}
.floating-particle {
position: absolute;
bottom: -20px;
width: 8px;
height: 8px;
background: rgba(0, 234, 255, 0.6);
border-radius: 50%;
filter: blur(1px);
box-shadow: 0 0 15px currentColor;
animation: floatUp linear infinite;
}
@keyframes floatUp {
0% {
transform: translateY(0) translateX(0) scale(0.5);
opacity: 0;
}
10% {
opacity: 1;
}
90% {
opacity: 0.6;
}
100% {
transform: translateY(-100vh) translateX(var(--drift, 0)) scale(1.2);
opacity: 0;
}
}
/* Different particle colors */
.floating-particle.cyan {
background: rgba(0, 234, 255, 0.7);
box-shadow: 0 0 20px rgba(0, 234, 255, 0.8);
}
.floating-particle.pink {
background: rgba(255, 0, 255, 0.6);
box-shadow: 0 0 20px rgba(255, 0, 255, 0.7);
}
.floating-particle.purple {
background: rgba(200, 100, 255, 0.6);
box-shadow: 0 0 20px rgba(200, 100, 255, 0.7);
}
.floating-particle.green {
background: rgba(0, 255, 200, 0.6);
box-shadow: 0 0 20px rgba(0, 255, 200, 0.7);
}
.floating-particle.orange {
background: rgba(255, 170, 0, 0.6);
box-shadow: 0 0 20px rgba(255, 170, 0, 0.7);
}
.starfield {
position: fixed;
inset: 0;
overflow: hidden;
pointer-events: none;
z-index: 1;
}
.starfield span {
position: absolute;
width: 4px;
height: 4px;
background: rgba(0,255,255,0.85);
border-radius: 50%;
filter: blur(2px);
animation: starMove linear infinite;
}
@keyframes starMove {
0% { transform: translateY(0); opacity: 0.85; }
100% { transform: translateY(-750px); opacity: 0; }
}
.cursor-light {
position: absolute;
width: 250px;
height: 250px;
background: radial-gradient(circle, rgba(0,255,255,0.28), transparent 72%);
border-radius: 50%;
pointer-events: none;
transform: translate(-50%, -50%);
filter: blur(45px);
mix-blend-mode: screen;
opacity: 0.65;
}
.merge-particle {
position: absolute;
width: 8px;
height: 8px;
border-radius: 50%;
pointer-events: none;
opacity: 0.95;
will-change: transform, opacity;
filter: blur(1px) drop-shadow(0 0 8px rgba(255,255,255,0.1));
}
/* ==========================
ENHANCED BACKGROUND EFFECTS
/* Update .particles styling */
.particles {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 0;
background:
radial-gradient(circle at 20% 30%, rgba(0,234,255,0.15), transparent 40%),
radial-gradient(circle at 80% 70%, rgba(255,0,90,0.15), transparent 40%),
radial-gradient(circle at 50% 50%, rgba(140,0,255,0.12), transparent 50%),
radial-gradient(circle at 10% 85%, rgba(0,255,200,0.1), transparent 50%),
radial-gradient(circle at 90% 15%, rgba(255,0,255,0.13), transparent 40%);
filter: blur(50px) brightness(110%);
animation: particlesFloat 18s ease-in-out infinite alternate;
}
@keyframes particlesFloat {
0% {
transform: translateY(0px) translateX(0px);
opacity: 0.8;
}
50% {
transform: translateY(-25px) translateX(15px);
opacity: 1;
}
100% {
transform: translateY(-40px) translateX(-20px);
opacity: 0.8;
}
}
/* Enhanced Starfield */
.starfield {
position: fixed;
inset: 0;
overflow: hidden;
pointer-events: none;
z-index: 1;
}
.starfield span {
position: absolute;
width: 3px;
height: 3px;
background: rgba(0,255,255,0.7);
border-radius: 50%;
filter: blur(1.5px);
box-shadow: 0 0 8px rgba(0, 234, 255, 0.6);
animation: starMove linear infinite;
}
@keyframes starMove {
0% {
transform: translateY(0);
opacity: 0.7;
}
100% {
transform: translateY(-750px);
opacity: 0;
}
}
/* Enhanced Cursor Light */
.cursor-light {
position: absolute;
width: 280px;
height: 280px;
background: radial-gradient(circle, rgba(0,255,255,0.2), rgba(255,0,255,0.15) 50%, transparent 70%);
border-radius: 50%;
pointer-events: none;
transform: translate(-50%, -50%);
filter: blur(50px);
mix-blend-mode: screen;
opacity: 0.5;
transition: opacity 0.3s ease;
}
/* ========================== /* ==========================
RESPONSIVE DESIGN RESPONSIVE DESIGN

View File

@ -4,6 +4,13 @@
<meta charset="UTF-8"/> <meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>2048</title> <title>2048</title>
<link rel="stylesheet" href="2048_Base.css"/>
<link rel="stylesheet" href="2048_Animation.css"/>
<link rel="stylesheet" href="2048_Layout.css"/>
<link rel="stylesheet" href="2048_Buttons.css"/>
<link rel="stylesheet" href="2048_Modals.css"/>
<link rel="stylesheet" href="2048_Background_Effects.css"/>
<link rel="stylesheet" href="2048_Floating_Particles.css"/>
<link rel="stylesheet" href="2048.css"/> <link rel="stylesheet" href="2048.css"/>
<link rel="preconnect" href="https://fonts.googleapis.com"/> <link rel="preconnect" href="https://fonts.googleapis.com"/>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
@ -320,5 +327,6 @@
<script src="2048_Logic.js"></script> <script src="2048_Logic.js"></script>
<script src="2048_Controls.js"></script> <script src="2048_Controls.js"></script>
<script src="Main_2048.js"></script> <script src="Main_2048.js"></script>
<script src="Tutorial_Logic.js"></script>
</body> </body>
</html> </html>

147
2048_Background_Effects.css Normal file
View File

@ -0,0 +1,147 @@
/* ==========================
BACKGROUND EFFECTS
========================== */
.particles {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 0;
background:
radial-gradient(circle at 20% 30%, rgba(0,234,255,0.2), transparent 48%),
radial-gradient(circle at 80% 70%, rgba(255,0,90,0.2), transparent 48%),
radial-gradient(circle at 50% 50%, rgba(140,0,255,0.16), transparent 58%),
radial-gradient(circle at 10% 85%, rgba(0,255,200,0.14), transparent 58%),
radial-gradient(circle at 90% 15%, rgba(255,0,255,0.17), transparent 48%);
filter: blur(60px) brightness(125%) saturate(135%);
animation: particlesFloat 20s ease-in-out infinite alternate;
}
@keyframes particlesFloat {
0% { transform: translateY(0px) translateX(0px); }
50% { transform: translateY(-30px) translateX(12px); }
100% { transform: translateY(-45px) translateX(-18px); }
}
.starfield {
position: fixed;
inset: 0;
overflow: hidden;
pointer-events: none;
z-index: 1;
}
.starfield span {
position: absolute;
width: 4px;
height: 4px;
background: rgba(0,255,255,0.85);
border-radius: 50%;
filter: blur(2px);
animation: starMove linear infinite;
}
@keyframes starMove {
0% { transform: translateY(0); opacity: 0.85; }
100% { transform: translateY(-750px); opacity: 0; }
}
.cursor-light {
position: absolute;
width: 250px;
height: 250px;
background: radial-gradient(circle, rgba(0,255,255,0.28), transparent 72%);
border-radius: 50%;
pointer-events: none;
transform: translate(-50%, -50%);
filter: blur(45px);
mix-blend-mode: screen;
opacity: 0.65;
}
.merge-particle {
position: absolute;
width: 8px;
height: 8px;
border-radius: 50%;
pointer-events: none;
opacity: 0.95;
will-change: transform, opacity;
filter: blur(1px) drop-shadow(0 0 8px rgba(255,255,255,0.1));
}
/* ENHANCED BACKGROUND EFFECTS */
/* Update .particles styling */
.particles {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 0;
background:
radial-gradient(circle at 20% 30%, rgba(0,234,255,0.15), transparent 40%),
radial-gradient(circle at 80% 70%, rgba(255,0,90,0.15), transparent 40%),
radial-gradient(circle at 50% 50%, rgba(140,0,255,0.12), transparent 50%),
radial-gradient(circle at 10% 85%, rgba(0,255,200,0.1), transparent 50%),
radial-gradient(circle at 90% 15%, rgba(255,0,255,0.13), transparent 40%);
filter: blur(50px) brightness(110%);
animation: particlesFloat 18s ease-in-out infinite alternate;
}
@keyframes particlesFloat {
0% {
transform: translateY(0px) translateX(0px);
opacity: 0.8;
}
50% {
transform: translateY(-25px) translateX(15px);
opacity: 1;
}
100% {
transform: translateY(-40px) translateX(-20px);
opacity: 0.8;
}
}
/* Enhanced Starfield */
.starfield {
position: fixed;
inset: 0;
overflow: hidden;
pointer-events: none;
z-index: 1;
}
.starfield span {
position: absolute;
width: 3px;
height: 3px;
background: rgba(0,255,255,0.7);
border-radius: 50%;
filter: blur(1.5px);
box-shadow: 0 0 8px rgba(0, 234, 255, 0.6);
animation: starMove linear infinite;
}
@keyframes starMove {
0% {
transform: translateY(0);
opacity: 0.7;
}
100% {
transform: translateY(-750px);
opacity: 0;
}
}
/* Enhanced Cursor Light */
.cursor-light {
position: absolute;
width: 280px;
height: 280px;
background: radial-gradient(circle, rgba(0,255,255,0.2), rgba(255,0,255,0.15) 50%, transparent 70%);
border-radius: 50%;
pointer-events: none;
transform: translate(-50%, -50%);
filter: blur(50px);
mix-blend-mode: screen;
opacity: 0.5;
transition: opacity 0.3s ease;
}

View File

@ -0,0 +1,63 @@
/* Floating Particles from Bottom */
.floating-particles {
position: fixed;
inset: 0;
overflow: hidden;
pointer-events: none;
z-index: 0; /* Di bawah semua elemen game */
}
.floating-particle {
position: absolute;
bottom: -20px;
width: 8px;
height: 8px;
background: rgba(0, 234, 255, 0.6);
border-radius: 50%;
filter: blur(1px);
box-shadow: 0 0 15px currentColor;
animation: floatUp linear infinite;
}
@keyframes floatUp {
0% {
transform: translateY(0) translateX(0) scale(0.5);
opacity: 0;
}
10% {
opacity: 1;
}
90% {
opacity: 0.6;
}
100% {
transform: translateY(-100vh) translateX(var(--drift, 0)) scale(1.2);
opacity: 0;
}
}
/* Different particle colors */
.floating-particle.cyan {
background: rgba(0, 234, 255, 0.7);
box-shadow: 0 0 20px rgba(0, 234, 255, 0.8);
}
.floating-particle.pink {
background: rgba(255, 0, 255, 0.6);
box-shadow: 0 0 20px rgba(255, 0, 255, 0.7);
}
.floating-particle.purple {
background: rgba(200, 100, 255, 0.6);
box-shadow: 0 0 20px rgba(200, 100, 255, 0.7);
}
.floating-particle.green {
background: rgba(0, 255, 200, 0.6);
box-shadow: 0 0 20px rgba(0, 255, 200, 0.7);
}
.floating-particle.orange {
background: rgba(255, 170, 0, 0.6);
box-shadow: 0 0 20px rgba(255, 170, 0, 0.7);
}

View File

@ -2,6 +2,7 @@
/* Main Container */ /* Main Container */
.container { .container {
margin-top: 70px;
position: relative; position: relative;
z-index: 2; z-index: 2;
background: rgba(20, 0, 40, 0.65); background: rgba(20, 0, 40, 0.65);

View File

@ -3,8 +3,8 @@
/* Back Button */ /* Back Button */
.btn-back { .btn-back {
position: fixed; position: fixed;
top: 30px; top: 20px;
left: 30px; left: 20px;
z-index: 100; z-index: 100;
width: 50px; width: 50px;
height: 50px; height: 50px;

View File

@ -1,4 +1,3 @@
/* register-form.css */
.input-group { .input-group {
margin-bottom: 25px; margin-bottom: 25px;
} }
@ -28,19 +27,15 @@ input::placeholder {
width: 100%; width: 100%;
padding: 15px; padding: 15px;
margin-top: 10px; margin-top: 10px;
background: linear-gradient(90deg, #00eaff, #ff00ff); background: linear-gradient(90deg, #00eaff, #ff00ff);
border: none; border: none;
border-radius: 12px; border-radius: 12px;
color: #fff; color: #fff;
font-weight: bold; font-weight: bold;
text-transform: uppercase; text-transform: uppercase;
font-size: 18px; font-size: 18px;
letter-spacing: 2px; letter-spacing: 2px;
cursor: pointer; cursor: pointer;
box-shadow: 0 5px 25px rgba(0, 217, 255, 0.4); box-shadow: 0 5px 25px rgba(0, 217, 255, 0.4);
transition: all 0.25s ease; transition: all 0.25s ease;
} }

View File

@ -37,24 +37,44 @@ document.getElementById("registerForm").addEventListener("submit", async functio
// 1. Tampilkan modal sukses // 1. Tampilkan modal sukses
showModal("success", "Register Successful!", data.message); showModal("success", "Register Successful!", data.message);
// 🔥 PERBAIKAN: SAVE SCORE & REDIRECT 🔥 // 2. Simpan Score (Logic Anda)
const pendingScore = localStorage.getItem('lastScore');
// Coba simpan skor jika variable 'score' atau 'gameScore' tersedia di global scope
// Atau jika kamu menyimpan skor sementara di localStorage
const pendingScore = localStorage.getItem('lastScore'); // Contoh jika pakai localStorage
if (pendingScore && typeof saveScore === "function") { if (pendingScore && typeof saveScore === "function") {
console.log("Menyimpan skor pending: " + pendingScore); console.log("Menyimpan skor pending: " + pendingScore);
saveScore(pendingScore); saveScore(pendingScore);
} else if (typeof score !== 'undefined') { } else if (typeof score !== 'undefined') {
// Jika variabel global 'score' ada (dari file game logic)
saveScore(score); saveScore(score);
} }
// Redirect ke halaman utama setelah 1.5 detik // --- 🔥 PERUBAHAN DI SINI 🔥 ---
// Cari tombol OK pada modal.
// PENTING: Pastikan ID ini ("modalOkBtn") sesuai dengan ID tombol OK di HTML/Modal Anda.
// Jika ID-nya beda, ganti string di bawah ini.
// --- 🔥 TAMBAHKAN KODE DI SINI (Bagian Tombol OK) 🔥 ---
const okBtn = document.getElementById("modalOkBtn");
if (okBtn) {
okBtn.addEventListener("click", function() {
// [BARU] Simpan username ke sessionStorage AGAR TUTORIAL MUNCUL
// Ini penting supaya logic di 2048.js tahu ini user baru
sessionStorage.setItem("loggedInUser", username);
// Setelah disimpan, baru pindah ke Homepage
window.location.href = "Homepage.html";
}, { once: true });
} else {
// Fallback jika tombol error
console.warn("Tombol OK tidak ditemukan, redirect otomatis.");
setTimeout(() => { setTimeout(() => {
window.location.href = "index.html"; // Ubah sesuai halaman game/menu kamu // [BARU] Simpan juga disini buat jaga-jaga
}, 1500); sessionStorage.setItem("loggedInUser", username);
window.location.href = "Homepage.html";
}, 2000);
}
} else { } else {
showModal("error", "Register Failed!", data.message || "An error occurred."); showModal("error", "Register Failed!", data.message || "An error occurred.");

View File

@ -1,4 +1,3 @@
/* register-modal.css */
.modal { .modal {
display: none; display: none;
position: fixed; position: fixed;

View File

@ -1,4 +1,3 @@
/* register-responsive.css */
@media (max-width: 480px) { @media (max-width: 480px) {
.container { .container {
padding: 30px 25px; padding: 30px 25px;

52
Tutorial_Logic.js Normal file
View File

@ -0,0 +1,52 @@
/* ----------------------------------------------------
FILE: Tutorial_Logic.js
---------------------------------------------------- */
// Kita buat fungsi agar bisa dipanggil manual jika perlu
function checkAndShowTutorial() {
// 1. Ambil user terbaru saat fungsi dijalankan
const currentUser = sessionStorage.getItem("loggedInUser") || "guest";
const tutorialKey = 'tutorialSeen_' + currentUser;
// DEBUG: Cek di Console browser (tekan F12 -> Console)
console.log(`[Tutorial Check] User: ${currentUser}`);
console.log(`[Tutorial Check] Key: ${tutorialKey}`);
// 2. Cek status di LocalStorage
const hasSeenTutorial = localStorage.getItem(tutorialKey);
console.log(`[Tutorial Check] Status Seen: ${hasSeenTutorial}`);
const tutorialOverlay = document.getElementById('tutorial-overlay');
// 3. Logic: Jika belum pernah lihat (null) -> Tampilkan
if (!hasSeenTutorial && tutorialOverlay) {
console.log("-> Menampilkan Tutorial untuk user baru.");
tutorialOverlay.style.display = 'flex';
} else {
console.log("-> User ini sudah pernah lihat tutorial (atau overlay tidak ketemu).");
}
}
// Jalankan otomatis saat halaman selesai loading
document.addEventListener('DOMContentLoaded', () => {
checkAndShowTutorial();
// Setup tombol close hanya sekali
const closeTutorialBtn = document.getElementById('close-tutorial');
const tutorialOverlay = document.getElementById('tutorial-overlay');
if (closeTutorialBtn) {
closeTutorialBtn.addEventListener('click', () => {
// Ambil user SAAT INI (penting jika user berubah tanpa reload)
const currentUser = sessionStorage.getItem("loggedInUser") || "guest";
const tutorialKey = 'tutorialSeen_' + currentUser;
if(tutorialOverlay) tutorialOverlay.style.display = 'none';
localStorage.setItem(tutorialKey, 'true');
console.log(`[Tutorial Check] Disimpan: ${tutorialKey} = true`);
});
}
});
// (Opsional) Bikin global biar bisa dipanggil dari file lain/console
window.checkTutorial = checkAndShowTutorial;