frontend tweaks

This commit is contained in:
Pagwin 2026-06-17 16:02:44 -04:00
parent 2e50f61e57
commit 9866af07d3

View file

@ -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;
}
</style>
</head>
@ -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 <details> 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:
`<b>Status:</b> ${s.status} &nbsp; <b>Objective:</b> ${s.objective_value ?? "—"} &nbsp; ` +
@ -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.