feat: rebuild math life around marks and directed endings
This commit is contained in:
@@ -313,18 +313,65 @@ async function beginStory(slug) {
|
||||
try {
|
||||
const run = await api(`math-life/stories/${slug}/start/`, { method: "POST", body: {} });
|
||||
renderStoryNode(run);
|
||||
$("#experience-dialog").showModal();
|
||||
$("#story-experience").classList.remove("hidden");
|
||||
document.body.classList.add("story-open");
|
||||
window.scrollTo({ top: 0 });
|
||||
} catch (error) {
|
||||
showToast(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
function closeStoryExperience() {
|
||||
$("#story-experience").classList.add("hidden");
|
||||
document.body.classList.remove("story-open");
|
||||
navigate("life");
|
||||
}
|
||||
|
||||
function renderStoryMarks(run) {
|
||||
const collected = new Set(run.collection.map((mark) => mark.code));
|
||||
const current = new Set(run.state.marks || []);
|
||||
const labels = run.domain_labels || {};
|
||||
const counts = $("#story-domain-counts");
|
||||
counts.replaceChildren(
|
||||
...Object.entries(labels).map(([domain, label]) => {
|
||||
const item = document.createElement("div");
|
||||
const value = document.createElement("b");
|
||||
value.textContent = run.state.mark_counts?.[domain] || 0;
|
||||
const name = document.createElement("span");
|
||||
name.textContent = label;
|
||||
item.append(value, name);
|
||||
return item;
|
||||
})
|
||||
);
|
||||
const list = $("#story-mark-list");
|
||||
list.replaceChildren(
|
||||
...run.mark_definitions.map((mark) => {
|
||||
const item = document.createElement("span");
|
||||
item.className = [
|
||||
"story-mark",
|
||||
collected.has(mark.code) ? "collected" : "",
|
||||
current.has(mark.code) ? "current-run" : "",
|
||||
].filter(Boolean).join(" ");
|
||||
item.dataset.domain = mark.domain;
|
||||
item.textContent = mark.name;
|
||||
item.title = `${labels[mark.domain] || mark.domain}印记`;
|
||||
return item;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function renderStoryNode(run) {
|
||||
const root = $("#experience-content");
|
||||
const root = $("#story-experience-content");
|
||||
root.replaceChildren();
|
||||
$("#story-experience-title").textContent = run.story;
|
||||
const chapterNumber = run.chapter?.number || (run.status === "completed" ? 8 : 1);
|
||||
$("#story-chapter-number").textContent = `第 ${chapterNumber} 章`;
|
||||
$("#story-chapter-title").textContent = run.chapter?.title || "直博方向";
|
||||
$("#story-chapter-progress").style.width = `${Math.min(100, chapterNumber / 8 * 100)}%`;
|
||||
renderStoryMarks(run);
|
||||
const label = document.createElement("span");
|
||||
label.className = "kicker";
|
||||
label.textContent = `${run.story} · ${run.current_node}`;
|
||||
label.textContent = `CHAPTER ${String(chapterNumber).padStart(2, "0")} · ${run.current_node}`;
|
||||
const title = document.createElement("h2");
|
||||
title.textContent = run.node.character || "旁白";
|
||||
const scene = document.createElement("p");
|
||||
@@ -333,13 +380,20 @@ function renderStoryNode(run) {
|
||||
const choices = document.createElement("div");
|
||||
choices.className = "choice-list";
|
||||
if (run.status === "completed") {
|
||||
root.classList.add("completed");
|
||||
const ending = document.createElement("p");
|
||||
ending.textContent = "这段人生已经抵达结局,路径已写入你的数学档案。";
|
||||
choices.append(ending);
|
||||
ending.textContent = "统一直博的方向已经确定。这段人生、结局与全部印记已写入你的数学档案。";
|
||||
const back = document.createElement("button");
|
||||
back.className = "primary-button";
|
||||
back.textContent = "返回数学人生大厅";
|
||||
back.addEventListener("click", closeStoryExperience);
|
||||
choices.append(ending, back);
|
||||
} else {
|
||||
root.classList.remove("completed");
|
||||
run.node.choices.forEach((choice) => {
|
||||
const button = document.createElement("button");
|
||||
button.textContent = choice.text;
|
||||
if (choice.special) button.classList.add("story-special-choice");
|
||||
button.addEventListener("click", async () => {
|
||||
button.disabled = true;
|
||||
try {
|
||||
@@ -818,6 +872,20 @@ async function loadProfile() {
|
||||
metrics.append(item);
|
||||
});
|
||||
root.append(heading, pet, metrics);
|
||||
if (profile.story_marks?.length) {
|
||||
const markTitle = document.createElement("h3");
|
||||
markTitle.className = "profile-subtitle";
|
||||
markTitle.textContent = `数学人生印记 · ${profile.story_marks.length} / 20`;
|
||||
const markList = document.createElement("div");
|
||||
markList.className = "profile-mark-list";
|
||||
profile.story_marks.forEach((mark) => {
|
||||
const item = document.createElement("span");
|
||||
item.textContent = mark.name;
|
||||
item.dataset.domain = mark.domain;
|
||||
markList.append(item);
|
||||
});
|
||||
root.append(markTitle, markList);
|
||||
}
|
||||
if (profile.recent_matches?.length) {
|
||||
const matchTitle = document.createElement("h3");
|
||||
matchTitle.className = "profile-subtitle";
|
||||
@@ -1205,6 +1273,7 @@ function bindUI() {
|
||||
$$("[data-jump]").forEach((button) => button.addEventListener("click", () => navigate(button.dataset.jump)));
|
||||
$$("[data-open-auth]").forEach((button) => button.addEventListener("click", openAuth));
|
||||
$("#start-mathbti").addEventListener("click", startMathBTI);
|
||||
$("#story-experience-close").addEventListener("click", closeStoryExperience);
|
||||
$("#save-formula").addEventListener("click", saveFormula);
|
||||
$("#latex-source").addEventListener("input", (event) => { renderLatexPreview(event.target.value); });
|
||||
$$(".tool-card").forEach((button) => {
|
||||
|
||||
Reference in New Issue
Block a user