offerpk3's picture
Update index.html
670c460 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sea Creatures Memory Match</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Arial', sans-serif;
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: white;
padding: 20px;
}
.game-header {
text-align: center;
margin-bottom: 30px;
}
.game-title {
font-size: 2.5rem;
margin-bottom: 10px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
background: linear-gradient(45deg, #00d4ff, #ffffff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.game-stats {
display: flex;
gap: 30px;
justify-content: center;
margin-bottom: 20px;
font-size: 1.2rem;
}
.stat-item {
background: rgba(255,255,255,0.1);
padding: 10px 20px;
border-radius: 25px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255,255,255,0.2);
}
.game-board {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 15px;
max-width: 600px;
margin: 0 auto;
}
.card {
width: 120px;
height: 120px;
background: linear-gradient(145deg, #3498db, #2980b9);
border-radius: 15px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
transform-style: preserve-3d;
position: relative;
box-shadow: 0 8px 20px rgba(0,0,0,0.2);
border: 2px solid rgba(255,255,255,0.1);
}
.card:hover {
transform: translateY(-5px) scale(1.02);
box-shadow: 0 12px 25px rgba(0,0,0,0.3);
}
.card.flipped {
background: linear-gradient(145deg, #ffffff, #f0f0f0);
color: #2c3e50;
transform: rotateY(180deg);
}
.card.matched {
background: linear-gradient(145deg, #2ecc71, #27ae60);
color: white;
animation: matchPulse 0.6s ease;
}
.card-content {
font-size: 3rem;
transition: opacity 0.3s ease;
}
.card:not(.flipped) .card-content {
opacity: 0;
}
.card.flipped .card-content,
.card.matched .card-content {
opacity: 1;
}
.card-back {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
color: rgba(255,255,255,0.8);
transition: opacity 0.3s ease;
}
.card.flipped .card-back,
.card.matched .card-back {
opacity: 0;
}
@keyframes matchPulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.1); }
}
.controls {
margin-top: 30px;
text-align: center;
}
.btn {
background: linear-gradient(145deg, #e74c3c, #c0392b);
color: white;
border: none;
padding: 12px 30px;
border-radius: 25px;
font-size: 1.1rem;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(231, 76, 60, 0.3);
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(231, 76, 60, 0.4);
}
.victory-message, .level-complete-message {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: linear-gradient(145deg, #2ecc71, #27ae60);
color: white;
padding: 30px;
border-radius: 20px;
text-align: center;
font-size: 1.5rem;
box-shadow: 0 20px 40px rgba(0,0,0,0.3);
opacity: 0;
visibility: hidden;
transition: all 0.5s ease;
z-index: 1000;
}
.level-complete-message {
background: linear-gradient(145deg, #3498db, #2980b9);
}
.victory-message.show, .level-complete-message.show {
opacity: 1;
visibility: visible;
animation: victoryBounce 0.6s ease;
}
.next-level-btn {
background: linear-gradient(145deg, #f39c12, #e67e22);
color: white;
border: none;
padding: 12px 30px;
border-radius: 25px;
font-size: 1.1rem;
cursor: pointer;
transition: all 0.3s ease;
margin-top: 15px;
}
.next-level-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(243, 156, 18, 0.4);
}
.how-to-play {
background: rgba(255,255,255,0.1);
backdrop-filter: blur(15px);
border: 1px solid rgba(255,255,255,0.2);
border-radius: 20px;
padding: 25px;
margin: 20px auto;
max-width: 600px;
text-align: left;
transition: all 0.5s ease;
}
.how-to-play.hidden {
opacity: 0;
visibility: hidden;
transform: translateY(-20px);
}
.how-to-play h3 {
text-align: center;
margin-bottom: 20px;
font-size: 1.5rem;
color: #00d4ff;
}
.how-to-play ul {
list-style: none;
padding: 0;
}
.how-to-play li {
margin: 12px 0;
padding: 8px 0;
font-size: 1.1rem;
line-height: 1.4;
}
.close-guide {
background: linear-gradient(145deg, #00d4ff, #0099cc);
color: white;
border: none;
padding: 10px 25px;
border-radius: 20px;
font-size: 1rem;
cursor: pointer;
display: block;
margin: 20px auto 0;
transition: all 0.3s ease;
}
.close-guide:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0, 153, 204, 0.4);
}
.sound-controls {
margin: 20px 0;
}
.sound-panel {
background: rgba(255,255,255,0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255,255,255,0.2);
border-radius: 15px;
padding: 20px;
display: flex;
gap: 20px;
align-items: center;
justify-content: center;
flex-wrap: wrap;
}
.sound-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
min-width: 120px;
}
.sound-item label {
font-size: 0.9rem;
font-weight: bold;
text-align: center;
}
.sound-toggle {
background: linear-gradient(145deg, #2ecc71, #27ae60);
color: white;
border: none;
padding: 6px 16px;
border-radius: 15px;
font-size: 0.8rem;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
min-width: 50px;
}
.sound-toggle.off {
background: linear-gradient(145deg, #e74c3c, #c0392b);
}
.sound-toggle:hover {
transform: translateY(-1px);
}
.sound-item input[type="range"] {
width: 80px;
height: 6px;
border-radius: 3px;
background: rgba(255,255,255,0.3);
outline: none;
cursor: pointer;
}
.sound-item input[type="range"]::-webkit-slider-thumb {
appearance: none;
width: 16px;
height: 16px;
border-radius: 50%;
background: #00d4ff;
cursor: pointer;
box-shadow: 0 2px 6px rgba(0,0,0,0.3);
}
.sound-item select {
background: rgba(255,255,255,0.1);
color: white;
border: 1px solid rgba(255,255,255,0.3);
border-radius: 8px;
padding: 6px 12px;
font-size: 0.9rem;
cursor: pointer;
}
.sound-item select option {
background: #2980b9;
color: white;
}
.guide-btn {
background: linear-gradient(145deg, #9b59b6, #8e44ad);
margin-left: 15px;
}
.guide-btn:hover {
box-shadow: 0 6px 20px rgba(155, 89, 182, 0.4);
}
@keyframes victoryBounce {
0%, 100% { transform: translate(-50%, -50%) scale(1); }
50% { transform: translate(-50%, -50%) scale(1.05); }
}
@media (max-width: 768px) {
.game-board {
grid-template-columns: repeat(3, 1fr);
gap: 10px;
}
.card {
width: 90px;
height: 90px;
}
.card-content {
font-size: 2rem;
}
.game-title {
font-size: 2rem;
}
.game-stats {
flex-direction: column;
gap: 10px;
}
.sound-panel {
flex-direction: column;
gap: 15px;
}
.how-to-play {
margin: 10px;
padding: 20px;
}
.how-to-play li {
font-size: 1rem;
}
.controls {
flex-direction: column;
gap: 10px;
}
.guide-btn {
margin-left: 0;
}
}
</style>
</head>
<body>
<div class="game-header">
<h1 class="game-title">๐ŸŒŠ Sea Creatures Memory Match ๐ŸŒŠ</h1>
<!-- How to Play Guide -->
<div class="how-to-play" id="howToPlay">
<h3>๐ŸŽฏ How to Play</h3>
<ul>
<li>๐Ÿ–ฑ๏ธ Click any card to flip it and reveal a sea creature</li>
<li>๐Ÿ” Click a second card to find its matching pair</li>
<li>โœ… If they match, they stay revealed with a green glow!</li>
<li>โŒ If they don't match, they flip back - remember their positions!</li>
<li>๐Ÿ† Find all 8 pairs to win the game</li>
<li>โšก Try to complete it in the fewest moves and fastest time!</li>
</ul>
<button class="close-guide" onclick="toggleGuide()">Got it! ๐Ÿš€</button>
</div>
<div class="game-stats">
<div class="stat-item">
<span>Level: </span><span id="level">1</span>
</div>
<div class="stat-item">
<span>Moves: </span><span id="moves">0</span>
</div>
<div class="stat-item">
<span>Matches: </span><span id="matches">0</span>
</div>
<div class="stat-item">
<span>Time: </span><span id="timer">00:00</span>
</div>
</div>
<!-- Sound Control Panel -->
<div class="sound-controls">
<div class="sound-panel">
<div class="sound-item">
<label>๐ŸŽต Background Music</label>
<button class="sound-toggle" id="musicToggle" onclick="toggleMusic()">ON</button>
<input type="range" id="musicVolume" min="0" max="100" value="30" onchange="setMusicVolume(this.value)">
</div>
<div class="sound-item">
<label>๐Ÿ”Š Sound Effects</label>
<button class="sound-toggle" id="sfxToggle" onclick="toggleSFX()">ON</button>
<input type="range" id="sfxVolume" min="0" max="100" value="50" onchange="setSFXVolume(this.value)">
</div>
<div class="sound-item">
<label>โšก Game Speed</label>
<select id="gameSpeed" onchange="setGameSpeed(this.value)">
<option value="fast">Fast</option>
<option value="normal" selected>Normal</option>
<option value="slow">Slow</option>
</select>
</div>
</div>
</div>
</div>
<div class="game-board" id="gameBoard"></div>
<div class="controls">
<button class="btn" onclick="resetGame()">๐Ÿ”„ New Game</button>
<button class="btn guide-btn" onclick="toggleGuide()">โ“ How to Play</button>
</div>
<div class="victory-message" id="victoryMessage">
<h2>๐ŸŽ‰ Congratulations! ๐ŸŽ‰</h2>
<p>You found all the sea creature pairs!</p>
<p id="finalStats"></p>
</div>
<div class="level-complete-message" id="levelCompleteMessage">
<h2>๐ŸŒŸ Level Complete! ๐ŸŒŸ</h2>
<p>Amazing! You've completed this level!</p>
<p id="levelStats"></p>
<button class="btn next-level-btn" onclick="nextLevel()">๐Ÿš€ Next Level</button>
</div>
<script>
const seaCreatures = [
'๐Ÿ ', '๐ŸŸ', '๐Ÿก', '๐Ÿฆˆ',
'๐Ÿ™', '๐Ÿฆ‘', '๐Ÿš', '๐Ÿฆ€',
'๐Ÿ‹', '๐Ÿณ', '๐Ÿฆž', '๐Ÿข',
'๐Ÿฌ', '๐Ÿฆญ', '๐Ÿง', 'โญ'
];
let gameBoard = [];
let flippedCards = [];
let matchedPairs = 0;
let moves = 0;
let startTime = null;
let timerInterval = null;
let gameActive = false;
let gameSpeed = 'normal';
let musicEnabled = true;
let sfxEnabled = true;
let musicVolume = 0.3;
let sfxVolume = 0.5;
let currentLevel = 1;
let totalMatches = 0;
// Audio context and sounds
let audioContext;
let backgroundMusic;
let matchSound;
let flipSound;
// Initialize audio
function initAudio() {
try {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
createBackgroundMusic();
createSoundEffects();
} catch (e) {
console.log('Audio not supported');
}
}
function createBackgroundMusic() {
if (!audioContext) return;
// Stop existing music
if (backgroundMusic && backgroundMusic.oscillators) {
backgroundMusic.oscillators.forEach(osc => {
try { osc.stop(); } catch (e) {}
});
}
// Create a rich, layered ambient soundtrack
const masterGain = audioContext.createGain();
masterGain.gain.setValueAtTime(musicVolume * 0.15, audioContext.currentTime);
masterGain.connect(audioContext.destination);
const oscillators = [];
// Base ocean drone (deep foundation)
const bass = audioContext.createOscillator();
const bassGain = audioContext.createGain();
bass.type = 'sine';
bass.frequency.setValueAtTime(55, audioContext.currentTime); // A1
bassGain.gain.setValueAtTime(0.3, audioContext.currentTime);
bass.connect(bassGain);
bassGain.connect(masterGain);
oscillators.push(bass);
// Harmonic layers (creating depth)
const harmonics = [110, 165, 220, 330]; // A2, E3, A3, E4
harmonics.forEach((freq, i) => {
const osc = audioContext.createOscillator();
const gain = audioContext.createGain();
osc.type = 'triangle';
osc.frequency.setValueAtTime(freq, audioContext.currentTime);
gain.gain.setValueAtTime(0.1 / (i + 1), audioContext.currentTime);
osc.connect(gain);
gain.connect(masterGain);
oscillators.push(osc);
});
// Gentle wave motion (LFO for movement)
const waveLFO = audioContext.createOscillator();
const waveGain = audioContext.createGain();
waveLFO.type = 'sine';
waveLFO.frequency.setValueAtTime(0.3, audioContext.currentTime);
waveGain.gain.setValueAtTime(8, audioContext.currentTime);
waveLFO.connect(waveGain);
waveGain.connect(bass.frequency);
oscillators.push(waveLFO);
// Subtle bubbles effect (higher frequency sparkles)
const bubbles = audioContext.createOscillator();
const bubblesGain = audioContext.createGain();
const bubblesLFO = audioContext.createOscillator();
const bubblesLFOGain = audioContext.createGain();
bubbles.type = 'sine';
bubbles.frequency.setValueAtTime(1760, audioContext.currentTime); // A6
bubblesGain.gain.setValueAtTime(0.02, audioContext.currentTime);
bubblesLFO.type = 'sine';
bubblesLFO.frequency.setValueAtTime(0.7, audioContext.currentTime);
bubblesLFOGain.gain.setValueAtTime(200, audioContext.currentTime);
bubblesLFO.connect(bubblesLFOGain);
bubblesLFOGain.connect(bubbles.frequency);
bubbles.connect(bubblesGain);
bubblesGain.connect(masterGain);
oscillators.push(bubbles, bubblesLFO);
// Distant whale-like calls (very subtle)
const whale = audioContext.createOscillator();
const whaleGain = audioContext.createGain();
const whaleLFO = audioContext.createOscillator();
const whaleLFOGain = audioContext.createGain();
whale.type = 'sawtooth';
whale.frequency.setValueAtTime(80, audioContext.currentTime);
whaleGain.gain.setValueAtTime(0.05, audioContext.currentTime);
whaleLFO.type = 'sine';
whaleLFO.frequency.setValueAtTime(0.1, audioContext.currentTime);
whaleLFOGain.gain.setValueAtTime(20, audioContext.currentTime);
whaleLFO.connect(whaleLFOGain);
whaleLFOGain.connect(whale.frequency);
whale.connect(whaleGain);
whaleGain.connect(masterGain);
oscillators.push(whale, whaleLFO);
backgroundMusic = { oscillators, masterGain };
if (musicEnabled) {
try {
oscillators.forEach(osc => osc.start());
} catch (e) {
console.log('Could not start background music');
}
}
}
function createSoundEffects() {
// Match sound - create a pleasant success sound
matchSound = () => {
if (!audioContext || !sfxEnabled) return;
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
oscillator.type = 'triangle';
oscillator.frequency.setValueAtTime(523.25, audioContext.currentTime); // C5
oscillator.frequency.setValueAtTime(659.25, audioContext.currentTime + 0.1); // E5
oscillator.frequency.setValueAtTime(783.99, audioContext.currentTime + 0.2); // G5
gainNode.gain.setValueAtTime(sfxVolume * 0.3, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5);
oscillator.start(audioContext.currentTime);
oscillator.stop(audioContext.currentTime + 0.5);
};
// Flip sound - subtle click sound
flipSound = () => {
if (!audioContext || !sfxEnabled) return;
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
oscillator.type = 'square';
oscillator.frequency.setValueAtTime(800, audioContext.currentTime);
gainNode.gain.setValueAtTime(sfxVolume * 0.1, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.1);
oscillator.start(audioContext.currentTime);
oscillator.stop(audioContext.currentTime + 0.1);
};
}
function getSpeedDelay() {
const speeds = {
fast: { match: 300, flip: 600 },
normal: { match: 500, flip: 1000 },
slow: { match: 800, flip: 1500 }
};
return speeds[gameSpeed];
}
function initGame() {
// Reset game state
gameBoard = [];
flippedCards = [];
matchedPairs = 0;
moves = 0;
startTime = null;
gameActive = true;
if (timerInterval) {
clearInterval(timerInterval);
}
// Create pairs and shuffle
const creatures = seaCreatures.slice(0, 8); // Use 8 different creatures
const pairs = [...creatures, ...creatures]; // Create pairs
gameBoard = shuffle(pairs);
// Update UI
updateStats();
createBoard();
document.getElementById('victoryMessage').classList.remove('show');
document.getElementById('levelCompleteMessage').classList.remove('show');
}
function shuffle(array) {
const shuffled = [...array];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
return shuffled;
}
function createBoard() {
const board = document.getElementById('gameBoard');
board.innerHTML = '';
gameBoard.forEach((creature, index) => {
const card = document.createElement('div');
card.className = 'card';
card.dataset.index = index;
card.innerHTML = `
<div class="card-back">๐ŸŒŠ</div>
<div class="card-content">${creature}</div>
`;
card.addEventListener('click', () => flipCard(index));
board.appendChild(card);
});
}
function flipCard(index) {
if (!gameActive) return;
const card = document.querySelector(`[data-index="${index}"]`);
// Don't flip if already flipped or matched
if (card.classList.contains('flipped') || card.classList.contains('matched')) {
return;
}
// Start timer on first move
if (!startTime) {
startTimer();
// Initialize audio on first interaction (browser requirement)
if (!audioContext) {
initAudio();
}
}
// Play flip sound
if (flipSound) flipSound();
// Flip the card
card.classList.add('flipped');
flippedCards.push(index);
// Check for match when 2 cards are flipped
if (flippedCards.length === 2) {
moves++;
updateStats();
checkForMatch();
}
}
function checkForMatch() {
const [first, second] = flippedCards;
const firstCard = document.querySelector(`[data-index="${first}"]`);
const secondCard = document.querySelector(`[data-index="${second}"]`);
const delays = getSpeedDelay();
if (gameBoard[first] === gameBoard[second]) {
// Match found!
setTimeout(() => {
// Play match sound
if (matchSound) matchSound();
firstCard.classList.add('matched');
secondCard.classList.add('matched');
firstCard.classList.remove('flipped');
secondCard.classList.remove('flipped');
matchedPairs++;
totalMatches++;
updateStats();
flippedCards = [];
// Check for level completion or game win
if (matchedPairs === 8) {
if (totalMatches >= 14) {
gameWon();
} else {
levelComplete();
}
}
}, delays.match);
} else {
// No match - flip back after delay
setTimeout(() => {
firstCard.classList.remove('flipped');
secondCard.classList.remove('flipped');
flippedCards = [];
}, delays.flip);
}
}
function startTimer() {
startTime = Date.now();
timerInterval = setInterval(updateTimer, 1000);
}
function updateTimer() {
if (!startTime) return;
const elapsed = Math.floor((Date.now() - startTime) / 1000);
const minutes = Math.floor(elapsed / 60).toString().padStart(2, '0');
const seconds = (elapsed % 60).toString().padStart(2, '0');
document.getElementById('timer').textContent = `${minutes}:${seconds}`;
}
function updateStats() {
document.getElementById('level').textContent = currentLevel;
document.getElementById('moves').textContent = moves;
document.getElementById('matches').textContent = `${totalMatches}`;
}
function levelComplete() {
gameActive = false;
clearInterval(timerInterval);
const finalTime = document.getElementById('timer').textContent;
document.getElementById('levelStats').innerHTML =
`Level ${currentLevel} Complete!<br>Time: ${finalTime}<br>Moves: ${moves}<br>Total Matches: ${totalMatches}`;
setTimeout(() => {
document.getElementById('levelCompleteMessage').classList.add('show');
}, 500);
}
function nextLevel() {
currentLevel++;
document.getElementById('levelCompleteMessage').classList.remove('show');
initGame();
}
function gameWon() {
gameActive = false;
clearInterval(timerInterval);
const finalTime = document.getElementById('timer').textContent;
document.getElementById('finalStats').innerHTML =
`๐Ÿ† Game Complete! ๐Ÿ†<br>Total Levels: ${currentLevel}<br>Final Time: ${finalTime}<br>Total Moves: ${moves}<br>Total Matches: ${totalMatches}`;
setTimeout(() => {
document.getElementById('victoryMessage').classList.add('show');
}, 500);
}
function resetGame() {
currentLevel = 1;
totalMatches = 0;
initGame();
}
// Sound and UI control functions
function toggleGuide() {
const guide = document.getElementById('howToPlay');
guide.classList.toggle('hidden');
}
function toggleMusic() {
musicEnabled = !musicEnabled;
const toggle = document.getElementById('musicToggle');
toggle.textContent = musicEnabled ? 'ON' : 'OFF';
toggle.classList.toggle('off', !musicEnabled);
if (backgroundMusic && musicEnabled) {
backgroundMusic.masterGain.gain.setValueAtTime(
musicEnabled ? musicVolume * 0.15 : 0,
audioContext.currentTime
);
}
}
function toggleSFX() {
sfxEnabled = !sfxEnabled;
const toggle = document.getElementById('sfxToggle');
toggle.textContent = sfxEnabled ? 'ON' : 'OFF';
toggle.classList.toggle('off', !sfxEnabled);
}
function setMusicVolume(value) {
musicVolume = value / 100;
if (backgroundMusic && musicEnabled) {
backgroundMusic.masterGain.gain.setValueAtTime(
musicVolume * 0.15,
audioContext.currentTime
);
}
}
function setSFXVolume(value) {
sfxVolume = value / 100;
}
function setGameSpeed(speed) {
gameSpeed = speed;
}
// Initialize the game when page loads
document.addEventListener('DOMContentLoaded', () => {
initGame();
// Hide guide initially
document.getElementById('howToPlay').classList.add('hidden');
});
</script>
</body>
</html>