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.