From 9866af07d35c2f2dcea180fd5bf51734c1b20f2e Mon Sep 17 00:00:00 2001 From: Pagwin Date: Wed, 17 Jun 2026 16:02:44 -0400 Subject: [PATCH] frontend tweaks --- index.html | 81 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 65 insertions(+), 16 deletions(-) diff --git a/index.html b/index.html index a0c8f80..bb80a69 100644 --- a/index.html +++ b/index.html @@ -185,6 +185,26 @@ opacity: .8; border-bottom: 1px solid #8886; } + + /* Solution cards: each solve gets its own collapsible block so older + solutions stay viewable, newest on top. */ + details.solution { + border: 1px solid #8884; + border-radius: 8px; + padding: .3rem .8rem; + margin-top: .8rem; + } + + details.solution>summary { + font-weight: 600; + cursor: pointer; + padding: .3rem 0; + } + + .pending { + opacity: .75; + margin-top: .8rem; + } @@ -445,8 +465,20 @@ [upBoxes[u], " " + u])); } } - type.onchange = renderUpgrades; + // Vat amounts only matter for Foundries; hide them otherwise and keep + // them as the card's last fields (before the actions). + const vatFields = [ + field("Vat steel", vs), + field("Vat brass", vb), + field("Vat electrum", ve), + ]; + function renderVats() { + const show = type.value === "foundry"; + for (const f of vatFields) f.style.display = show ? "" : "none"; + } + type.onchange = () => {renderUpgrades(); renderVats();}; renderUpgrades(); + renderVats(); card._get = () => { const o = {name: name.value, type: type.value}; @@ -469,14 +501,12 @@ field("Name", name), field("Type", type), field("Renown", renown), - field("Vat steel", vs), - field("Vat brass", vb), - field("Vat electrum", ve), field("Upgrades (already installed)", upWrap), checkField("Can renovate", reno), field("Adjacent cities (csv of names)", adjacent), field("Forced actions (turn:action, csv)", forced), field("Avail turns (csv, blank=all)", avail), + ...vatFields, el("div", {class: "card-actions"}, removeBtn(card))); document.getElementById("cities").append(card); } @@ -497,11 +527,13 @@ const forced = el("input", {value: "", placeholder: "0:Aridias"}); const avail = el("input", {value: ""}); + const bastionsField = field("Bastions (Baron only)", bastions); + let nameEdited = !!a.name; name.oninput = () => {nameEdited = true;}; function syncType() { if (!nameEdited) name.value = type.value; - bastions.disabled = (type.value !== "Baron"); + bastionsField.style.display = (type.value === "Baron") ? "" : "none"; desc.textContent = AGENT_DESC[type.value] || ""; } type.onchange = syncType; @@ -519,9 +551,9 @@ card.append( field("Agent", type), field("Effect", desc), - field("Bastions (Baron only)", bastions), field("Forced city (turn:city, csv)", forced), field("Avail turns (csv, blank=all)", avail), + bastionsField, el("div", {class: "card-actions"}, removeBtn(card))); document.getElementById("agents").append(card); } @@ -645,16 +677,24 @@ }; } + // Each solve produces its own collapsible
card; older ones are + // kept (collapsed) so previous solutions stay viewable. solutionCount labels + // them in request order. + let solutionCount = 0; + async function run() { const errBox = document.getElementById("error"); const out = document.getElementById("output"); - errBox.textContent = ""; out.innerHTML = ""; + errBox.textContent = ""; + // A placeholder card for this request, prepended so the newest is on top. + // It's replaced by the solution on success, or removed on error. + const pending = el("p", {class: "pending"}, "Solving…"); + out.prepend(pending); let problem, time; try { problem = buildProblem(); time = +document.getElementById("time").value; - } catch (e) {errBox.textContent = e.message; return;} - out.textContent = "Solving…"; + } catch (e) {errBox.textContent = e.message; pending.remove(); return;} try { const resp = await fetch("/solve", { method: "POST", @@ -662,15 +702,20 @@ body: JSON.stringify({problem, max_time_seconds: time}), }); const data = await resp.json(); - if (!resp.ok) {out.innerHTML = ""; errBox.textContent = data.error || "Server error"; return;} - renderSolution(data); - } catch (e) {out.innerHTML = ""; errBox.textContent = e.message;} + if (!resp.ok) {pending.remove(); errBox.textContent = data.error || "Server error"; return;} + renderSolution(data, pending); + } catch (e) {pending.remove(); errBox.textContent = e.message;} } - function renderSolution(s) { - const out = document.getElementById("output"); - out.innerHTML = ""; - out.append(el("h2", {}, "Solution")); + function renderSolution(s, placeholder) { + // Collapse any previously-shown solutions so the new one is the focus. + for (const d of document.querySelectorAll("#output details.solution")) d.open = false; + + const n = ++solutionCount; + const details = el("details", {class: "solution", open: ""}); + details.append(el("summary", {}, + `Solution ${n} — ${s.status}, objective ${s.objective_value ?? "—"}`)); + const out = details; out.append(el("p", { html: `Status: ${s.status}   Objective: ${s.objective_value ?? "—"}   ` + @@ -709,6 +754,10 @@ ["Turn", "Converted into", "Amount"], s.trade_conversions.map(c => [c.turn, c.resource, fmtNum(c.amount)]))); } + + // Swap the finished card in for this request's placeholder. + if (placeholder) placeholder.replaceWith(details); + else document.getElementById("output").prepend(details); } // Trim trailing ".0" so whole numbers read cleanly.