(() => { "use strict"; const SAMPLE_QUESTION = "我是否要接受这次工作调整?怎样做能更稳妥?"; const SAMPLE_LINES = [9, 8, 8, 8, 8, 8]; const SAMPLE_HISTORY = [ { id: "sample-1", date: "今天 · 21:10", lines: [9, 8, 8, 8, 8, 8], question: SAMPLE_QUESTION, explanationSource: "AI 生成", action: "写下三条已知事实,再约一次不超过 20 分钟的信息沟通。" }, { id: "sample-2", date: "昨天 · 07:40", lines: [8, 8, 7, 8, 8, 8], question: null, explanationSource: "本地解读", action: null }, { id: "sample-3", date: "7 月 30 日 · 22:15", lines: [7, 7, 8, 8, 7, 6], question: "这段合作应该怎样调整边界?", explanationSource: null, action: "先把需要确认的职责写成一页清单。" } ]; const HEXAGRAM_NAMES = [ "", "乾", "坤", "屯", "蒙", "需", "讼", "师", "比", "小畜", "履", "泰", "否", "同人", "大有", "谦", "豫", "随", "蛊", "临", "观", "噬嗑", "贲", "剥", "复", "无妄", "大畜", "颐", "大过", "坎", "离", "咸", "恒", "遁", "大壮", "晋", "明夷", "家人", "睽", "蹇", "解", "损", "益", "夬", "姤", "萃", "升", "困", "井", "革", "鼎", "震", "艮", "渐", "归妹", "丰", "旅", "巽", "兑", "涣", "节", "中孚", "小过", "既济", "未济" ]; // 行为下卦、列为上卦;三位编码均按自下而上排列,1 为阳、0 为阴。 const KING_WEN = { "111": { "111": 1, "100": 34, "010": 5, "001": 26, "000": 11, "011": 9, "101": 14, "110": 43 }, "100": { "111": 25, "100": 51, "010": 3, "001": 27, "000": 24, "011": 42, "101": 21, "110": 17 }, "010": { "111": 6, "100": 40, "010": 29, "001": 4, "000": 7, "011": 59, "101": 64, "110": 47 }, "001": { "111": 33, "100": 62, "010": 39, "001": 52, "000": 15, "011": 53, "101": 56, "110": 31 }, "000": { "111": 12, "100": 16, "010": 8, "001": 23, "000": 2, "011": 20, "101": 35, "110": 45 }, "011": { "111": 44, "100": 32, "010": 48, "001": 18, "000": 46, "011": 57, "101": 50, "110": 28 }, "101": { "111": 13, "100": 55, "010": 63, "001": 22, "000": 36, "011": 37, "101": 30, "110": 49 }, "110": { "111": 10, "100": 54, "010": 60, "001": 41, "000": 19, "011": 61, "101": 38, "110": 58 } }; const icons = { lock: '', arrow: '', chevron: '', shield: '', book: '' }; const screen = document.querySelector("#screen"); const modalRoot = document.querySelector("#modal-root"); const toastRegion = document.querySelector("#toast-region"); const backButton = document.querySelector("[data-action='back']"); const state = { screen: "welcome", question: "", lines: [], coins: [null, null, null], modal: null, consent: false, explanationSource: "ai", aiError: false, historyRecords: [], selectedRecordId: null, autoSaveHistory: true, saveQuestion: true, saveExplanation: true, saveAction: true, saved: false, currentRecordId: "current-session", currentSessionOptedOut: false, currentSaveMode: null, explanationGenerated: false, questionOrigin: "welcome", settingsOrigin: "home", deleteReturnScreen: "history" }; function sampleHistory() { return SAMPLE_HISTORY.map((record) => ({ ...record, lines: [...record.lines] })); } function escapeHtml(value) { return String(value) .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function lineIsYang(value) { return value === 7 || value === 9; } function lineIsMoving(value) { return value === 6 || value === 9; } function transformLines(lines) { return lines.map((value) => { if (value === 6) return 7; if (value === 9) return 8; return value; }); } function lookupHexagram(lines) { const bits = lines.map((value) => lineIsYang(value) ? "1" : "0"); const lower = bits.slice(0, 3).join(""); const upper = bits.slice(3, 6).join(""); const number = KING_WEN[lower][upper]; return { number, name: HEXAGRAM_NAMES[number] }; } function lineName(value, index) { const position = ["初", "二", "三", "四", "五", "上"][index]; const polarity = lineIsYang(value) ? "九" : "六"; if (index === 0) return `${position}${polarity}`; if (index === 5) return `${position}${polarity}`; return `${polarity}${position}`; } function resultData() { const sourceLines = state.lines.length === 6 ? state.lines : SAMPLE_LINES; const changedLines = transformLines(sourceLines); return { sourceLines, changedLines, primary: lookupHexagram(sourceLines), changed: lookupHexagram(changedLines), movingIndexes: sourceLines .map((value, index) => lineIsMoving(value) ? index : -1) .filter((index) => index >= 0) }; } function hasSavableQuestion() { return Boolean(state.question && !state.question.startsWith("未记录具体问题")); } function generatedExplanationSource() { if (!state.explanationGenerated || !state.saveExplanation) return null; return state.explanationSource === "ai" ? "AI 生成" : "本地解读"; } function currentSessionRecord() { return state.historyRecords.find((record) => record.id === state.currentRecordId); } function persistCurrentSession(mode = "manual") { if (state.lines.length !== 6) return; const data = resultData(); const existing = currentSessionRecord(); const record = { id: state.currentRecordId, date: "刚刚", lines: [...data.sourceLines], question: state.saveQuestion && hasSavableQuestion() ? state.question : null, explanationSource: generatedExplanationSource(), action: state.explanationGenerated && state.saveAction ? "写下三条已知事实,再进行一次短沟通。" : null }; if (existing) Object.assign(existing, record); else state.historyRecords.unshift(record); state.saved = true; state.currentSessionOptedOut = false; state.currentSaveMode = mode; } function attachExplanationToCurrentSession() { const record = currentSessionRecord(); if (!record || !state.saved) return; if (state.saveExplanation) record.explanationSource = generatedExplanationSource(); if (state.saveAction) { record.action = "写下三条已知事实,再进行一次短沟通。"; } } function prepareNewSession() { state.currentRecordId = `current-session-${Date.now()}-${state.historyRecords.length}`; state.saved = false; state.currentSessionOptedOut = false; state.currentSaveMode = null; state.explanationGenerated = false; state.aiError = false; } function stepper(current, total) { const progress = Math.round((current / total) * 100); return `
${String(current).padStart(2, "0")} ${String(total).padStart(2, "0")}
`; } function welcomeView() { return `

慢一点,看清当下

把一个犹豫,安静地放在这里。

用你手中的三枚硬币,依次记录六次结果。这里提供文化文本与反思线索,不替你预测,也不替你决定。

工作名“一问”仅用于本轮视觉评审

`; } function historyRecordData(record) { const changedLines = transformLines(record.lines); const movingIndexes = record.lines .map((value, index) => lineIsMoving(value) ? index : -1) .filter((index) => index >= 0); return { primary: lookupHexagram(record.lines), changed: lookupHexagram(changedLines), changedLines, movingIndexes }; } function recordRoute(record) { const data = historyRecordData(record); return data.movingIndexes.length ? `${data.primary.name} 之 ${data.changed.name}` : data.primary.name; } function recentRecordCard(record) { const data = historyRecordData(record); return ` `; } function homeView() { const latest = state.historyRecords[0]; return `

慢一点,看清当下

此刻,有什么想安静看清?

你投币,应用记录;结果用于整理想法,不替你预测或决定。

你的观照

${latest ? recentRecordCard(latest) : ` `}
`; } function historyCard(record) { const data = historyRecordData(record); return `
  • `; } function historyView() { const hasRecords = state.historyRecords.length > 0; return `

    本机记录

    问卦簿

    ${hasRecords ? `${state.historyRecords.length} 条` : ""}

    回看当时看见了什么,而不是用旧结果替今天做决定。

    ${icons.lock} 记录只在本机,默认排除系统备份与设备迁移;你可随时删除。
    ${hasRecords ? `
      ${state.historyRecords.map(historyCard).join("")}
    ` : `

    还没有保存的记录

    ${state.autoSaveHistory ? "完成一次起卦后,会默认把问题、卦象与后续解读保存在本机。" : "自动保存已经关闭;完成后仍可在结果页选择保存本次。"}

    `}
    `; } function settingRow({ action, title, description, checked, disabled = false, detail = false }) { return ` `; } function settingsView() { const detailsDisabled = !state.autoSaveHistory; return `

    本机与隐私

    保存设置

    默认留住完整的反思过程;你可以全局关闭,也可以只调整保存内容。

    ${icons.shield}
    只保存在 App 私有存储

    历史不会主动上传,并默认排除系统备份、设备迁移与云同步。

    ${settingRow({ action: "auto-save-toggle", title: "自动保存完整记录", description: state.autoSaveHistory ? "已开启 · 完成起卦后立即保存到本机" : "已关闭 · 结果页仍可手动保存本次", checked: state.autoSaveHistory })}
    ${settingRow({ action: "save-question-setting", title: "保存问题原文", description: "与你写下的卦象背景一同回顾", checked: state.saveQuestion, disabled: detailsDisabled, detail: true })} ${settingRow({ action: "save-explanation-setting", title: "保存解读全文", description: "本地或 AI 解读会加入同一次记录", checked: state.saveExplanation, disabled: detailsDisabled, detail: true })} ${settingRow({ action: "save-action-setting", title: "保存行动记录", description: "保留“可以试的一小步”与完成状态", checked: state.saveAction, disabled: detailsDisabled, detail: true })}

    这些开关只控制本机历史,不会允许或触发 AI。每次 AI 解读仍需要你主动选择并确认发送范围。

    管理本机记录

    当前原型会话中有 ${state.historyRecords.length} 条记录。

    `; } function historyDetailView() { const record = state.historyRecords.find((item) => item.id === state.selectedRecordId); if (!record) return historyView(); const data = historyRecordData(record); const hasMoving = data.movingIndexes.length > 0; return `

    ${record.date}

    ${recordRoute(record)}

    保存的起卦快照

    coin-v1 · 本地快照${hasMoving ? `${data.movingIndexes.length} 个动爻` : "无动爻"}
    ${hexagramFigure(record.lines, data.primary, true)} ${hasMoving ? `
    之
    ${hexagramFigure(data.changedLines, data.changed, false)}` : ""}

    当时所问

    ${record.question ? "已保存" : "未保存"}

    ${escapeHtml(record.question ?? "这条记录没有保存问题原文。卦象结果和方法版本仍可独立复核。")}

    保存内容

    起卦结果
    ${data.primary.name}${hasMoving ? `之${data.changed.name}` : ""} · ${data.movingIndexes.length ? data.movingIndexes.map((index) => lineName(record.lines[index], index)).join("、") : "无动爻"}
    解释来源
    ${record.explanationSource ?? "未保存解释"}
    版本
    coin-v1 · content-demo
    ${record.action ? `
    当时记下的一小步

    ${escapeHtml(record.action)}

    ` : ""}

    删除前会再次确认;原型刷新后恢复演示数据。

    `; } function questionView() { return `
    ${stepper(1, 2)}

    起念

    你想看清什么?

    写成一个开放的问题,比只问“会不会”更容易照见可行动的部分。

    ${state.autoSaveHistory ? "完成起卦后默认保存在本机" : "自动保存已关闭,本次可稍后手动保存"} ${state.question.length}/120
    ${icons.lock}

    起卦计算在本地完成。只有你主动选择 AI 解读并再次确认后,所需内容才会被发送。

    `; } function lineStack(lines) { return [5, 4, 3, 2, 1, 0].map((index) => { const value = lines[index]; if (!value) { return '
    '; } const kind = lineIsYang(value) ? "yang" : "yin"; const moving = lineIsMoving(value) ? " line-slot--moving" : ""; return `
    ${lineIsMoving(value) ? `${value}` : ""}
    `; }).join(""); } function coinButton(value, index) { const face = value === 2 ? "字" : value === 3 ? "背" : "点选"; const score = value ? String(value) : "字 ↔ 背"; const valueAttr = value ? `data-value="${value}"` : ""; const next = value === null ? "字,2 分" : value === 2 ? "背,3 分" : "字,2 分"; return `
    硬币 ${index + 1}
    `; } function castingView() { const round = state.lines.length + 1; const selected = state.coins.every((value) => value !== null); const sum = selected ? state.coins.reduce((total, value) => total + value, 0) : null; const currentType = sum ? ({ 6: "老阴 · 动", 7: "少阳", 8: "少阴", 9: "老阳 · 动" })[sum] : ""; return `
    ${stepper(2, 2)}
    ${escapeHtml(state.question)}
    ${state.lines.length} / 6

    从下往上记录

    ${state.lines.length ? '' : ""}
    ${lineStack(state.lines)}

    第 ${round} 爻

    依照手中硬币,逐枚点选“字”或“背”

    ${state.coins.map(coinButton).join("")}
    ${sum ? `本次合计 ${sum} · ${currentType}` : "点按硬币可在“字 2”与“背 3”间切换"}
    `; } function hexLine(value, index, showMoving) { const kind = lineIsYang(value) ? "yang" : "yin"; const moving = showMoving && lineIsMoving(value) ? " hex-line--moving" : ""; return `
    `; } function hexagramFigure(lines, hexagram, showMoving) { const rendered = [5, 4, 3, 2, 1, 0] .map((index) => hexLine(lines[index], index, showMoving)) .join(""); return `
    ${hexagram.name}
    第 ${hexagram.number} 卦
    `; } function resultCopy(data) { if (data.primary.number === 24) { return { classic: "复:亨。出入无疾,朋来无咎。反复其道,七日来复。利有攸往。", plain: "“复”提醒人把注意力放回可返回、可重新开始之处。它不是替你判断成败,而是邀请你看见:此刻是否有一条更朴素、更合乎本心的路。", moving: "初九:不远复,无祇悔,元吉。", movingPlain: "偏离尚不远时就察觉并回转,代价通常较小。先修正一个最近的动作,比一次解决所有问题更可行。" }; } return { classic: `${data.primary.name}卦 · 第 ${data.primary.number} 卦。本原型仅演示排盘与阅读层级,正式经典文本将从经校勘的本地内容包读取。`, plain: "先观察卦象呈现的张力与变化,再回到你能核实的事实、感受和资源。不要把符号当成替你下结论的证据。", moving: data.movingIndexes.length ? `${data.movingIndexes.map((index) => lineName(data.sourceLines[index], index)).join("、")}发生变化。` : "", movingPlain: "变化的位置可作为反思入口:哪里正在松动,哪里仍需要保留余地?" }; } function errorCard() { return ` `; } function currentSavePanel({ compact = false } = {}) { if (state.saved) { const explanationAttached = state.explanationGenerated && state.saveExplanation; const savedContext = state.saveQuestion && hasSavableQuestion() ? "问题、卦象" : "卦象"; return `
    ${icons.book} ${explanationAttached ? "解读已加入本次记录" : state.currentSaveMode === "automatic" ? "已自动保存到本机" : "已保存到本机问卦簿"} ${explanationAttached ? `与${savedContext}归在同一次观照中` : `${savedContext}快照已进入问卦簿`}
    `; } return `
    ${icons.lock} 本次未保存 ${state.currentSessionOptedOut ? "你已选择不保留这次记录" : "自动保存已关闭;不会写入问卦簿"}
    `; } function resultView() { const data = resultData(); const copy = resultCopy(data); const hasMoving = data.movingIndexes.length > 0; const question = state.question || "未记录具体问题"; return `
    起卦结果 · 本地计算 ${hasMoving ? `${data.movingIndexes.length} 个动爻` : "无动爻"}
    ${hexagramFigure(data.sourceLines, data.primary, true)} ${hasMoving ? `
    之
    ${hexagramFigure(data.changedLines, data.changed, false)}` : ""}
    ${hasMoving ? "" : '
    卦象稳定 · 不显示之卦
    '}

    你所问

    本次记录

    ${escapeHtml(question)}

    ${state.aiError ? errorCard() : ""}

    经典原文

    经典文本
    ${copy.classic}

    本地白话

    本地内容

    ${copy.plain}

    ${hasMoving ? `

    动爻

    变化处
    ${data.movingIndexes.map((index) => lineName(data.sourceLines[index], index)).join(" · ")}

    ${copy.moving}

    ${copy.movingPlain}

    ` : ""}
    ${currentSavePanel()}
    `; } function loadingView() { return `

    正在整理反思线索

    结果已经生成。AI 只在此处协助组织文字,不参与起卦。

    `; } function explanationView() { const data = resultData(); const ai = state.explanationSource === "ai"; return `

    ${ai ? "辅助反思" : "离线阅读"}

    从“回到起点”开始

    ${ai ? "AI 生成" : "本地解读"} ${data.primary.name}卦 · 第 ${data.primary.number} 卦

    眼前更重要的,也许不是立刻选定一条路,而是辨认哪一步能让你重新获得稳定与判断力。

    可以先观察

    问问自己

    1. 如果暂时不追求一次做对,我最想先确认的一个事实是什么?
    2. 哪一种选择更容易保留回转余地,而不是把自己锁死?
    3. 我可以向谁说明顾虑,并获得一条具体信息?
    可以试的一小步

    在今天结束前,写下“已知事实、尚待确认、我的底线”各一条,再约一次不超过 20 分钟的信息沟通。

    这是一种文化反思与文字整理,不是预测、诊断或替代你的判断。涉及医疗、法律、财务或人身安全时,请寻求相应专业支持。

    ${currentSavePanel({ compact: true })}
    `; } function deleteRecordModal() { return ` `; } function clearAllModal() { return ` `; } function interpretationModal() { return ` `; } function consentModal() { return ` `; } function methodModal() { return ` `; } function renderModal() { if (state.modal === "interpretation") modalRoot.innerHTML = interpretationModal(); else if (state.modal === "consent") modalRoot.innerHTML = consentModal(); else if (state.modal === "method") modalRoot.innerHTML = methodModal(); else if (state.modal === "delete") modalRoot.innerHTML = deleteRecordModal(); else if (state.modal === "clear-all") modalRoot.innerHTML = clearAllModal(); else modalRoot.innerHTML = ""; document.body.style.overflow = state.modal ? "hidden" : ""; if (state.modal) { window.setTimeout(() => { modalRoot.querySelector("button, input")?.focus(); }, 20); } } function render(options = {}) { if (state.screen === "welcome") screen.innerHTML = welcomeView(); else if (state.screen === "home") screen.innerHTML = homeView(); else if (state.screen === "history") screen.innerHTML = historyView(); else if (state.screen === "history-detail") screen.innerHTML = historyDetailView(); else if (state.screen === "settings") screen.innerHTML = settingsView(); else if (state.screen === "question") screen.innerHTML = questionView(); else if (state.screen === "casting") screen.innerHTML = castingView(); else if (state.screen === "result") screen.innerHTML = resultView(); else if (state.screen === "loading") screen.innerHTML = loadingView(); else if (state.screen === "explanation") screen.innerHTML = explanationView(); backButton.hidden = state.screen === "welcome" || state.screen === "home"; renderModal(); if (!options.preserveFocus) { screen.focus({ preventScroll: true }); window.scrollTo({ top: 0, behavior: "auto" }); } } function showToast(message) { toastRegion.innerHTML = `
    ${escapeHtml(message)}
    `; window.setTimeout(() => { toastRegion.innerHTML = ""; }, 1800); } function closeModal() { state.modal = null; state.consent = false; renderModal(); } function goBack() { if (state.modal) { closeModal(); return; } if (state.screen === "question") state.screen = state.questionOrigin; else if (state.screen === "history") state.screen = "home"; else if (state.screen === "history-detail") state.screen = "history"; else if (state.screen === "settings") state.screen = state.settingsOrigin; else if (state.screen === "casting") state.screen = "question"; else if (state.screen === "result") state.screen = "question"; else if (state.screen === "loading" || state.screen === "explanation") state.screen = "result"; render(); } function beginAiExplanation() { state.modal = null; state.screen = "loading"; state.explanationSource = "ai"; render(); window.setTimeout(() => { if (state.screen !== "loading") return; state.explanationGenerated = true; attachExplanationToCurrentSession(); state.screen = "explanation"; render(); }, 900); } document.addEventListener("input", (event) => { if (!event.target.matches("[data-question-input]")) return; state.question = event.target.value; document.querySelector("[data-char-count]").textContent = `${state.question.length}/120`; const startButton = document.querySelector("[data-action='start-casting']"); if (startButton) startButton.disabled = !state.question.trim(); }); document.addEventListener("change", (event) => { if (event.target.matches("[data-action='consent-toggle']")) { state.consent = event.target.checked; const confirm = document.querySelector("[data-action='confirm-ai']"); if (confirm) confirm.disabled = !state.consent; } else if (event.target.matches("[data-action='auto-save-toggle']")) { state.autoSaveHistory = event.target.checked; render({ preserveFocus: true }); document.querySelector("[data-action='auto-save-toggle']")?.focus(); } else if (event.target.matches("[data-action='save-question-setting']")) { state.saveQuestion = event.target.checked; } else if (event.target.matches("[data-action='save-explanation-setting']")) { state.saveExplanation = event.target.checked; } else if (event.target.matches("[data-action='save-action-setting']")) { state.saveAction = event.target.checked; } }); document.addEventListener("click", (event) => { const actionTarget = event.target.closest("[data-action]"); if (!actionTarget) return; const action = actionTarget.dataset.action; if (action === "dismiss-modal" && event.target.closest("[data-modal-sheet]")) return; if (action === "back") goBack(); else if (action === "begin") { state.questionOrigin = state.screen; state.screen = "question"; render(); } else if (action === "open-history") { state.screen = "history"; render(); } else if (action === "open-settings") { state.settingsOrigin = state.screen === "settings" ? "home" : state.screen; state.screen = "settings"; render(); } else if (action === "open-history-detail") { state.selectedRecordId = actionTarget.dataset.recordId; state.screen = "history-detail"; render(); } else if (action === "skip-question") { prepareNewSession(); state.question = "未记录具体问题(仅作自我观察)"; state.lines = []; state.coins = [null, null, null]; state.screen = "casting"; render(); } else if (action === "start-casting") { if (!state.question.trim()) return; prepareNewSession(); state.lines = []; state.coins = [null, null, null]; state.screen = "casting"; render(); } else if (action === "toggle-coin") { const index = Number(actionTarget.dataset.index); const current = state.coins[index]; state.coins[index] = current === null || current === 3 ? 2 : 3; render({ preserveFocus: true }); document.querySelector(`[data-action='toggle-coin'][data-index='${index}']`)?.focus(); } else if (action === "confirm-line") { if (!state.coins.every((value) => value !== null)) return; state.lines.push(state.coins.reduce((total, value) => total + value, 0)); state.coins = [null, null, null]; if (state.lines.length === 6) { if (state.autoSaveHistory) persistCurrentSession("automatic"); state.screen = "result"; } render(); } else if (action === "undo-line") { state.lines.pop(); state.coins = [null, null, null]; render(); showToast("已撤销上一爻"); } else if (action === "open-interpretation") { state.modal = "interpretation"; renderModal(); } else if (action === "choose-ai") { state.modal = "consent"; state.consent = false; renderModal(); } else if (action === "choose-local") { state.modal = null; state.explanationSource = "local"; state.explanationGenerated = true; attachExplanationToCurrentSession(); state.screen = "explanation"; render(); } else if (action === "confirm-ai") { if (!state.consent) return; beginAiExplanation(); } else if (action === "retry-ai") { state.aiError = false; state.modal = "consent"; state.consent = false; renderModal(); } else if (action === "save-current") { persistCurrentSession(); render(); showToast("本次记录已保存到本机"); } else if (action === "remove-current-record") { if (!state.saved) return; state.selectedRecordId = state.currentRecordId; state.deleteReturnScreen = state.screen; state.modal = "delete"; renderModal(); } else if (action === "delete-record") { state.deleteReturnScreen = "history"; state.modal = "delete"; renderModal(); } else if (action === "confirm-delete") { const deletedCurrentSession = state.selectedRecordId === state.currentRecordId; state.historyRecords = state.historyRecords.filter((record) => record.id !== state.selectedRecordId); state.selectedRecordId = null; state.modal = null; if (deletedCurrentSession) { state.saved = false; state.currentSessionOptedOut = true; state.currentSaveMode = null; } state.screen = state.deleteReturnScreen; render(); showToast("记录已从本机删除"); } else if (action === "open-clear-all") { if (!state.historyRecords.length) return; state.modal = "clear-all"; renderModal(); } else if (action === "confirm-clear-all") { const includedCurrentSession = Boolean(currentSessionRecord()); state.historyRecords = []; state.selectedRecordId = null; state.modal = null; if (includedCurrentSession) { state.saved = false; state.currentSessionOptedOut = true; state.currentSaveMode = null; } render(); showToast("已清空全部本机记录"); } else if (action === "restart") { state.question = ""; state.lines = []; state.coins = [null, null, null]; prepareNewSession(); state.questionOrigin = state.historyRecords.length ? "home" : "welcome"; state.screen = "question"; render(); } else if (action === "back-to-result") { state.screen = "result"; render(); } else if (action === "open-method") { state.modal = "method"; renderModal(); } else if (action === "close-modal" || action === "dismiss-modal") { closeModal(); } }); document.addEventListener("keydown", (event) => { if (event.key === "Escape" && state.modal) closeModal(); }); function seedPreview() { const view = new URLSearchParams(window.location.search).get("view"); if (!view || view === "welcome") return; if (view === "question") { state.screen = "question"; state.question = SAMPLE_QUESTION; return; } if (["home", "home-empty", "history", "history-empty", "history-detail", "delete", "settings", "settings-off", "clear-all"].includes(view)) { state.historyRecords = view === "home-empty" || view === "history-empty" ? [] : sampleHistory(); state.screen = view.startsWith("home") ? "home" : view === "history-detail" || view === "delete" ? "history-detail" : view === "settings" || view === "settings-off" || view === "clear-all" ? "settings" : "history"; state.selectedRecordId = state.historyRecords[0]?.id ?? null; if (view === "settings-off") state.autoSaveHistory = false; if (view === "delete") state.modal = "delete"; if (view === "clear-all") state.modal = "clear-all"; return; } state.question = SAMPLE_QUESTION; if (view === "casting") { state.screen = "casting"; state.lines = [9, 8]; state.coins = [2, 3, null]; return; } state.lines = view === "static" ? [7, 7, 7, 7, 7, 7] : [...SAMPLE_LINES]; state.screen = "result"; if (view === "result-not-saved") state.autoSaveHistory = false; if (state.autoSaveHistory) persistCurrentSession("automatic"); if (view === "consent") state.modal = "consent"; else if (view === "explanation") { state.screen = "explanation"; state.explanationSource = "ai"; state.explanationGenerated = true; attachExplanationToCurrentSession(); } else if (view === "local") { state.screen = "explanation"; state.explanationSource = "local"; state.explanationGenerated = true; attachExplanationToCurrentSession(); } else if (view === "error") { state.aiError = true; } } seedPreview(); render(); })();