Memory Match Adventure
by oktaviardi pratama
HTML
<link href="https://fonts.googleapis.com/css2?family=Baloo+2:wght@400;600;700;800&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<!-- Floating background -->
<div class="bg-shapes" id="bgShapes" aria-hidden="true"></div>
<!-- Sound toggle -->
<button class="sound-toggle" id="soundToggle" aria-label="Toggle sound">
<i class="fas fa-volume-high"></i>
</button>
<!-- Toast -->
<div class="toast-msg" id="toast" aria-live="polite"></div>
<!-- ===== HERO SECTION ===== -->
<section class="hero" id="heroSection" role="main">
<!-- Decorative floating emojis -->
<span class="deco" style="top:8%;left:10%;--dur:6s;animation-delay:-1s;">๐ถ</span>
<span class="deco" style="top:15%;right:12%;--dur:8s;animation-delay:-3s;">๐</span>
<span class="deco" style="bottom:20%;left:8%;--dur:7s;animation-delay:-2s;">๐</span>
<span class="deco" style="bottom:12%;right:10%;--dur:9s;animation-delay:-4s;">๐</span>
<span class="deco" style="top:40%;left:5%;--dur:6.5s;animation-delay:-0.5s;">โญ</span>
<span class="deco" style="top:55%;right:6%;--dur:7.5s;animation-delay:-2.5s;">๐จ</span>
<h1 class="hero-title">Memory Match</h1>
<p class="hero-sub">Flip cards, find pairs, and train your brain</p>
<div class="diff-cards" role="radiogroup" aria-label="Select difficulty">
<button class="diff-card diff-easy selected" data-diff="easy" role="radio" aria-checked="true" tabindex="0">
<span class="diff-label">Easy</span>
<span class="diff-desc">4 x 3 Grid</span>
</button>
<button class="diff-card diff-medium" data-diff="medium" role="radio" aria-checked="false" tabindex="0">
<span class="diff-label">Medium</span>
...
CSS
:root {
--sky: #4FC3F7;
--yellow: #FFD54F;
--pink: #FF6B9D;
--green: #66BB6A;
--orange: #FF9800;
--neon-blue: #00E5FF;
--gold: #FFD700;
--bg: #FFF8E1;
--card-back: linear-gradient(135deg, #4FC3F7, #0288D1);
--text: #2D3436;
--text-light: #636E72;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Baloo 2', cursive, sans-serif;
background: var(--bg);
color: var(--text);
overflow-x: hidden;
min-height: 100vh;
}
/* Floating background shapes */
.bg-shapes {
position: fixed; inset: 0;
pointer-events: none; z-index: 0; overflow: hidden;
}
.floating-shape {
position: absolute; border-radius: 50%; opacity: 0.12;
animation: floatShape var(--dur, 15s) ease-in-out infinite;
}
@keyframes floatShape {
0%, 100% { transform: translateY(0) rotate(0deg) scale(1); }
33% { transform: translateY(-35px) rotate(120deg) scale(1.08); }
66% { transform: translateY(-18px) rotate(240deg) scale(0.94); }
}
/* Hero */
.hero {
position: relative; z-index: 1;
display: flex; flex-direction: column; align-items: center;
justify-content: center; min-height: 100vh;
padding: 2rem; text-align: center;
}
.hero.hidden { display: none; }
.hero-title {
font-size: clamp(2.8rem, 8vw, 5.2rem);
font-weight: 800; line-height: 1.1; margin-bottom: 0.3rem;
background: linear-gradient(135deg, var(--sky), var(--green), var(--yellow), var(--pink));
background-size: 300% 300%;
-webkit-background-clip: text; -webkit-text-fill-color:...
JavaScript
/* ==============================
CONFIGURATION & STATE
============================== */
const EMOJIS = [
'๐ถ','๐ฑ','๐ฐ','๐ฆ','๐ป','๐ผ','๐ธ','๐ฆ',
'๐ฏ','๐ฎ','๐ท','๐ฆ','๐','๐','๐','๐',
'๐','๐','๐ฏ','๐จ','๐ฐ','๐','๐ต','๐ช'
];
const DIFFICULTIES = {
easy: { cols: 4, rows: 3, pairs: 6, label: 'Easy' },
medium: { cols: 4, rows: 4, pairs: 8, label: 'Medium' },
hard: { cols: 5, rows: 4, pairs: 10, label: 'Hard' }
};
let state = {
difficulty: 'easy',
cards: [],
flippedIndices: [],
matchedPairs: 0,
totalPairs: 6,
moves: 0,
score: 0,
combo: 0,
timerSeconds: 0,
timerInterval: null,
isLocked: false,
gameStarted: false
};
let audioCtx = null;
let soundEnabled = true;
/* ==============================
AUDIO (Web Audio API)
============================== */
function initAudio() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
}
if (audioCtx.state === 'suspended') audioCtx.resume();
}
function playSound(type) {
if (!audioCtx || !soundEnabled) return;
const now = audioCtx.currentTime;
const masterGain = audioCtx.createGain();
masterGain.connect(audioCtx.destination);
switch (type) {
case 'flip': {
// Short percussive snap
const osc = audioCtx.createOscillator();
const g = audioCtx.createGain();
osc.connect(g); g.connect(masterGain);
osc.type = 'sine';
osc.frequency.setValueAtTime(1200, now);
osc.frequency.exponentialRampToValueAtTime(600, now + 0.08);
g.gain.setValueAtTime(0.12, now);
g.gain.exponentialRampToValueAtTime(0.001, now + 0.08);
osc.start(now); osc.stop(now + 0.08);
break;
}
case 'match': {
// Happy ascending arpeggio (C5-E5-G5)
[523.25, 659.25, 783.99].forEach((freq, i)...