feat: Adding audio player + music to play

This commit is contained in:
gauvainboiche
2026-10-25 22:35:53 +01:00
parent 7a525d084a
commit dd4714f499
39 changed files with 850 additions and 81 deletions
+33 -5
View File
@@ -8,6 +8,7 @@ import { encodeSave, decodeSave, migrate } from "../save/codec.js";
import { KEYS, SLOT_COUNT, readSave, writeSave } from "../save/storage.js";
import { $, esc } from "./desk.js";
import { renderMarkdown } from "./markdown.js";
import { MIX_GROUPS } from "./audio.js";
const ARM_MS = 4000; // how long a "Sure?" button stays armed
@@ -221,9 +222,7 @@ export function createMenu(root, { content, storage, getState, getSettings, hand
<input type="checkbox" id="setting-sound" ${s.sound ? "checked" : ""}>
<span><b>${t("settings.sound")}</b><small>${t("settings.soundHint")}</small></span>
</label>
<label class="menu__field">${t("settings.volume")}
<input type="range" id="setting-volume" min="0" max="1" step="0.05" value="${s.volume}" ${s.sound ? "" : "disabled"}>
</label>
<div class="menu__list">${button("mixer", t("settings.mixer"))}</div>
${storage.persistent ? "" : `<p class="menu__msg menu__msg--error">${t("settings.storage")}</p>`}
${back()}`;
}
@@ -253,7 +252,26 @@ export function createMenu(root, { content, storage, getState, getSettings, hand
return `<div class="rules" tabindex="0" aria-label="${t("rules.title")}">${html}</div>${back()}`;
}
const SCREENS = { home, new: newGame, load, save, export: exportScreen, import: importScreen, settings, credits, rules };
// One slider per sound group, plus the overall volume and the music. Levels apply as you drag.
function mixer() {
const s = getSettings();
const rows = [["master", s.volume], ["music", s.mix.music], ...MIX_GROUPS.map((g) => [g, s.mix[g]])];
return `
<h3>${t("mixer.title")}</h3>
<p class="menu__sub">${t("mixer.hint")}</p>
<div class="mixer">
${rows.map(([key, v]) => `
<label class="mixer__row ${key === "master" ? "mixer__row--master" : ""}">
<span>${t("mixer." + key)}</span>
<input type="range" min="0" max="1" step="0.05" value="${v}" data-mix="${key}" aria-label="${t("mixer." + key)}">
<output>${Math.round(v * 100)}%</output>
</label>`).join("")}
</div>
<div class="menu__list">${button("mixerReset", t("mixer.reset"))}</div>
<footer class="menu__foot">${button("settings", t("menu.back"))}</footer>`;
}
const SCREENS = { home, new: newGame, load, save, export: exportScreen, import: importScreen, settings, credits, rules, mixer };
function render() {
root.innerHTML = `
@@ -324,6 +342,8 @@ export function createMenu(root, { content, storage, getState, getSettings, hand
import: () => go("import"),
settings: () => go("settings"),
rules: () => go("rules"),
mixer: () => go("mixer"),
mixerReset: () => { handlers.onSettings({ resetMix: true }); render(); },
credits: () => go("credits"),
back: () => go("home"),
resume: () => handlers.onResume(),
@@ -362,10 +382,18 @@ export function createMenu(root, { content, storage, getState, getSettings, hand
ACTIONS[action](arg);
});
root.addEventListener("input", (e) => {
const key = e.target.dataset?.mix;
if (!key) return;
const v = Number(e.target.value);
e.target.parentElement.querySelector("output").textContent = `${Math.round(v * 100)}%`;
handlers.onSettings(key === "master" ? { volume: v } : { mix: { [key]: v } });
});
root.addEventListener("change", async (e) => {
if (e.target.dataset?.mix) handlers.onPreview?.(e.target.dataset.mix);
if (e.target.id === "setting-slow") handlers.onSettings({ slow: e.target.checked });
if (e.target.id === "setting-sound") { handlers.onSettings({ sound: e.target.checked }); render(); }
if (e.target.id === "setting-volume") handlers.onSettings({ volume: Number(e.target.value) });
if (e.target.id === "import-file" && e.target.files[0]) {
$(root, "#import-text").value = await e.target.files[0].text();
}