This commit is contained in:
Jevinca Marvella 2025-12-01 20:57:32 +07:00
commit cd0dccc729
11 changed files with 328 additions and 159 deletions

View File

@ -1047,14 +1047,14 @@ h1 {
} }
} }
/* Best Score Display - ORANGE gradient */ /* High Score Display - ORANGE gradient */
.best-score-display { .high-score-display {
margin-top: 28px; margin-top: 28px;
padding-top: 28px; padding-top: 28px;
border-top: 2px solid rgba(255, 140, 0, 0.25); border-top: 2px solid rgba(255, 140, 0, 0.25);
} }
.best-score-label { .high-score-label {
font-size: 11px; font-size: 11px;
text-transform: uppercase; text-transform: uppercase;
color: rgba(255, 160, 50, 0.7); color: rgba(255, 160, 50, 0.7);
@ -1063,7 +1063,7 @@ h1 {
font-weight: 700; font-weight: 700;
} }
.best-score-value { .high-score-value {
font-size: clamp(34px, 5vw, 42px); font-size: clamp(34px, 5vw, 42px);
font-weight: 900; font-weight: 900;
background: linear-gradient(135deg, #ff8c00 0%, #ffa500 50%, #ffb347 100%); background: linear-gradient(135deg, #ff8c00 0%, #ffa500 50%, #ffb347 100%);

View File

@ -210,7 +210,7 @@
</div> </div>
<div class="score-box"> <div class="score-box">
<div class="score-label">HIGH SCORE</div> <div class="score-label">HIGH SCORE</div>
<div class="score-value" id="best-score">0</div> <div class="score-value" id="high-score">0</div>
</div> </div>
</div> </div>
</div> </div>
@ -325,14 +325,14 @@
New High Score New High Score
</div> </div>
<!-- Best Score Display - show if NOT new record --> <!-- high Score Display - show if NOT new record -->
<div <div
class="best-score-display" class="high-score-display"
id="best-score-display" id="high-score-display"
style="display: none" style="display: none"
> >
<div class="best-score-label">High Score</div> <div class="high-score-label">High Score</div>
<div class="best-score-value" id="modal-best-score">0</div> <div class="high-score-value" id="modal-high-score">0</div>
</div> </div>
</div> </div>

46
2048.js
View File

@ -1,11 +1,18 @@
/* ------------------------ /* ------------------------
State & Variables State & Variables
------------------------ */ ------------------------ */
let board = []; let board = [];
let currentScore = 0; let currentScore = 0;
let bestScore = parseInt(localStorage.getItem('bestScore2048')) || 0;
// 1. Ambil username dari sessionStorage (sesuai sistem login kamu)
const currentUser = sessionStorage.getItem("loggedInUser") || "guest";
// 2. Buat nama kunci unik, misal: "highScore2048_budi"
const storageKey = 'highScore2048_' + currentUser;
// 3. Ambil skor milik user tersebut saja
let highScore = parseInt(localStorage.getItem(storageKey)) || 0;
let lastMoveDir = null; let lastMoveDir = null;
let isMoving = false; let isMoving = false;
let mergesInCurrentMove = 0; let mergesInCurrentMove = 0;
@ -71,7 +78,7 @@ function checkAndShowTutorial() {
DOM Ready DOM Ready
------------------------ */ ------------------------ */
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
updateBestScoreDisplay(); updateHighScoreDisplay();
setupBoard(); setupBoard();
addNewTile(); addNewTile();
addNewTile(); addNewTile();
@ -223,17 +230,18 @@ function updateScoreDisplay() {
scoreEl.textContent = currentScore; scoreEl.textContent = currentScore;
} }
if (currentScore > bestScore) { if (currentScore > highScore) {
bestScore = currentScore; highScore = currentScore;
localStorage.setItem('bestScore2048', bestScore); // Gunakan storageKey yang sudah kita buat di atas (dinamis sesuai user)
updateBestScoreDisplay(); localStorage.setItem(storageKey, highScore);
} updateHighScoreDisplay();
} }
}
function updateBestScoreDisplay() { function updateHighScoreDisplay() {
const bestScoreEl = document.getElementById('best-score'); const highScoreEl = document.getElementById('high-score');
if (bestScoreEl) { if (highScoreEl) {
bestScoreEl.textContent = bestScore; highScoreEl.textContent = highScore;
} }
} }
@ -601,7 +609,7 @@ function showGameOver() {
console.error("Fungsi saveScore tidak ditemukan! Pastikan Score_Request.js sudah diload."); console.error("Fungsi saveScore tidak ditemukan! Pastikan Score_Request.js sudah diload.");
} }
const isNewHighScore = finalScore >= bestScore && finalScore > 0; const isNewHighScore = finalScore >= highScore && finalScore > 0;
const finalScoreEl = document.getElementById('final-score'); const finalScoreEl = document.getElementById('final-score');
if (finalScoreEl) { if (finalScoreEl) {
@ -609,16 +617,16 @@ function showGameOver() {
} }
const newHighScoreBadge = document.getElementById('new-high-score-badge'); const newHighScoreBadge = document.getElementById('new-high-score-badge');
const bestScoreDisplay = document.getElementById('best-score-display'); const highScoreDisplay = document.getElementById('high-score-display');
if (isNewHighScore) { if (isNewHighScore) {
if (newHighScoreBadge) newHighScoreBadge.style.display = 'inline-block'; if (newHighScoreBadge) newHighScoreBadge.style.display = 'inline-block';
if (bestScoreDisplay) bestScoreDisplay.style.display = 'none'; if (highScoreDisplay) highScoreDisplay.style.display = 'none';
} else { } else {
if (newHighScoreBadge) newHighScoreBadge.style.display = 'none'; if (newHighScoreBadge) newHighScoreBadge.style.display = 'none';
if (bestScoreDisplay) bestScoreDisplay.style.display = 'block'; if (highScoreDisplay) highScoreDisplay.style.display = 'block';
const modalBestScore = document.getElementById('modal-best-score'); const modalHighScore = document.getElementById('modal-high-score');
if (modalBestScore) modalBestScore.textContent = bestScore; if (modalHighScore) modalHighScore.textContent = highScore;
} }
const gameOverOverlay = document.getElementById('game-over-overlay'); const gameOverOverlay = document.getElementById('game-over-overlay');

View File

@ -198,7 +198,9 @@ h1::before {
box-shadow: 0 0 15px rgba(0, 234, 255, 0.8); box-shadow: 0 0 15px rgba(0, 234, 255, 0.8);
} }
/* Leaderboard Item */ /* --- LEADERBOARD ITEMS & COLORS (HARMONISASI) --- */
/* Base Item Style */
.leaderboard-item { .leaderboard-item {
display: flex; display: flex;
align-items: center; align-items: center;
@ -206,7 +208,7 @@ h1::before {
padding: 18px 25px; padding: 18px 25px;
border-radius: 14px; border-radius: 14px;
background: rgba(30, 0, 50, 0.5); background: rgba(30, 0, 50, 0.5);
border: 1px solid rgba(0, 255, 255, 0.2); border: 1px solid rgba(255, 255, 255, 0.1);
transition: all 0.3s ease; transition: all 0.3s ease;
position: relative; position: relative;
} }
@ -217,57 +219,105 @@ h1::before {
box-shadow: 0 0 20px rgba(0, 255, 255, 0.3); box-shadow: 0 0 20px rgba(0, 255, 255, 0.3);
} }
/* Rank Styles */ /* === RANK 1: CYAN NEON (Juara) === */
.leaderboard-item.rank-1 { .leaderboard-item.rank-1,
background: linear-gradient(135deg, rgba(0, 234, 255, 0.25), rgba(0, 255, 136, 0.2)); .your-rank-container.rank-1 .your-rank-item {
border: 2px solid rgba(0, 234, 255, 0.6); background: linear-gradient(90deg, rgba(0, 234, 255, 0.25) 0%, rgba(0, 234, 255, 0.05) 100%);
box-shadow: border: 2px solid #00eaff;
0 0 25px rgba(0, 234, 255, 0.4), box-shadow: 0 0 15px rgba(0, 234, 255, 0.3);
inset 0 0 25px rgba(0, 234, 255, 0.15);
}
.leaderboard-item.rank-2 {
background: linear-gradient(135deg, rgba(255, 0, 255, 0.2), rgba(204, 0, 255, 0.15));
border: 2px solid rgba(255, 0, 255, 0.5);
box-shadow: 0 0 18px rgba(255, 0, 255, 0.3);
}
.leaderboard-item.rank-3 {
background: linear-gradient(135deg, rgba(138, 43, 226, 0.25), rgba(75, 0, 130, 0.2));
border: 2px solid rgba(138, 43, 226, 0.5);
box-shadow: 0 0 15px rgba(138, 43, 226, 0.3);
}
/* Rank Badge */
.rank-badge {
width: 45px;
height: 45px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 17px;
font-weight: bold;
flex-shrink: 0;
} }
.rank-1 .rank-badge { .rank-1 .rank-badge {
background: linear-gradient(135deg, #00eaff, #00ff88); background: #00eaff;
color: #0c001a; color: #000;
box-shadow: 0 0 20px rgba(0, 234, 255, 0.8); box-shadow: 0 0 15px #00eaff;
animation: pulseBadge 2s ease-in-out infinite; animation: pulseBadge 2s ease-in-out infinite;
} }
.rank-2 .rank-badge { .rank-1 .player-name {
background: linear-gradient(135deg, #ff00ff, #cc00ff); /* 1. Matikan efek gradient aneh pada teks */
background: none;
-webkit-text-fill-color: initial;
background-clip: border-box;
/* 2. Samakan ukuran font dengan yang lain (sebelumnya 22px jadi 20px) */
font-size: 20px;
/* 3. Warna Putih Solid + Sedikit Glow Cyan biar tetap spesial tapi rapi */
color: #ffffff; color: #ffffff;
box-shadow: 0 0 18px rgba(255, 0, 255, 0.7); text-shadow: 0 0 10px rgba(0, 234, 255, 0.8);
/* Hapus filter drop-shadow berlebih */
filter: none;
}
/* FIX: Warna Score Rank 1 jadi Putih Solid (Anti Silau) */
.rank-1 .score-value {
/* 1. Ukuran disamakan dengan yang lain */
font-size: 22px !important;
/* 2. Warna Teks Putih Solid */
background: none !important;
-webkit-text-fill-color: initial !important;
color: #ffffff !important;
/* 3. GLOWING: Ganti bayangan hitam jadi cahaya Cyan */
text-shadow: 0 0 10px #00eaff, 0 0 20px rgba(0, 234, 255, 0.4) !important;
/* Reset filter */
filter: none !important;
}
/* === RANK 2: MAGENTA NEON (Runner Up) === */
.leaderboard-item.rank-2,
.your-rank-container.rank-2 .your-rank-item {
background: linear-gradient(90deg, rgba(255, 0, 255, 0.25) 0%, rgba(255, 0, 255, 0.05) 100%);
border: 2px solid #ff00ff;
box-shadow: 0 0 15px rgba(255, 0, 255, 0.3);
}
.rank-2 .rank-badge {
background: #ff00ff;
color: #fff;
box-shadow: 0 0 15px #ff00ff;
}
.rank-2 .score-value {
color: #fff;
text-shadow: 0 0 10px #ff00ff;
}
.rank-2 .player-score {
color: #ff00ff;
}
/* === RANK 3: VIOLET NEON (Third) === */
.leaderboard-item.rank-3,
.your-rank-container.rank-3 .your-rank-item {
background: linear-gradient(90deg, rgba(138, 43, 226, 0.25) 0%, rgba(138, 43, 226, 0.05) 100%);
border: 2px solid #8a2be2;
box-shadow: 0 0 15px rgba(138, 43, 226, 0.3);
} }
.rank-3 .rank-badge { .rank-3 .rank-badge {
background: linear-gradient(135deg, #8a2be2, #4b0082); background: #8a2be2;
color: #ffffff; color: #fff;
box-shadow: 0 0 15px rgba(138, 43, 226, 0.6); box-shadow: 0 0 15px #8a2be2;
}
.rank-3 .score-value {
color: #fff;
text-shadow: 0 0 10px #8a2be2;
}
.rank-3 .player-score {
color: #a855f7;
}
/* === RANK OTHER === */
.leaderboard-item.rank-other {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
} }
.rank-other .rank-badge { .rank-other .rank-badge {
@ -276,6 +326,7 @@ h1::before {
border: 1px solid rgba(0, 255, 255, 0.3); border: 1px solid rgba(0, 255, 255, 0.3);
} }
/* Animation */
@keyframes pulseBadge { @keyframes pulseBadge {
0%, 100% { 0%, 100% {
transform: scale(1); transform: scale(1);
@ -287,7 +338,7 @@ h1::before {
} }
} }
/* Player Info */ /* Player Info Common */
.player-info { .player-info {
flex: 1; flex: 1;
display: flex; display: flex;
@ -305,22 +356,11 @@ h1::before {
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.rank-1 .player-name { /* Score Common */
background: linear-gradient(90deg, #00eaff, #00ff88);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-shadow: none;
filter: drop-shadow(0 0 6px rgba(0, 234, 255, 0.5));
font-size: 22px;
}
/* Score */
.player-score { .player-score {
font-weight: 700; font-weight: 700;
font-size: 18px; font-size: 18px;
color: #00eaff; color: #00eaff;
text-shadow: 0 0 12px rgba(0, 234, 255, 0.7);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-end; align-items: flex-end;
@ -332,25 +372,6 @@ h1::before {
font-size: 22px; font-size: 22px;
} }
.rank-1 .score-value {
font-size: 25px;
background: linear-gradient(90deg, #00eaff, #ff00ff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
filter: drop-shadow(0 0 10px rgba(0, 234, 255, 0.8));
}
.rank-2 .player-score {
color: #ff00ff;
text-shadow: 0 0 12px rgba(255, 0, 255, 0.7);
}
.rank-3 .player-score {
color: #a855f7;
text-shadow: 0 0 12px rgba(138, 43, 226, 0.6);
}
.score-label { .score-label {
font-size: 11px; font-size: 11px;
text-transform: uppercase; text-transform: uppercase;
@ -358,15 +379,26 @@ h1::before {
color: rgba(200, 200, 255, 0.5); color: rgba(200, 200, 255, 0.5);
} }
/* Your Rank Fixed Section */ /* Badge Size Common */
.rank-badge {
width: 45px;
height: 45px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 17px;
font-weight: bold;
flex-shrink: 0;
}
/* Your Rank Section Structure */
.your-rank-container { .your-rank-container {
background: rgba(20, 0, 40, 0.9); background: transparent;
border-radius: 14px; border: none;
padding: 15px 25px; box-shadow: none;
border: 2px solid rgba(0, 255, 136, 0.6); padding: 0;
box-shadow: margin-top: 20px;
0 0 25px rgba(0, 255, 136, 0.4),
inset 0 0 20px rgba(0, 255, 136, 0.15);
} }
.your-rank-item { .your-rank-item {
@ -375,15 +407,34 @@ h1::before {
gap: 20px; gap: 20px;
padding: 18px 25px; padding: 18px 25px;
border-radius: 14px; border-radius: 14px;
background: linear-gradient(135deg, rgba(0, 255, 136, 0.15), rgba(0, 234, 255, 0.1)); /* Default style if not top 3 */
background: linear-gradient(135deg, rgba(0, 255, 136, 0.15), rgba(0, 0, 0, 0.5));
border: 2px solid rgba(0, 255, 136, 0.5); border: 2px solid rgba(0, 255, 136, 0.5);
box-shadow: 0 0 20px rgba(0, 255, 136, 0.3); box-shadow: 0 0 20px rgba(0, 255, 136, 0.3);
} }
/* Responsive */ /* --- RESPONSIVE FIX (MODE LAPTOP/LAYAR KECIL) --- */
@media screen and (max-width: 1440px) {
/* Mencegah container nabrak tombol back */
.container {
width: 88%;
padding: 30px 50px;
max-height: 90vh;
}
.btn-back {
top: 20px;
left: 20px;
width: 45px;
height: 45px;
}
}
@media (max-width: 768px) { @media (max-width: 768px) {
.container { .container {
padding: 35px 30px; padding: 35px 30px;
width: 95%;
} }
h1 { h1 {
@ -394,18 +445,6 @@ h1::before {
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
gap: 10px; gap: 10px;
} }
.btn-back {
width: 45px;
height: 45px;
top: 20px;
left: 20px;
}
.btn-back svg {
width: 20px;
height: 20px;
}
} }
@media (max-width: 480px) { @media (max-width: 480px) {

View File

@ -21,6 +21,8 @@
<ul class="leaderboard-list" id="leaderboardList"></ul> <ul class="leaderboard-list" id="leaderboardList"></ul>
<div id="userRankContainer"></div>
</div> </div>
<script src="Leaderboard.js"></script> <script src="Leaderboard.js"></script>

View File

@ -7,12 +7,53 @@ function loadLeaderboard() {
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.status === "success") { if (data.status === "success") {
// Render Top 10
renderLeaderboard(data.leaderboard); renderLeaderboard(data.leaderboard);
// Render Ranking Saya (User Rank)
if (data.user_rank) {
renderUserRank(data.user_rank);
}
} }
}) })
.catch(error => console.error("Error loading leaderboard:", error)); .catch(error => console.error("Error loading leaderboard:", error));
} }
// ... fungsi renderLeaderboard tetap sama ...
// TAMBAHKAN FUNGSI INI DI BAWAH
function renderUserRank(user) {
const container = document.getElementById('userRankContainer');
if (!container) return;
// Format angka skor
const formattedScore = new Intl.NumberFormat().format(user.score);
// HTML structure sesuai CSS .your-rank-container
const html = `
<div class="your-rank-container">
<div style="color: #00ff88; margin-bottom: 10px; font-weight: bold; text-transform: uppercase; letter-spacing: 2px;">
</div>
<div class="your-rank-item">
<div class="rank-badge" style="background: #00ff88; color: #000; box-shadow: 0 0 15px #00ff88;">
${user.rank}
</div>
<div class="player-info">
<div class="player-name">${escapeHtml(user.username)}</div>
</div>
<div class="player-score">
<div class="score-value">${formattedScore}</div>
<div class="score-label">Points</div>
</div>
</div>
</div>
`;
container.innerHTML = html;
}
// ... fungsi escapeHtml tetap sama ...
function renderLeaderboard(players) { function renderLeaderboard(players) {
const listContainer = document.getElementById('leaderboardList'); const listContainer = document.getElementById('leaderboardList');
if (!listContainer) return; // Safety check if (!listContainer) return; // Safety check

View File

@ -1,23 +1,76 @@
<?php <?php
header('Content-Type: application/json'); header('Content-Type: application/json');
require 'Connection.php'; require 'Connection.php';
session_start();
$query = "SELECT username, score FROM leaderboard ORDER BY score DESC LIMIT 10"; $response = [
"status" => "error",
"leaderboard" => [],
"user_rank" => null
];
// ---------------------------------------------------------
// 1. Ambil Top 10 Global (DIPERBAIKI)
// Ditambahkan "user_id ASC" agar urutannya PASTI (Konsisten)
// Jika skor sama, user dengan ID lebih kecil (daftar duluan) akan di atas
// ---------------------------------------------------------
$query = "SELECT username, score FROM leaderboard ORDER BY score DESC, user_id ASC LIMIT 10";
$result = $conn->query($query); $result = $conn->query($query);
$leaderboard = [];
if ($result && $result->num_rows > 0) { if ($result && $result->num_rows > 0) {
while ($row = $result->fetch_assoc()) { while ($row = $result->fetch_assoc()) {
$leaderboard[] = $row; $response['leaderboard'][] = $row;
} }
} }
// Kirim data ke frontend dalam bentuk JSON // ---------------------------------------------------------
echo json_encode([ // 2. Ambil Ranking User Login (DIPERBAIKI)
"status" => "success", // ---------------------------------------------------------
"leaderboard" => $leaderboard if (isset($_SESSION['user_id'])) {
]); $my_id = $_SESSION['user_id'];
// Ambil score user saat ini
$scoreQuery = $conn->prepare("SELECT username, score FROM leaderboard WHERE user_id = ?");
$scoreQuery->bind_param("i", $my_id);
$scoreQuery->execute();
$scoreResult = $scoreQuery->get_result();
if ($scoreRow = $scoreResult->fetch_assoc()) {
$myScore = $scoreRow['score'];
$myUsername = $scoreRow['username'];
// --- LOGIKA BARU ---
// Hitung ranking dengan Tie-Breaker.
// Rank = Jumlah orang yang skornya LEBIH BESAR
// DITAMBAH Jumlah orang yang skornya SAMA tapi ID-nya LEBIH KECIL (dia di atas kita)
$rankQuery = $conn->prepare("
SELECT COUNT(*) as rank_above
FROM leaderboard
WHERE score > ?
OR (score = ? AND user_id < ?)
");
// Kita bind 3 parameter: score, score, id
$rankQuery->bind_param("iii", $myScore, $myScore, $my_id);
$rankQuery->execute();
$rankResult = $rankQuery->get_result();
$rankRow = $rankResult->fetch_assoc();
// Rank adalah jumlah orang di atas kita + 1
$myRank = $rankRow['rank_above'] + 1;
$response['user_rank'] = [
"username" => $myUsername,
"score" => $myScore,
"rank" => $myRank
];
}
}
$response['status'] = "success";
echo json_encode($response);
$conn->close(); $conn->close();
?> ?>

View File

@ -1,12 +1,11 @@
<?php <?php
// ✅ CORS Headers HARUS di paling atas sebelum apapun // ... (Header CORS tetap sama) ...
header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, GET, OPTIONS'); header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization'); header('Access-Control-Allow-Headers: Content-Type, Authorization');
header('Access-Control-Max-Age: 86400'); header('Access-Control-Max-Age: 86400');
header('Content-Type: application/json'); header('Content-Type: application/json');
// ✅ Handle preflight OPTIONS request
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200); http_response_code(200);
exit(); exit();
@ -15,17 +14,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
session_start(); session_start();
include 'Connection.php'; include 'Connection.php';
// Ambil data dari JSON body
$input = json_decode(file_get_contents('php://input'), true); $input = json_decode(file_get_contents('php://input'), true);
$username = $input['username'] ?? ''; $username = $input['username'] ?? '';
$password = $input['password'] ?? ''; $password = $input['password'] ?? '';
if (empty($username) || empty($password)) { // ... (Validasi input kosong tetap sama) ...
echo json_encode(["success" => false, "message" => "Username dan password wajib diisi"]);
exit;
}
$stmt = $conn->prepare("SELECT password FROM users WHERE username = ?"); // 🔴 PERBAIKAN 1: Tambahkan 'id' di dalam SELECT
$stmt = $conn->prepare("SELECT id, password FROM users WHERE username = ?");
$stmt->bind_param("s", $username); $stmt->bind_param("s", $username);
$stmt->execute(); $stmt->execute();
$stmt->store_result(); $stmt->store_result();
@ -37,11 +33,15 @@ if ($stmt->num_rows === 0) {
exit; exit;
} }
$stmt->bind_result($hashedPassword); // 🔴 PERBAIKAN 2: Bind result untuk menangkap 'id' dan 'password'
$stmt->bind_result($userId, $hashedPassword);
$stmt->fetch(); $stmt->fetch();
if (password_verify($password, $hashedPassword)) { if (password_verify($password, $hashedPassword)) {
// 🔴 PERBAIKAN 3: Simpan 'user_id' ke dalam SESSION
$_SESSION['user_id'] = $userId;
$_SESSION['username'] = $username; $_SESSION['username'] = $username;
echo json_encode([ echo json_encode([
"success" => true, "success" => true,
"message" => "Login berhasil", "message" => "Login berhasil",

View File

@ -62,6 +62,7 @@
</div> </div>
</div> </div>
<script src="Score_Request.js"></script>
<script type="module" src="Register.js"></script> <script type="module" src="Register.js"></script>
<script src="Animation_Register.js"></script> <script src="Animation_Register.js"></script>
</body> </body>

View File

@ -28,17 +28,34 @@ document.getElementById("registerForm").addEventListener("submit", async functio
// Button loading state // Button loading state
const submitBtn = this.querySelector("button[type='submit']"); const submitBtn = this.querySelector("button[type='submit']");
const originalText = submitBtn.textContent;
submitBtn.disabled = true; submitBtn.disabled = true;
try { try {
const data = await registerRequest(username, password); const data = await registerRequest(username, password);
// API temenmu return: { status: "success", message: "..." }
if (data.status === "success") { if (data.status === "success") {
// 1. Tampilkan modal sukses
showModal("success", "Register Successful!", data.message); showModal("success", "Register Successful!", data.message);
// 🔥 PERBAIKAN: SAVE SCORE & REDIRECT 🔥
// 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") {
console.log("Menyimpan skor pending: " + pendingScore);
saveScore(pendingScore);
} else if (typeof score !== 'undefined') {
// Jika variabel global 'score' ada (dari file game logic)
saveScore(score);
}
// Redirect ke halaman utama setelah 1.5 detik
setTimeout(() => {
window.location.href = "index.html"; // Ubah sesuai halaman game/menu kamu
}, 1500);
} else { } else {
showModal("error", "Register Failed!", data.message || "An error occurred."); showModal("error", "Register Failed!", data.message || "An error occurred.");
setInputError("username"); setInputError("username");
@ -48,11 +65,9 @@ document.getElementById("registerForm").addEventListener("submit", async functio
console.error("Register Error:", error); console.error("Register Error:", error);
let msg = "A connection error occurred."; let msg = "A connection error occurred.";
if (error.message === "Failed to fetch") { if (error.message === "Failed to fetch") {
msg = "Unable to connect to server."; msg = "Unable to connect to server.";
} }
showModal("error", "Error!", msg); showModal("error", "Error!", msg);
} finally { } finally {
@ -68,8 +83,10 @@ document.getElementById("confirmPassword").addEventListener("input", clearInputS
// Efek error merah neon // Efek error merah neon
function setInputError(id) { function setInputError(id) {
const el = document.getElementById(id); const el = document.getElementById(id);
if(el) {
el.style.borderColor = "#ff0080"; el.style.borderColor = "#ff0080";
el.style.boxShadow = "0 0 20px rgba(255, 0, 128, 0.3)"; el.style.boxShadow = "0 0 20px rgba(255, 0, 128, 0.3)";
}
} }
// Hilangkan error visual // Hilangkan error visual

View File

@ -2,12 +2,12 @@
// ✅ Set timezone Indonesia (WIB) // ✅ Set timezone Indonesia (WIB)
date_default_timezone_set('Asia/Jakarta'); date_default_timezone_set('Asia/Jakarta');
// ✅ CORS Headers - di paling atas // ✅ CORS Headers
header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, GET, OPTIONS'); header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization'); header('Access-Control-Allow-Headers: Content-Type, Authorization');
header('Access-Control-Max-Age: 86400'); header('Access-Control-Max-Age: 86400');
header('Content-Type: application/json'); // Cukup 1x saja header('Content-Type: application/json');
// ✅ Handle preflight OPTIONS // ✅ Handle preflight OPTIONS
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
@ -17,7 +17,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
include 'Connection.php'; include 'Connection.php';
// ✅ Handle input dari JSON body atau POST form // ✅ Handle input
$input = json_decode(file_get_contents('php://input'), true); $input = json_decode(file_get_contents('php://input'), true);
$username = trim($input['username'] ?? $_POST['username'] ?? ''); $username = trim($input['username'] ?? $_POST['username'] ?? '');
$password = $input['password'] ?? $_POST['password'] ?? ''; $password = $input['password'] ?? $_POST['password'] ?? '';
@ -31,7 +31,7 @@ if (empty($username) || empty($password)) {
exit; exit;
} }
// ✅ Validasi panjang password minimal // ✅ Validasi panjang password
if (strlen($password) < 6) { if (strlen($password) < 6) {
echo json_encode([ echo json_encode([
"status" => "error", "status" => "error",
@ -68,15 +68,23 @@ $check->close();
// ✅ Hash password dan insert ke database // ✅ Hash password dan insert ke database
$hashedPassword = password_hash($password, PASSWORD_DEFAULT); $hashedPassword = password_hash($password, PASSWORD_DEFAULT);
$created_at = date("Y-m-d H:i:s");
$stmt = $conn->prepare("INSERT INTO users (username, password) VALUES (?, ?)"); $stmt = $conn->prepare("INSERT INTO users (username, password, created_at) VALUES (?, ?, ?)");
$stmt->bind_param("ss", $username, $hashedPassword); $stmt->bind_param("sss", $username, $hashedPassword, $created_at);
if ($stmt->execute()) { if ($stmt->execute()) {
// 🔥 PERBAIKAN UTAMA DI SINI (AUTO-LOGIN) 🔥
$new_user_id = $stmt->insert_id; // Ambil ID user baru
session_start();
$_SESSION['user_id'] = $new_user_id; // Set Session ID
$_SESSION['username'] = $username; // Set Session Username
echo json_encode([ echo json_encode([
"status" => "success", "status" => "success",
"message" => "Pendaftaran berhasil", "message" => "Pendaftaran berhasil & Auto-login",
"registered_at" => date("Y-m-d H:i:s") // timestamp sudah WIB "registered_at" => $created_at
]); ]);
} else { } else {
echo json_encode([ echo json_encode([