test
This commit is contained in:
parent
27fdee7566
commit
19d3eb26cd
@ -281,6 +281,45 @@ body {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
// === SIMPAN SKOR KE LEADERBOARD ===
|
||||||
|
function saveScoreToLeaderboard(totalScore) {
|
||||||
|
let leaderboard = JSON.parse(localStorage.getItem("leaderboard")) || [];
|
||||||
|
|
||||||
|
let record = {
|
||||||
|
player: localStorage.getItem("username") || "Guest",
|
||||||
|
score: totalScore,
|
||||||
|
date: new Date().toLocaleString()
|
||||||
|
};
|
||||||
|
|
||||||
|
leaderboard.push(record);
|
||||||
|
leaderboard.sort((a, b) => b.score - a.score);
|
||||||
|
leaderboard = leaderboard.slice(0, 10); // max 10 skor terbaik
|
||||||
|
|
||||||
|
localStorage.setItem("leaderboard", JSON.stringify(leaderboard));
|
||||||
|
}
|
||||||
|
|
||||||
|
// === GANTI BAGIAN showEndScreen() DENGAN TAMBAHAN SAVE ===
|
||||||
|
function showEndScreen() {
|
||||||
|
clearInterval(countdown);
|
||||||
|
let baseScore = score;
|
||||||
|
let timeBonus = time * 5;
|
||||||
|
let moveBonus = Math.max(0, 200 - moves * 10);
|
||||||
|
let total = baseScore + timeBonus + moveBonus;
|
||||||
|
|
||||||
|
document.getElementById("baseScoreEnd").textContent = baseScore;
|
||||||
|
document.getElementById("timeBonusEnd").textContent = "+" + timeBonus;
|
||||||
|
document.getElementById("moveBonusEnd").textContent = "+" + moveBonus;
|
||||||
|
document.getElementById("totalScoreEnd").textContent = total;
|
||||||
|
document.getElementById("endScreen").style.display = "flex";
|
||||||
|
|
||||||
|
saveScoreToLeaderboard(total); // ⬅ SIMPAN SKOR
|
||||||
|
}
|
||||||
|
|
||||||
|
// === KLIK TOMBOL LEADERBOARD KE HALAMAN LEADERBOARD ===
|
||||||
|
document.querySelector(".leaderboard").addEventListener("click", () => {
|
||||||
|
window.location.href = "leaderboard.html";
|
||||||
|
});
|
||||||
|
|
||||||
const images = [
|
const images = [
|
||||||
"images/fruit1.png",
|
"images/fruit1.png",
|
||||||
"images/fruit2.png",
|
"images/fruit2.png",
|
||||||
|
|||||||
@ -200,6 +200,16 @@ body {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gameboard {
|
||||||
|
flex: 1;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
padding: 20px 50px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.play-again { background: #b700ff; color: white; }
|
.play-again { background: #b700ff; color: white; }
|
||||||
.leaderboard { background: gold; }
|
.leaderboard { background: gold; }
|
||||||
.back-menu { background: #444; color: white; }
|
.back-menu { background: #444; color: white; }
|
||||||
|
|||||||
@ -7,8 +7,6 @@ body {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
/* Pink → Ungu Gradient */
|
|
||||||
background: linear-gradient(135deg, #ff9ed1, #d784ff);
|
background: linear-gradient(135deg, #ff9ed1, #d784ff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
109
leaderboard.css
Normal file
109
leaderboard.css
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: Poppins, Arial, sans-serif;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background: linear-gradient(135deg, #ff9ed1, #d784ff);
|
||||||
|
}
|
||||||
|
/* animasi fruit */
|
||||||
|
.fruit {
|
||||||
|
position: absolute;
|
||||||
|
width: 95px;
|
||||||
|
opacity: 0.85;
|
||||||
|
animation: float 7s infinite ease-in-out;
|
||||||
|
pointer-events: none;
|
||||||
|
filter: drop-shadow(0 4px 6px rgba(0,0,0,0.12));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Posisi lebih seimbang & tidak menutupi teks */
|
||||||
|
.f1 { top: 6%; left: 12%; animation-delay: .3s; }
|
||||||
|
.f2 { top: 12%; right: 18%; animation-delay: 1.2s; }
|
||||||
|
.f3 { top: 48%; right: 8%; animation-delay: .7s; }
|
||||||
|
.f4 { top: 72%; left: 20%; animation-delay: 1.6s; }
|
||||||
|
.f5 { top: 28%; left: 60%; animation-delay: .5s; }
|
||||||
|
.f6 { top: 68%; right: 22%; animation-delay: 1s; }
|
||||||
|
.f7 { top: 40%; left: 10%; animation-delay: .9s; }
|
||||||
|
.f8 { top: 82%; left: 70%; animation-delay: 1.4s; }
|
||||||
|
.f9 { top: 18%; right: 46%; animation-delay: .6s; }
|
||||||
|
.f10 { top: 80%; right: 10%; animation-delay: 1.8s; }
|
||||||
|
|
||||||
|
/* Smooth floating animation */
|
||||||
|
@keyframes float {
|
||||||
|
0% { transform: translateY(0) rotate(0deg); }
|
||||||
|
50% { transform: translateY(-18px) rotate(6deg); }
|
||||||
|
100% { transform: translateY(0) rotate(0deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes drift {
|
||||||
|
0% { transform: translateX(0); }
|
||||||
|
50% { transform: translateX(22px); }
|
||||||
|
100% { transform: translateX(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.fruit {
|
||||||
|
width: clamp(70px, 7vw, 105px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* leaderboard */
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
.rank-up {
|
||||||
|
animation: rankUp 0.6s ease;
|
||||||
|
background: #d2ffdf !important; /* hijau */
|
||||||
|
}
|
||||||
|
|
||||||
|
.rank-down {
|
||||||
|
animation: rankDown 0.6s ease;
|
||||||
|
background: #ffd2d2 !important; /* merah */
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rankUp {
|
||||||
|
from { transform: translateY(10px); opacity: 0.4; }
|
||||||
|
to { transform: translateY(0); opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rankDown {
|
||||||
|
from { transform: translateY(-10px); opacity: 0.4; }
|
||||||
|
to { transform: translateY(0); opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rankFlashUp {
|
||||||
|
0% { background: rgba(0,255,0,0.7); }
|
||||||
|
100% { background: rgba(0,255,0,0.2); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rankFlashDown {
|
||||||
|
0% { background: rgba(255,0,0,0.7); }
|
||||||
|
100% { background: rgba(255,0,0,0.2); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Difficulty Tabs */
|
||||||
|
.difficulty-tabs {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.difficulty-tabs button {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: #d2b6ff;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.difficulty-tabs button.active {
|
||||||
|
background: #8e00ff;
|
||||||
|
color: white;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
36
leaderboard.html
Normal file
36
leaderboard.html
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Leaderboard – Memory Card</title>
|
||||||
|
<link rel="stylesheet" href="leaderboard.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="leaderboardPopup" class="leaderboard-popup">
|
||||||
|
<div class="leaderboard-box">
|
||||||
|
<h2>🏆 Leaderboard</h2>
|
||||||
|
<div class="difficulty-tabs">
|
||||||
|
<button onclick="changeDifficulty('easy')" id="tab-easy">Easy</button>
|
||||||
|
<button onclick="changeDifficulty('medium')" id="tab-medium">Medium</button>
|
||||||
|
<button onclick="changeDifficulty('hard')" id="tab-hard">Hard</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Rank</th>
|
||||||
|
<th>Nama</th>
|
||||||
|
<th>Score</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="leaderboard-body"></tbody>
|
||||||
|
</table>
|
||||||
|
<button class="close-leaderboard" onclick="closeLeaderboard()">✖ Tutup</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="leaderboard.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
108
leaderboard.js
Normal file
108
leaderboard.js
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
const leaderboardFile = "leaderboard.json";
|
||||||
|
|
||||||
|
// 🔹 Buka popup leaderboard
|
||||||
|
function openLeaderboard() {
|
||||||
|
document.getElementById("leaderboardPopup").style.display = "flex";
|
||||||
|
loadLeaderboard();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🔹 Tutup popup leaderboard
|
||||||
|
function closeLeaderboard() {
|
||||||
|
document.getElementById("leaderboardPopup").style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 🔹 Muat leaderboard dari JSON
|
||||||
|
async function loadLeaderboard() {
|
||||||
|
const difficulty = localStorage.getItem("difficulty") || "easy";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(leaderboardFile + "?v=" + Date.now());
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
// Ambil list sesuai difficulty
|
||||||
|
let list = data[difficulty] || [];
|
||||||
|
|
||||||
|
// Urutkan score dari paling tinggi
|
||||||
|
list.sort((a, b) => b.score - a.score);
|
||||||
|
|
||||||
|
const tbody = document.getElementById("leaderboard-body");
|
||||||
|
tbody.innerHTML = "";
|
||||||
|
|
||||||
|
newOrder.length = 0;
|
||||||
|
list.forEach((p, i) => {
|
||||||
|
newOrder.push(p.name);
|
||||||
|
|
||||||
|
let movementClass = "";
|
||||||
|
if (previousOrder.length > 0) {
|
||||||
|
const oldIndex = previousOrder.indexOf(p.name);
|
||||||
|
if (oldIndex !== -1) {
|
||||||
|
if (oldIndex > i) movementClass = "rank-up";
|
||||||
|
if (oldIndex < i) movementClass = "rank-down";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody.innerHTML += `
|
||||||
|
<tr class="${movementClass}">
|
||||||
|
<td class="rank">#${i + 1}</td>
|
||||||
|
<td>${p.name}</td>
|
||||||
|
<td>${p.score}</td>
|
||||||
|
</tr>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
highlightTab(difficulty);
|
||||||
|
|
||||||
|
// Simpan posisi lama → jadi perbandingan load berikutnya
|
||||||
|
localStorage.setItem("prev_ranking", JSON.stringify(newOrder));
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
document.getElementById("leaderboard-body").innerHTML =
|
||||||
|
`<tr><td colspan="3">⚠ Gagal memuat leaderboard</td></tr>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 🔹 Menyimpan skor ke leaderboard.json
|
||||||
|
async function saveScore(name, score, difficulty) {
|
||||||
|
const res = await fetch(leaderboardFile);
|
||||||
|
let data = await res.json();
|
||||||
|
let board = data[difficulty];
|
||||||
|
|
||||||
|
const player = board.find(e => e.name.toLowerCase() === name.toLowerCase());
|
||||||
|
|
||||||
|
if (player) {
|
||||||
|
if (score > player.score) player.score = score;
|
||||||
|
} else {
|
||||||
|
board.push({ name, score });
|
||||||
|
}
|
||||||
|
|
||||||
|
await fetch(leaderboardFile, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(data, null, 2),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const difficulty = localStorage.getItem("difficulty") || "easy";
|
||||||
|
localStorage.setItem("playerName", username);
|
||||||
|
|
||||||
|
let previousOrder = JSON.parse(localStorage.getItem("prev_ranking") || "[]");
|
||||||
|
let newOrder = [];
|
||||||
|
|
||||||
|
function highlightTab(diff) {
|
||||||
|
document.querySelectorAll(".difficulty-tabs button").forEach(btn => btn.classList.remove("active"));
|
||||||
|
document.getElementById("tab-" + diff).classList.add("active");
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeDifficulty(diff) {
|
||||||
|
localStorage.setItem("difficulty", diff);
|
||||||
|
previousOrder = JSON.parse(localStorage.getItem("prev_ranking")) || [];
|
||||||
|
loadLeaderboard();
|
||||||
|
highlightTab(diff);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
5
leaderboard.json
Normal file
5
leaderboard.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"easy": [],
|
||||||
|
"medium": [],
|
||||||
|
"hard": []
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user