merge: integrate bug1.0 updates

This commit is contained in:
2026-08-09 03:34:36 +08:00
23 changed files with 313 additions and 17 deletions
+150 -13
View File
@@ -1,3 +1,30 @@
const ROUTE_MATHEMATICIANS = {
believer: [
{ name: "高斯", portrait: "/static/img/mathematicians/1000.png", identity: "定理工匠" },
{ name: "祖冲之", portrait: "/static/img/mathematicians/1010.png", identity: "穷竭守尺人" },
{ name: "欧拉", portrait: "/static/img/mathematicians/1001.png", identity: "公式狂僧" },
{ name: "诺特", portrait: "/static/img/mathematicians/1011.png", identity: "抽象代数宗师" },
],
spreader: [
{ name: "斐波那契", portrait: "/static/img/mathematicians/1100.png", identity: "商路算郎" },
{ name: "华罗庚", portrait: "/static/img/mathematicians/1110.png", identity: "双法布道者" },
{ name: "伽罗瓦", portrait: "/static/img/mathematicians/1101.png", identity: "决斗狂热者" },
{ name: "Lovelace", portrait: "/static/img/mathematicians/1111.png", identity: "织机先知" },
],
applier: [
{ name: "秦九韶", portrait: "/static/img/mathematicians/0100.png", identity: "大衍谋士" },
{ name: "牛顿", portrait: "/static/img/mathematicians/0110.png", identity: "宇宙立法者" },
{ name: "图灵", portrait: "/static/img/mathematicians/0111.png", identity: "密码破译使" },
{ name: "冯·诺依曼", portrait: "/static/img/mathematicians/0101.png", identity: "博弈游侠" },
],
seer: [
{ name: "希帕提娅", portrait: "/static/img/mathematicians/0000.png", identity: "几何殉道者" },
{ name: "赵爽", portrait: "/static/img/mathematicians/0010.png", identity: "弦图先觉" },
{ name: "庞加莱", portrait: "/static/img/mathematicians/0001.png", identity: "拓扑游方" },
{ name: "约翰逊", portrait: "/static/img/mathematicians/0011.png", identity: "人脑计算机" },
],
};
const state = {
user: null,
stories: [],
@@ -184,25 +211,100 @@ function card({ meta, title, body, foot, action, onClick, disabled = false }) {
return article;
}
function renderRouteMathematicians() {
Object.entries(ROUTE_MATHEMATICIANS).forEach(([clan, mathematicians]) => {
const root = $(`[data-mathematicians="${clan}"]`);
if (!root) return;
root.replaceChildren(...mathematicians.map((person) => {
const item = document.createElement("div");
item.className = "route-mathematician";
const img = document.createElement("img");
img.src = person.portrait;
img.alt = person.name;
img.loading = "lazy";
const name = document.createElement("b");
name.textContent = person.name;
const identity = document.createElement("small");
identity.textContent = person.identity;
item.append(img, name, identity);
return item;
}));
});
}
async function loadStories() {
const root = $("#story-list");
try {
state.stories = await api("math-life/stories/");
root.classList.remove("loading");
root.replaceChildren(...state.stories.map((story) => card({
meta: story.kind === "flagship" ? "旗舰人生" : story.kind === "skill" ? "人物 Skill" : "特别篇",
title: story.title,
body: story.summary || "在不完整信息中判断机会、关系与代价。",
foot: `${story.estimated_minutes} 分钟`,
action: story.available ? "开始人生 →" : "查看预告",
disabled: !story.available,
onClick: () => beginStory(story.slug),
})));
renderRouteBadges();
renderSkillList();
} catch (error) {
root.textContent = error.message;
showToast(error.message);
}
}
function renderRouteBadges() {
const flagship = state.stories.find((story) => story.kind === "flagship" && story.available);
const skillCount = state.stories.filter((story) => story.kind === "skill").length;
const alumniCount = $("#alumni-count");
alumniCount.textContent = skillCount > 0 ? `当前 ${skillCount} 个校友人生可体验` : "即将上线校友访谈";
if (flagship) {
const route = flagship.slug.includes("believer") ? "believer" : "believer";
const card = $(`.route-card[data-route="${route}"]`);
if (card) {
card.querySelector(".route-status").textContent = `可体验 · 约 ${flagship.estimated_minutes} 分钟`;
}
}
}
function renderSkillList() {
const root = $("#skill-list");
const skills = state.stories.filter((story) => story.kind === "skill");
if (skills.length === 0) {
root.classList.remove("loading");
root.textContent = "校友访谈即将上线,敬请期待。";
return;
}
root.classList.remove("loading");
root.replaceChildren(...skills.map((story) => {
const article = document.createElement("article");
article.className = "skill-card";
const header = document.createElement("div");
header.className = "skill-header";
const meta = document.createElement("span");
meta.className = "skill-meta";
meta.textContent = story.kind === "skill" ? "校友访谈" : "特别篇";
header.append(meta);
const body = document.createElement("div");
body.className = "skill-body";
const title = document.createElement("h3");
title.textContent = story.title;
const summary = document.createElement("p");
summary.textContent = story.summary || "在不完整信息中判断机会、关系与代价。";
const footer = document.createElement("footer");
const duration = document.createElement("span");
duration.textContent = `${story.estimated_minutes} 分钟`;
const button = document.createElement("button");
button.textContent = story.available ? "开始人生 →" : "查看预告";
button.disabled = !story.available;
if (story.available) button.addEventListener("click", () => beginStory(story.slug));
footer.append(duration, button);
body.append(title, summary, footer);
article.append(header, body);
return article;
}));
}
function showLifeHub() {
$("#life-hub").classList.remove("hidden");
$("#life-skills").classList.add("hidden");
}
function showLifeSkills() {
$("#life-hub").classList.add("hidden");
$("#life-skills").classList.remove("hidden");
window.scrollTo({ top: 0, behavior: "smooth" });
}
async function beginStory(slug) {
if (!requireAuth()) return;
try {
@@ -958,6 +1060,18 @@ function switchTool(tool) {
window.HuluToolbox?.activate(tool);
}
function renderLatexPreview(source) {
const target = $("#latex-preview");
target.replaceChildren();
if (!source.trim()) return;
if (typeof katex === "undefined") { target.textContent = source; return; }
try {
katex.render(source, target, { throwOnError: false, displayMode: true });
} catch {
target.textContent = source;
}
}
async function saveFormula() {
if (!requireAuth()) return;
try {
@@ -977,13 +1091,34 @@ function bindUI() {
$$("[data-open-auth]").forEach((button) => button.addEventListener("click", openAuth));
$("#start-mathbti").addEventListener("click", startMathBTI);
$("#save-formula").addEventListener("click", saveFormula);
$("#latex-source").addEventListener("input", (event) => { $("#latex-preview").textContent = event.target.value; });
$("#latex-source").addEventListener("input", (event) => { renderLatexPreview(event.target.value); });
$$(".tool-card").forEach((button) => {
button.addEventListener("click", () => switchTool(button.dataset.tool));
});
$("#symbol-search").addEventListener("input", (event) => {
renderSymbols(event.target.value);
});
$("#enter-alumni").addEventListener("click", showLifeSkills);
$("#back-to-hub").addEventListener("click", showLifeHub);
$$(".route-card").forEach((card) => {
card.addEventListener("click", (event) => {
if (event.target.closest(".route-enter")) return;
const route = card.dataset.route;
const flagship = state.stories.find(
(story) => story.kind === "flagship" && story.available && story.slug.includes(route),
);
if (flagship) beginStory(flagship.slug);
else showToast(`${card.querySelector(".route-spirit b").textContent}人生即将开放`);
});
card.querySelector(".route-enter").addEventListener("click", () => {
const route = card.dataset.route;
const flagship = state.stories.find(
(story) => story.kind === "flagship" && story.available && story.slug.includes(route),
);
if (flagship) beginStory(flagship.slug);
else showToast(`${card.querySelector(".route-spirit b").textContent}人生即将开放`);
});
});
$$(".ability-node").forEach((button) => {
button.addEventListener("click", () => {
state.videoAbility =
@@ -1041,6 +1176,7 @@ function bindUI() {
async function boot() {
bindUI();
renderRouteMathematicians();
window.HuluToolbox?.init();
window.HuluRealtime?.init();
renderSymbols();
@@ -1051,6 +1187,7 @@ async function boot() {
loadContent(),
window.HuluGames?.load(),
]);
renderLatexPreview($("#latex-source").value);
}
boot();