diff --git a/index.php b/index.php
index e0ce76a..b3280b1 100644
--- a/index.php
+++ b/index.php
@@ -13,6 +13,7 @@
+
diff --git a/js/game.js b/js/game.js
index f1b73a0..604f36b 100644
--- a/js/game.js
+++ b/js/game.js
@@ -8,6 +8,36 @@ const leaderboardEl = document.getElementById('leaderboard');
const saveScoreFormEl = document.getElementById('saveScoreForm');
const playerNameEl = document.getElementById('playerName');
const restartBtnEl = document.getElementById('restartBtn');
+const musicToggleEl = document.getElementById('musicToggle');
+
+const MUSIC_STORAGE_KEY = 'dinoLand.musicEnabled';
+
+function resolveInitialMusicEnabled() {
+ try {
+ const raw = window.localStorage.getItem(MUSIC_STORAGE_KEY);
+ if (raw === null) return false;
+ return raw !== '0' && raw !== 'false';
+ } catch (_) {
+ return false;
+ }
+}
+
+function persistMusicEnabled(enabled) {
+ try {
+ window.localStorage.setItem(MUSIC_STORAGE_KEY, enabled ? '1' : '0');
+ } catch (_) {
+ // no-op in restricted contexts
+ }
+}
+
+let musicEnabled = resolveInitialMusicEnabled();
+
+function refreshMusicToggleUi() {
+ if (!musicToggleEl) return;
+ musicToggleEl.textContent = musicEnabled ? '🎵 Music: On' : '🔇 Music: Off';
+ musicToggleEl.setAttribute('aria-pressed', musicEnabled ? 'true' : 'false');
+ musicToggleEl.setAttribute('title', musicEnabled ? 'Disable music' : 'Enable music (first click also unlocks audio)');
+}
const startScreenEl = document.getElementById('startScreen');
const startFormEl = document.getElementById('startForm');
@@ -64,10 +94,22 @@ let gameConfig = { accessMode: 'standard', difficulty: 'medium' };
function ensureAudioCtx() {
if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
- if (audioCtx.state === 'suspended') audioCtx.resume();
+ return audioCtx;
+}
+
+async function resumeAudioContext() {
+ const ctx = ensureAudioCtx();
+ if (ctx.state !== 'suspended') return true;
+ try {
+ await ctx.resume();
+ return true;
+ } catch (_) {
+ return false;
+ }
}
function playSfx(type) {
+ if (!musicEnabled) return;
try {
ensureAudioCtx();
const now = audioCtx.currentTime;
@@ -238,6 +280,22 @@ window.addEventListener('keydown', (e) => {
});
window.addEventListener('keyup', (e) => keys.delete(e.key));
+if (musicToggleEl) {
+ refreshMusicToggleUi();
+ musicToggleEl.addEventListener('click', async (ev) => {
+ ev.preventDefault();
+ ev.stopPropagation();
+
+ if (!musicEnabled) {
+ await resumeAudioContext();
+ }
+
+ musicEnabled = !musicEnabled;
+ persistMusicEnabled(musicEnabled);
+ refreshMusicToggleUi();
+ });
+}
+
startFormEl.addEventListener('submit', (e) => {
e.preventDefault();
const pass = passwordEl.value.trim();
diff --git a/styles.css b/styles.css
index 53b74a5..7e508ed 100644
--- a/styles.css
+++ b/styles.css
@@ -25,6 +25,23 @@ body {
border: 2px solid #1b3a47;
background: #b5ecff;
}
+#musicToggle {
+ position: fixed;
+ right: 16px;
+ bottom: 16px;
+ z-index: 9;
+ width: auto;
+ min-width: 132px;
+ padding: 10px 12px;
+ border-radius: 10px;
+ border: 1px solid rgba(255,255,255,0.24);
+ background: rgba(0,0,0,0.65);
+ color: #fff;
+ font-size: 14px;
+ font-weight: 700;
+ cursor: pointer;
+}
+
#deathScreen,
#startScreen {
position: fixed;