This commit is contained in:
Evelyn 2025-12-15 01:27:20 +07:00
parent 19d3eb26cd
commit 6957a5f9d9
7 changed files with 9 additions and 331 deletions

View File

@ -217,8 +217,6 @@ body {
cursor: pointer;
}
.play-again { background: #b700ff; color: white; }
.leaderboard { background: gold; }
.back-menu { background: #444; color: white; }
@ -281,45 +279,6 @@ body {
</div>
<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 = [
"images/fruit1.png",
"images/fruit2.png",

View File

@ -19,7 +19,7 @@ body {
flex-direction: column;
}
/* TOP UI */
.topbar {
display: flex;
justify-content: space-between;
@ -56,7 +56,7 @@ body {
box-shadow: 0 3px 6px rgba(0,0,0,0.2), inset 0 0 6px rgba(255,255,255,0.6);
}
/* === 5×4 GRID === */
.gameboard {
flex: 1;
display: grid;
@ -67,7 +67,7 @@ body {
box-sizing: border-box;
}
/* === CARDS === */
.card {
width: 100%;
height: 100%;
@ -84,7 +84,7 @@ body {
border-radius: 10px;
}
/* Face */
.front, .back {
position: absolute;
width: 100%;
@ -116,7 +116,7 @@ body {
filter: drop-shadow(0 3px 4px rgba(0,0,0,0.45));
}
/* Hover & Flip */
.card:not(.flipped):hover .front {
transform: scale(1.05);
transition: 0.25s;
@ -124,14 +124,14 @@ body {
.card.flipped .inner { transform: rotateY(180deg); }
/* Match Shine */
.card.matched .front,
.card.matched .back {
border-color: #7affd6;
box-shadow: 0 0 15px #7affd6, 0 0 30px rgba(122,255,214,0.8);
}
/* === COMBO POPUP === */
.combo-popup {
position: absolute;
padding: 12px 20px;
@ -200,16 +200,6 @@ body {
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; }
.leaderboard { background: gold; }
.back-menu { background: #444; color: white; }
@ -232,7 +222,6 @@ body {
<div class="end-box">
<div class="end-title">🎉 Selamat!</div>
<p>Anda berhasil menyelesaikan permainan!</p>
<div class="score-row"><span>Skor Base:</span> <span id="baseScoreEnd">0</span></div>
<div class="score-row"><span>Time Bonus:</span> <span id="timeBonusEnd">0</span></div>
<div class="score-row"><span>Move Bonus:</span> <span id="moveBonusEnd">0</span></div>
@ -251,7 +240,7 @@ body {
</div>
<script>
/* === 10 GAMBAR (20 kartu) === */
const images = [
"images/fruit1.png",
"images/fruit2.png",
@ -272,7 +261,6 @@ let time = 60;
let moves = 0;
let score = 0;
let countdown;
let combo = 1;
let pendingMatch = false;
let lastMatchTime = 0;
@ -292,7 +280,6 @@ function showComboPopup(targetCard, combo, bonus) {
setTimeout(() => popup.remove(), 1500);
}
function shuffleDistant(cards, minDistance = 4) {
let valid = false;
let result;

View File

@ -19,7 +19,6 @@ body {
flex-direction: column;
}
/* TOP UI */
.topbar {
display: flex;
justify-content: space-between;
@ -56,7 +55,6 @@ body {
box-shadow: 0 3px 6px rgba(0,0,0,0.2), inset 0 0 6px rgba(255,255,255,0.6);
}
/* === 4×4 GRID === */
.gameboard {
flex: 1;
display: grid;
@ -67,7 +65,6 @@ body {
box-sizing: border-box;
}
/* === CARDS === */
.card {
width: 100%;
height: 100%;
@ -84,7 +81,6 @@ body {
border-radius: 10px;
}
/* Face */
.front, .back {
position: absolute;
width: 100%;
@ -116,7 +112,6 @@ body {
filter: drop-shadow(0 3px 4px rgba(0,0,0,0.45));
}
/* Hover & Flip */
.card:not(.flipped):hover .front {
transform: scale(1.05);
transition: 0.25s;
@ -124,14 +119,12 @@ body {
.card.flipped .inner { transform: rotateY(180deg); }
/* Match Shine */
.card.matched .front,
.card.matched .back {
border-color: #7affd6;
box-shadow: 0 0 15px #7affd6, 0 0 30px rgba(122,255,214,0.8);
}
/* === COMBO POPUP === */
.combo-popup {
position: absolute;
padding: 12px 20px;
@ -248,7 +241,6 @@ body {
<div class="end-box">
<div class="end-title">🎉 Selamat!</div>
<p>Anda berhasil menyelesaikan permainan!</p>
<div class="score-row"><span>Skor Base:</span> <span id="baseScoreEnd">0</span></div>
<div class="score-row"><span>Time Bonus:</span> <span id="timeBonusEnd">0</span></div>
<div class="score-row"><span>Move Bonus:</span> <span id="moveBonusEnd">0</span></div>
@ -267,7 +259,7 @@ body {
</div>
<script>
/* === 8 GAMBAR (16 kartu) === */
const images = [
"images/fruit1.png",
"images/fruit2.png",
@ -286,7 +278,6 @@ let time = 60;
let moves = 0;
let score = 0;
let countdown;
let combo = 1;
let pendingMatch = false;
let lastMatchTime = 0;
@ -306,7 +297,6 @@ function showComboPopup(targetCard, combo, bonus) {
setTimeout(() => popup.remove(), 1500);
}
function shuffleDistant(cards, minDistance = 4) {
let valid = false;
let result;

View File

@ -1,109 +0,0 @@
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);
}

View File

@ -1,36 +0,0 @@
<!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>

View File

@ -1,108 +0,0 @@
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);
}

View File

@ -1,5 +0,0 @@
{
"easy": [],
"medium": [],
"hard": []
}