1. Project Overview & Features
The Memory Card Matching Game is an essential frontend project designed to help you bridge the gap between learning JavaScript syntax and building interactive, responsive web applications.
Key Features Implemented:
- 4x4 grid of 16 shuffled emoji cards
- 3D card flip transitions
- Real-time timer and move counter
- Auto victory detection with congratulations summary
2. Step-by-Step Code Walkthrough
A Step 1: Semantic HTML Structure
We structure the interface using clean, semantic HTML elements. This ensures maximum accessibility and provides intuitive hooks for CSS classes and JavaScript query selectors.
<div class="memory-card-game">
<div class="stats-bar">
<div class="stat-box">Moves: <span id="moves-count">0</span></div>
<div class="stat-box">Time: <span id="time-count">00:00</span></div>
<button id="restart-memory-btn" class="btn-restart-sm"><i class="fas fa-redo"></i></button>
</div>
<div class="memory-grid" id="memory-grid"></div>
<div id="win-modal" class="win-modal hidden">
<div class="win-content">
<h2>🎉 Congratulations!</h2>
<p id="win-summary">You solved the puzzle in 14 moves and 42 seconds.</p>
<button id="modal-play-again" class="btn-play-again">Play Again</button>
</div>
</div>
</div>B Step 2: Styling & Responsive CSS
Modern CSS flexbox and grid layouts are used to make the UI look polished, centered, and responsive across desktop, tablet, and mobile screens.
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
body {
background: #0f172a;
color: #f8fafc;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.memory-card-game {
background: #1e293b;
border: 1px solid #334155;
border-radius: 24px;
padding: 24px;
width: 100%;
max-width: 440px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4);
position: relative;
}
.stats-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.stat-box {
background: #0f172a;
padding: 8px 16px;
border-radius: 10px;
font-weight: 600;
font-size: 0.9rem;
}
.btn-restart-sm {
background: #a855f7;
color: #ffffff;
border: none;
padding: 8px 14px;
border-radius: 8px;
cursor: pointer;
}
.memory-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
perspective: 800px;
}
.m-card {
height: 80px;
position: relative;
transform-style: preserve-3d;
transition: transform 0.4s;
cursor: pointer;
}
.m-card.flipped {
transform: rotateY(180deg);
}
.card-face {
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
}
.card-front {
background: #0f172a;
border: 2px solid #334155;
color: #a855f7;
}
.card-back {
background: #334155;
border: 2px solid #a855f7;
transform: rotateY(180deg);
}
.m-card.matched .card-back {
background: #065f46;
border-color: #10b981;
}
.win-modal {
position: absolute;
inset: 0;
background: rgba(15, 23, 42, 0.95);
border-radius: 24px;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
padding: 20px;
}
.win-modal.hidden { display: none; }
.btn-play-again {
background: #a855f7;
color: #ffffff;
border: none;
padding: 12px 24px;
border-radius: 10px;
font-weight: 700;
cursor: pointer;
margin-top: 16px;
}C Step 3: JavaScript Logic & Event Handling
Here is the complete JavaScript code handling the state, event listeners, mathematical logic, and dynamic DOM rendering:
const emojis = ['🚀', '🍕', '🎮', '💎', '🦄', '🎧', '⚡', '🔥'];
let cards = [...emojis, ...emojis];
let flippedCards = [];
let matchedCount = 0;
let moves = 0;
let timeSec = 0;
let timer = null;
let lockBoard = false;
const grid = document.getElementById('memory-grid');
const movesEl = document.getElementById('moves-count');
const timeEl = document.getElementById('time-count');
const winModal = document.getElementById('win-modal');
const winSummary = document.getElementById('win-summary');
const restartBtn = document.getElementById('restart-memory-btn');
const modalPlayAgain = document.getElementById('modal-play-again');
function shuffle(arr) {
return arr.sort(() => Math.random() - 0.5);
}
function initGame() {
grid.innerHTML = '';
cards = shuffle([...emojis, ...emojis]);
flippedCards = [];
matchedCount = 0;
moves = 0;
timeSec = 0;
lockBoard = false;
movesEl.textContent = '0';
timeEl.textContent = '00:00';
winModal.classList.add('hidden');
clearInterval(timer);
timer = setInterval(() => {
timeSec++;
const m = String(Math.floor(timeSec / 60)).padStart(2, '0');
const s = String(timeSec % 60).padStart(2, '0');
timeEl.textContent = `${m}:${s}`;
}, 1000);
cards.forEach(emoji => {
const card = document.createElement('div');
card.className = 'm-card';
card.dataset.emoji = emoji;
card.innerHTML = `
<div class="card-face card-front"><i class="fas fa-question"></i></div>
<div class="card-face card-back">${emoji}</div>
`;
card.addEventListener('click', flipCard);
grid.appendChild(card);
});
}
function flipCard() {
if (lockBoard || this === flippedCards[0] || this.classList.contains('matched')) return;
this.classList.add('flipped');
flippedCards.push(this);
if (flippedCards.length === 2) {
moves++;
movesEl.textContent = moves;
checkMatch();
}
}
function checkMatch() {
const [c1, c2] = flippedCards;
const isMatch = c1.dataset.emoji === c2.dataset.emoji;
if (isMatch) {
c1.classList.add('matched');
c2.classList.add('matched');
matchedCount += 2;
flippedCards = [];
if (matchedCount === cards.length) {
clearInterval(timer);
setTimeout(() => {
winSummary.textContent = `You solved the puzzle in ${moves} moves and ${timeSec} seconds!`;
winModal.classList.remove('hidden');
}, 500);
}
} else {
lockBoard = true;
setTimeout(() => {
c1.classList.remove('flipped');
c2.classList.remove('flipped');
flippedCards = [];
lockBoard = false;
}, 900);
}
}
restartBtn.addEventListener('click', initGame);
modalPlayAgain.addEventListener('click', initGame);
initGame();3. Core JavaScript Concepts You Learned
Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.
Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.
Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.
Crucial concept utilized to power the dynamic state, user interactions, and styling of this project.
4. How to Run Locally on Your Computer
- Download the project ZIP: Click the Download Project (.ZIP) button at the top or in the live runner to save
memory-game-project.zip. - Extract the archive: Unzip the downloaded file. Inside you will find separate
index.html,style.css,script.js,index-standalone.html, and a comprehensiveREADME.md. - Open in any browser: Simply double-click
index.html(orindex-standalone.html) to open and test immediately in Chrome, Firefox, Safari, or Edge. - Edit in Visual Studio Code: Open the extracted folder in VS Code. Right-click
index.htmland select "Open with Live Server" to enjoy instant live reloading as you modify HTML, CSS, or JS!
Bonus Challenges for Learners
Ready to level up? Try adding these extra features on your own:
- Add sound effects or audio feedback for user button clicks.
- Add custom theme color customizer with CSS variables.
- Export the results to PDF or copy as a formatted text summary.