feat: Doing more polishing (M8b)

This commit is contained in:
gauvainboiche
2026-10-25 18:50:17 +01:00
parent c2103c32e7
commit c55e5a2484
34 changed files with 1605 additions and 106 deletions
+31 -1
View File
@@ -7,6 +7,7 @@ import { dateOfDay, restoreState, PRONOUNS } from "../engine/index.js";
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";
const ARM_MS = 4000; // how long a "Sure?" button stays armed
@@ -17,6 +18,7 @@ export function createMenu(root, { content, storage, getState, getSettings, hand
let armed = null; // "action:arg" waiting for a second click
let armTimer = 0;
let exportText = "";
const rulesCache = {}; // language -> rendered html, or "error"
const say = (text, kind = "ok") => { message = { text, kind }; };
const go = (next) => { screen = next; message = null; disarm(); render(); };
@@ -85,6 +87,7 @@ export function createMenu(root, { content, storage, getState, getSettings, hand
${button("load", t("menu.load"))}
${button("export", t("menu.export"))}
${button("import", t("menu.import"))}
${button("rules", t("menu.rules"))}
${button("settings", t("menu.settings"))}
${button("quit", t("menu.quit"))}
</div>`;
@@ -100,6 +103,7 @@ export function createMenu(root, { content, storage, getState, getSettings, hand
${button("new", t("menu.new"))}
${button("load", t("menu.load"))}
${button("import", t("menu.import"))}
${button("rules", t("menu.rules"))}
${button("settings", t("menu.settings"))}
${button("credits", t("menu.credits"))}
</div>`;
@@ -213,6 +217,13 @@ export function createMenu(root, { content, storage, getState, getSettings, hand
<input type="checkbox" id="setting-slow" ${s.slow ? "checked" : ""}>
<span><b>${t("settings.slow")}</b><small>${t("settings.slowHint")}</small></span>
</label>
<label class="menu__field menu__check">
<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>
${storage.persistent ? "" : `<p class="menu__msg menu__msg--error">${t("settings.storage")}</p>`}
${back()}`;
}
@@ -226,7 +237,23 @@ export function createMenu(root, { content, storage, getState, getSettings, hand
${back()}`;
}
const SCREENS = { home, new: newGame, load, save, export: exportScreen, import: importScreen, settings, credits };
// The rules are plain Markdown files (data/rules/rules.<lang>.md), fetched on first use.
function rules() {
const lang = getLang();
const body = rulesCache[lang];
if (body === undefined) {
rulesCache[lang] = null; // loading
fetch(`data/rules/rules.${lang}.md`)
.then((r) => (r.ok ? r.text() : Promise.reject(new Error(String(r.status)))))
.then((md) => { rulesCache[lang] = renderMarkdown(md); })
.catch(() => { rulesCache[lang] = "error"; })
.finally(() => { if (screen === "rules" && !root.hidden) render(); });
}
const html = body === "error" ? `<p class="menu__msg menu__msg--error">${t("rules.error")}</p>` : body ?? `<p class="menu__sub">${t("rules.loading")}</p>`;
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 };
function render() {
root.innerHTML = `
@@ -296,6 +323,7 @@ export function createMenu(root, { content, storage, getState, getSettings, hand
export: () => { exportText = encodeSave(getState()); go("export"); },
import: () => go("import"),
settings: () => go("settings"),
rules: () => go("rules"),
credits: () => go("credits"),
back: () => go("home"),
resume: () => handlers.onResume(),
@@ -336,6 +364,8 @@ export function createMenu(root, { content, storage, getState, getSettings, hand
root.addEventListener("change", async (e) => {
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();
}