Kelompok02-Memory-Card/gameboard-medium.html

413 lines
9.2 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Memory Card Premium 4x4</title>
<style>
html, body {
margin: 0;
height: 100%;
overflow: hidden;
font-family: "Poppins", sans-serif;
}
body {
background: linear-gradient(135deg, #8929ff 30%, #ff419b 100%);
display: flex;
flex-direction: column;
}
/* TOP UI */
.topbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 14px;
background: rgb(255, 255, 255);
border-bottom: 2px solid rgba(39, 35, 35, 0.6);
backdrop-filter: blur(14px);
z-index: 50;
}
.back-btn {
font-size: 28px;
background: none;
border: none;
cursor: pointer;
color: #222;
}
.right-info {
display: flex;
gap: 10px;
}
.pill {
display: flex;
align-items: center;
gap: 6px;
padding: 5px 12px;
background: white;
border-radius: 20px;
border: 2px solid rgba(255,255,255,0.8);
font-weight: 600;
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;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(4, 1fr);
gap: 8px;
padding: 10px;
box-sizing: border-box;
}
/* === CARDS === */
.card {
width: 100%;
height: 100%;
perspective: 1000px;
cursor: pointer;
}
.inner {
width: 100%;
height: 100%;
position: relative;
transform-style: preserve-3d;
transition: transform 0.55s ease;
border-radius: 10px;
}
/* Face */
.front, .back {
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden;
display: flex;
justify-content: center;
align-items: center;
border-radius: 10px;
background: linear-gradient(135deg, #7a28ff 20%, #eb2bbf 80%);
border: 2px solid rgba(255,255,255,0.55);
box-shadow: 0 3px 6px rgba(0,0,0,0.25);
}
.front {
font-size: calc(10px + 2vw);
font-weight: 700;
color: #ff6a4d;
}
.back {
transform: rotateY(180deg);
}
.back img {
width: 85%;
height: 85%;
object-fit: contain;
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;
}
.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;
background: linear-gradient(135deg, rgba(255,95,109,0.92), rgba(255,0,102,0.92));
border-radius: 14px;
color: white;
font-weight: 800;
text-align: center;
font-size: 20px;
box-shadow: 0 0 18px rgba(255, 75, 43, 0.9), 0 0 35px rgba(255, 0, 85, 0.7);
animation: comboFade 1.4s ease forwards;
pointer-events: none;
border: 2px solid rgba(255,255,255,0.35);
}
@keyframes comboFade {
0% { opacity: 0; transform: translate(-50%, -10px) scale(0.7); }
20% { opacity: 1; transform: translate(-50%, -30px) scale(1); }
100% { opacity: 0; transform: translate(-50%, -80px) scale(1.1); }
}
.end-screen {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.45);
backdrop-filter: blur(7px);
display: none;
justify-content: center;
align-items: center;
z-index: 200;
}
.end-box {
width: 80%;
max-width: 380px;
background: white;
border-radius: 22px;
padding: 20px;
text-align: center;
}
.end-title {
font-size: 28px;
font-weight: 700;
margin-bottom: 4px;
}
.score-row {
display: flex;
justify-content: space-between;
margin: 8px 0;
font-size: 18px;
}
.end-btn {
width: 100%;
padding: 12px;
border-radius: 16px;
border: none;
font-size: 18px;
margin-top: 10px;
cursor: pointer;
font-weight: 600;
}
.play-again { background: #b700ff; color: white; }
.leaderboard { background: gold; }
.back-menu { background: #444; color: white; }
</style>
</head>
<body>
<header class="topbar">
<button class="back-btn" onclick="window.location.href='mainboard.html'"></button>
<div class="right-info">
<div class="pill"><span class="icon"></span><span id="timer">60</span>s</div>
<div class="pill"><span class="icon"></span><span id="score">0</span></div>
<div class="pill"><span class="icon">🎯</span><span id="moves">0</span></div>
</div>
</header>
<div id="game-board" class="gameboard"></div>
<div id="endScreen" class="end-screen">
<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>
<hr>
<div class="score-row" style="font-weight:700;">
<span>Total Skor:</span>
<span id="totalScoreEnd">0</span>
</div>
<button class="end-btn play-again" onclick="startGame()">🔁 Main Lagi</button>
<button class="end-btn leaderboard">🏆 Lihat Leaderboard</button>
<button class="end-btn back-menu" onclick="window.location.href='mainboard.html'">⬅ Kembali</button>
</div>
</div>
<script>
/* === 8 GAMBAR (16 kartu) === */
const images = [
"asset/alpukat.jpg",
"asset/anggur.jpg",
"asset/apel.jpg",
"asset/buah naga.jpg",
"asset/jambu.jpg",
"asset/jeruk.jpg",
"asset/lemon.jpg",
"asset/nanas.jpg"
];
let cards = [...images, ...images];
let flipped = [];
let timerStarted = false;
let time = 60;
let moves = 0;
let score = 0;
let countdown;
let combo = 1;
let pendingMatch = false;
let lastMatchTime = 0;
function showComboPopup(targetCard, combo, bonus) {
const popup = document.createElement("div");
popup.className = "combo-popup";
popup.innerHTML = `
COMBO X${combo}<br>
<span style="font-size:14px;">+${bonus} Bonus</span>
`;
const rect = targetCard.getBoundingClientRect();
popup.style.left = rect.left + rect.width / 2 + "px";
popup.style.top = rect.top + "px";
popup.style.position = "fixed";
document.body.appendChild(popup);
setTimeout(() => popup.remove(), 1500);
}
function shuffleDistant(cards, minDistance = 4) {
let valid = false;
let result;
while (!valid) {
result = [...cards].sort(() => Math.random() - 0.5);
valid = true;
const checked = new Set();
for (let i = 0; i < result.length; i++) {
const value = result[i];
if (checked.has(value)) continue;
checked.add(value);
const firstIndex = result.indexOf(value);
const lastIndex = result.lastIndexOf(value);
const distance = Math.abs(lastIndex - firstIndex);
if (distance < minDistance) {
valid = false;
break;
}
}
}
return result;
}
function startTimer() {
timerStarted = true;
countdown = setInterval(() => {
document.getElementById("timer").textContent = --time;
if (time <= 0) {
clearInterval(countdown);
document.getElementById("endScreen").style.display = "flex";
}
}, 1000);
}
function flipCard(card) {
if (!timerStarted) startTimer();
if (flipped.length === 2 || card.classList.contains("matched") || card.classList.contains("flipped"))
return;
card.classList.add("flipped");
flipped.push(card);
if (flipped.length === 2) {
moves++;
document.getElementById("moves").textContent = moves;
let img1 = flipped[0].querySelector("img").src;
let img2 = flipped[1].querySelector("img").src;
if (img1 === img2) {
const now = Date.now();
if (!pendingMatch) {
pendingMatch = true;
combo = 1;
} else {
if (now - lastMatchTime <= 3000) {
combo++;
let comboBonus = combo * 10;
score += comboBonus;
showComboPopup(flipped[0], combo, comboBonus);
} else combo = 1;
}
lastMatchTime = now;
flipped.forEach(c => c.classList.add("matched"));
score += 50;
document.getElementById("score").textContent = score;
time += 5;
document.getElementById("timer").textContent = time;
flipped = [];
if (document.querySelectorAll(".matched").length === cards.length) {
setTimeout(() => document.getElementById("endScreen").style.display = "flex", 500);
}
} else {
setTimeout(() => {
flipped.forEach(c => c.classList.remove("flipped"));
flipped = [];
}, 800);
}
}
}
function startGame() {
document.getElementById("endScreen").style.display = "none";
const board = document.getElementById("game-board");
board.innerHTML = "";
shuffleDistant(cards, 4).forEach(image => {
const card = document.createElement("div");
card.className = "card";
card.innerHTML = `
<div class="inner">
<div class="front">?</div>
<div class="back"><img src="${image}"></div>
</div>
`;
card.onclick = () => flipCard(card);
board.appendChild(card);
});
time = 60;
moves = 0;
score = 0;
combo = 1;
pendingMatch = false;
flipped = [];
timerStarted = false;
document.getElementById("timer").textContent = time;
document.getElementById("moves").textContent = moves;
document.getElementById("score").textContent = score;
}
startGame();
</script>
</body>
</html>