feat: add recipe theme and resume controls

Fixes #4, #7
This commit is contained in:
2026-08-14 13:51:00 -05:00
parent 803d722836
commit 22daf703bb
7 changed files with 157 additions and 7 deletions
+32
View File
@@ -3,6 +3,8 @@ const status = document.querySelector("#status");
const list = document.querySelector("#recipes");
const search = document.querySelector("#search");
const exit = document.querySelector("#exit");
const resume = document.querySelector("#resume");
const themeToggle = document.querySelector("#theme-toggle");
const portionControls = document.querySelector("#portions");
const portionCount = document.querySelector("#portion-count");
const fewerPortions = document.querySelector("#fewer-portions");
@@ -19,6 +21,9 @@ let displayedRecipe = null;
let portions = null;
let progress = { completedIngredients: [], completedSteps: [] };
let recipeModeActive = false;
let canResume = false;
let lastRecipeName = null;
let theme = "dark";
async function request(path, options) {
const response = await fetch(`${apiBase}${path}`, {
@@ -56,6 +61,9 @@ function render() {
portionControls.hidden = !recipeModeActive;
portionCount.textContent = portions ?? "";
fewerPortions.disabled = portions <= 1;
resume.hidden = !canResume;
resume.textContent = lastRecipeName ? `Resume ${lastRecipeName}` : "Resume last recipe";
themeToggle.textContent = theme === "light" ? "Use dark mode" : "Use light mode";
renderActiveRecipe();
}
@@ -112,6 +120,9 @@ function applyState(state) {
displayedRecipe = recipeModeActive ? state.recipe : null;
portions = state.portions;
progress = state.progress || { completedIngredients: [], completedSteps: [] };
canResume = Boolean(state.canResume);
lastRecipeName = state.lastRecipeName || null;
theme = state.theme === "light" ? "light" : "dark";
}
async function activate(recipe) {
@@ -161,6 +172,27 @@ async function updateProgress(kind, index, completed) {
fewerPortions.addEventListener("click", () => adjustPortions(-1));
morePortions.addEventListener("click", () => adjustPortions(1));
resume.addEventListener("click", async () => {
try {
applyState(await request("/resume", { method: "POST", body: "{}" }));
status.textContent = `${activeRecipeLabel} is back on the mirror.`;
render();
} catch (error) {
status.textContent = error.message;
}
});
themeToggle.addEventListener("click", async () => {
try {
applyState(await request("/theme", {
method: "POST", body: JSON.stringify({ theme: theme === "light" ? "dark" : "light" })
}));
render();
} catch (error) {
status.textContent = error.message;
}
});
exit.addEventListener("click", async () => {
status.textContent = "Leaving recipe mode…";
try {
+5 -1
View File
@@ -13,7 +13,11 @@
<p class="eyebrow">Kitchen Mirror</p>
<h1>Recipes</h1>
</div>
<button id="exit" class="secondary" type="button">Exit recipe mode</button>
<div class="header-actions">
<button id="theme-toggle" class="secondary" type="button"></button>
<button id="resume" class="secondary" type="button" hidden>Resume last recipe</button>
<button id="exit" class="secondary" type="button">Exit recipe mode</button>
</div>
</header>
<p id="status" role="status">Loading recipes…</p>
<div id="portions" class="portions" hidden>
+2
View File
@@ -23,6 +23,8 @@ header {
justify-content: space-between;
}
.header-actions { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; }
h1 { margin: 0; font-size: 2.3rem; }
.eyebrow {