feat: rebuild math life around marks and directed endings
This commit is contained in:
@@ -176,6 +176,7 @@ button { color: inherit; }
|
||||
.metric { background: rgba(25,101,72,.06); border-radius: 14px; padding: 17px; }
|
||||
.metric b, .metric span { display: block; }.metric b { font: 28px Georgia, serif; }.metric span { margin-top: 5px; color: var(--muted); font-size: 11px; }
|
||||
.profile-subtitle { margin: 30px 0 12px; font: 24px Georgia, serif; }.profile-game-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 9px; }.profile-game-list > div { padding: 13px; border-radius: 11px; background: #eef1eb; }.profile-game-list b, .profile-game-list span { display: block; }.profile-game-list span { margin-top: 5px; color: var(--muted); font-size: 11px; }
|
||||
.profile-mark-list { display: flex; flex-wrap: wrap; gap: 8px; }.profile-mark-list span { border: 1px solid rgba(25,101,72,.2); border-radius: 99px; padding: 7px 10px; background: rgba(204,232,91,.12); color: var(--green); font-size: 10px; }
|
||||
.home-match-history[hidden] { display: none; }
|
||||
.match-history-list { display: grid; gap: 10px; }
|
||||
.match-history-item { display: flex; align-items: center; justify-content: space-between; gap: 18px; padding: 16px 18px; border: 1px solid var(--line); border-left: 4px solid var(--green); border-radius: 13px; background: rgba(255,255,252,.8); }
|
||||
@@ -268,6 +269,16 @@ dialog::backdrop { background: rgba(17,25,20,.55); backdrop-filter: blur(5px); }
|
||||
.hidden { display: none !important; }.form-error { min-height: 18px; color: #b84136; font-size: 12px; }
|
||||
.experience-dialog { width: min(760px, calc(100% - 30px)); }
|
||||
.experience-dialog h2 { font: 34px Georgia, serif; }.experience-dialog .scene { white-space: pre-line; line-height: 1.9; color: #3e4942; }
|
||||
.story-open { overflow: hidden; }
|
||||
.story-experience { position: fixed; inset: 0; z-index: 120; overflow-y: auto; padding: 28px clamp(20px, 5vw, 78px) 60px; background: radial-gradient(circle at 86% 8%, rgba(204,232,91,.18), transparent 30%), linear-gradient(145deg, #f7f6ef, #ecefe8); }
|
||||
.story-experience.hidden { display: none; }
|
||||
.story-experience-header { position: sticky; top: 0; z-index: 3; display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 14px 0 18px; background: linear-gradient(#f7f6ef 72%, transparent); }.story-experience-header h1 { margin: 7px 0 0; font: clamp(30px, 4vw, 52px) Georgia, serif; }
|
||||
.story-chapter-bar { display: grid; grid-template-columns: minmax(210px, .4fr) 1fr; align-items: end; gap: 25px; margin: 12px 0 24px; }.story-chapter-bar span, .story-chapter-bar b { display: block; }.story-chapter-bar span { color: var(--green); font-size: 11px; letter-spacing: .12em; }.story-chapter-bar b { margin-top: 5px; font: 20px Georgia, serif; }
|
||||
.story-experience-layout { display: grid; grid-template-columns: minmax(0, 1fr) minmax(260px, 340px); gap: 24px; align-items: start; }
|
||||
.story-scene-panel { min-height: min(620px, calc(100dvh - 210px)); padding: clamp(28px, 5vw, 64px); border: 1px solid var(--line); border-radius: 24px; background: rgba(255,255,252,.9); box-shadow: 0 28px 80px rgba(38,48,40,.08); }.story-scene-panel h2 { margin: 13px 0 20px; font: clamp(30px, 4vw, 48px) Georgia, serif; }.story-scene-panel .scene { min-height: 180px; white-space: pre-line; color: #354139; font-size: clamp(16px, 1.8vw, 20px); line-height: 2; }.story-scene-panel .choice-list { margin-top: 36px; }
|
||||
.story-special-choice { border-color: #a47b20 !important; background: #fff4c7 !important; color: #74520c !important; box-shadow: 0 8px 24px rgba(164,123,32,.12); }
|
||||
.story-mark-panel { position: sticky; top: 112px; padding: 24px; border-radius: 20px; background: var(--ink); color: white; }.story-mark-panel h2 { margin: 8px 0 20px; font: 28px Georgia, serif; }.story-domain-counts { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; margin-bottom: 18px; }.story-domain-counts div { padding: 9px 4px; border-radius: 9px; background: rgba(255,255,255,.07); text-align: center; }.story-domain-counts b, .story-domain-counts span { display: block; }.story-domain-counts b { color: var(--lime); font-size: 20px; }.story-domain-counts span { margin-top: 3px; color: #aeb9b1; font-size: 8px; }
|
||||
.story-mark-list { display: flex; flex-wrap: wrap; gap: 7px; }.story-mark { border: 1px solid rgba(255,255,255,.12); border-radius: 99px; padding: 6px 9px; color: #748078; font-size: 9px; }.story-mark.collected { border-color: rgba(204,232,91,.45); color: var(--lime); }.story-mark.current-run { background: rgba(204,232,91,.12); }
|
||||
.choice-list { display: grid; gap: 9px; margin-top: 25px; }
|
||||
.choice-list button { text-align: left; border: 1px solid var(--line); border-radius: 12px; padding: 14px; background: white; cursor: pointer; }
|
||||
.choice-list button:hover { border-color: var(--green); }
|
||||
@@ -295,6 +306,7 @@ dialog::backdrop { background: rgba(17,25,20,.55); backdrop-filter: blur(5px); }
|
||||
.preview-pane { min-height: 320px; }.match-history-item { align-items: flex-start; flex-direction: column; }.match-history-item > div:last-child { text-align: left; }
|
||||
.match-mode-switch { grid-template-columns: 1fr; }
|
||||
.board-online-panel { grid-template-columns: 1fr; }.board-online-panel > div, .board-online-panel form { display: grid; grid-template-columns: 1fr; }.board-online-panel p, .board-online-panel > strong { grid-column: 1; }
|
||||
.story-experience { padding: 14px 14px 40px; }.story-experience-header { align-items: flex-start; }.story-experience-header .ghost-button { width: auto; padding: 9px 11px; font-size: 10px; }.story-chapter-bar, .story-experience-layout { grid-template-columns: 1fr; }.story-scene-panel { min-height: 0; padding: 25px 20px; }.story-scene-panel .scene { min-height: 120px; }.story-mark-panel { position: static; }.story-domain-counts { grid-template-columns: repeat(2, 1fr); }
|
||||
.tool-card { min-height: 125px; }.workspace-heading, .map-heading { display: block; }.workspace-heading p { margin-top: 12px; }.tool-workspace { padding: 16px; }.calculator-controls { grid-template-columns: 1fr 1fr; }.calculator-display { min-height: 120px; padding: 18px; }.calculator-display output { font-size: 34px; }.calc-examples .primary-button { width: 100%; margin-left: 0; }.drawing-toolbar { align-items: stretch; }.drawing-toolbar .primary-button { width: 100%; margin-left: 0; }.canvas-stage canvas { width: 100%; min-width: 0; }
|
||||
.math-games-section { margin-top: 45px; }.game-card { min-height: 260px; padding: 21px; }.game-card-controls { align-items: stretch; }.game-card-controls .primary-button { flex: 1; }.twenty-four-numbers { gap: 7px; }.twenty-four-numbers button { border-radius: 13px; font-size: 28px; }.sudoku-board input { font-size: clamp(13px, 4.5vw, 20px); }.sudoku-actions { display: grid; grid-template-columns: 1fr 1fr; }.game-keypad { gap: 5px; }
|
||||
.challenge-panel { grid-template-columns: 1fr; padding: 20px; }.challenge-actions form { grid-template-columns: 1fr; }.challenge-actions .dark-button { width: 100%; }.challenge-code { width: 100%; padding: 14px 10px; font-size: 27px; }.realtime-status-line { display: grid; }.realtime-progress-panel { grid-template-columns: 1fr 1fr; }
|
||||
@@ -309,9 +321,6 @@ dialog::backdrop { background: rgba(17,25,20,.55); backdrop-filter: blur(5px); }
|
||||
.life-map { margin-top: 28px; }
|
||||
.life-map-canvas { position: relative; max-width: 1200px; margin: 0 auto; display: grid; grid-template-columns: 1fr 1fr; gap: 40px; padding: 30px 0; }
|
||||
.life-map-lines { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 0; pointer-events: none; }
|
||||
.life-map-center { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); z-index: 1; display: grid; place-items: center; text-align: center; pointer-events: none; }
|
||||
.life-map-center b { display: grid; place-items: center; width: 56px; height: 56px; border-radius: 50%; background: var(--ink); color: var(--lime); font: 22px Georgia, serif; margin-bottom: 6px; }
|
||||
.life-map-center span { font-size: 9px; color: var(--muted); letter-spacing: .12em; line-height: 1.4; }
|
||||
|
||||
.route-card {
|
||||
position: relative; width: 100%; padding: 28px 26px 24px; border-radius: 22px; z-index: 2;
|
||||
@@ -365,7 +374,7 @@ dialog::backdrop { background: rgba(17,25,20,.55); backdrop-filter: blur(5px); }
|
||||
|
||||
@media (max-width: 1050px) {
|
||||
.life-map-canvas { grid-template-columns: 1fr; }
|
||||
.life-map-lines, .life-map-center { display: none; }
|
||||
.life-map-lines { display: none; }
|
||||
.alumni-entry-content { flex-direction: column; align-items: flex-start; }
|
||||
.alumni-entry-action { align-items: flex-start; }
|
||||
.skill-grid { grid-template-columns: 1fr 1fr; }
|
||||
|
||||
@@ -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