refactor: establish standalone application boundary

This commit is contained in:
leefer
2026-08-03 21:42:25 +08:00
parent cc5fb8d73e
commit e1e76cd51e
324 changed files with 63090 additions and 44743 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+255
View File
@@ -0,0 +1,255 @@
<section id="heavenView" class="workspace-view member-feature-view heaven-shell wt">
<div class="member-gate" hidden><div class="member-gate-icon"><i data-lucide="lock-keyhole"></i></div><div><strong>问天仅对会员开放</strong><span>开通会员后可使用观势、观气、观心及平台解读。会员状态可从顶部账号标识进入。</span></div></div>
<header class="wt-head">
<div class="wt-title-line">
<h1 class="wt-serif">问 天</h1>
<span id="heavenDataDate">--</span>
</div>
<div class="verse wt-serif">观天之道 · 执天之行</div>
<nav class="wt-tabs" aria-label="问天模块">
<button class="wt-tab wt-serif on" type="button" data-heaven-panel="trend" aria-current="page">观势<small>三才六爻 · 量化成卦</small></button>
<button class="wt-tab wt-serif" type="button" data-heaven-panel="fortune">观气<small>五运六气 · 日辰生克</small></button>
<button class="wt-tab wt-serif" type="button" data-heaven-panel="heart">观心<small>静心占卜 · 第一念</small></button>
</nav>
</header>
<p class="heaven-proverb wt-serif">遇事不决可问春风,春风不语即随本心</p>
<div id="heavenNotice" class="inline-notice" role="status" hidden></div>
<section id="heavenTrendPanel" class="heaven-panel active-heaven-panel">
<div class="heaven-controls">
<div class="heaven-stock-query">
<label class="form-field" for="heavenStockInput"><span>股票代码或名称</span><input id="heavenStockInput" type="text" inputmode="text" maxlength="30" autocomplete="off" placeholder="例如 600000 或 浦发银行"></label>
<div id="heavenStockIdentity" class="heaven-stock-identity" aria-live="polite" hidden>
<span>当前标的:</span>
<strong id="heavenStockName">--</strong>
<span id="heavenStockTaxonomy">申万二级 ·</span>
<strong id="heavenStockSector">--</strong>
</div>
</div>
<div class="heaven-trend-actions">
<button id="loadHeavenSelectionButton" class="button" type="button">载入</button>
<button id="historyTrendButton" class="button" type="button">历史记录</button>
<button id="interpretTrendButton" class="button primary" type="button" disabled>解势</button>
</div>
</div>
<div class="cast-hint wt-serif">载入,以指数为天、行业为人、个股为地,六爻皆由行情量化而成</div>
<div class="wt-stage">
<div class="stars" id="stars" aria-hidden="true"></div>
<svg class="bagua" id="baguaSvg" width="560" height="560" viewBox="0 0 300 300" aria-hidden="true"></svg>
<div id="heavenTrendEmpty" class="wt-empty" role="status">
<div class="big wt-serif">三才六爻</div>
<p class="wt-serif">指数外显为上爻 · 内核为五爻 · 行业外显为四爻 · 内核为三爻 · 个股外显为二爻 · 内核为初爻<br>六爻皆由行情量化而成 —— 输入标的,点「载入」成卦</p>
</div>
<div class="heaven-trend-layout stage-in" hidden>
<section class="hexagram-board">
<div class="hexagram-heading">
<div><span class="metric-label">三才六爻</span><h3 id="marketHexagramName">--</h3></div>
<div class="hexagram-change"><span>动而之卦</span><strong id="marketTransformedName">--</strong></div>
</div>
<div id="marketHexagramLines" class="hexagram-lines"></div>
<p id="marketHexagramText" class="hexagram-text">--</p>
<p id="marketMovementSummary" class="market-movement-summary">--</p>
</section>
<section class="trend-reading-panel">
<div class="trend-score-line">
<div><span class="metric-label">势值</span><strong id="heavenMomentumScore">--</strong></div>
<span id="heavenMomentumLabel">--</span>
</div>
<div class="trend-score-meter" role="meter" aria-label="势值" aria-valuemin="-100" aria-valuemax="100">
<div class="trend-score-track"><i id="heavenMomentumNeedle"></i></div>
<div class="trend-score-marks"><span>-100 · 势衰</span><span>0</span><span>+100 · 势盛</span></div>
</div>
<div id="threeTalentReadings" class="three-talent-readings"></div>
<div class="heaven-hex-transition" aria-label="本卦与之卦">
<figure class="compact-hex-figure">
<div id="heavenOriginalHexLines" class="compact-hex-lines" aria-hidden="true"></div>
<figcaption><strong>本卦 · <b id="heavenOriginalHexName">--</b></strong><small id="heavenOriginalHexDetail">--</small></figcaption>
</figure>
<div class="compact-hex-change"><i aria-hidden="true"></i><span>动而之卦</span></div>
<figure class="compact-hex-figure">
<div id="heavenChangedHexLines" class="compact-hex-lines" aria-hidden="true"></div>
<figcaption><strong>之卦 · <b id="heavenChangedHexName">--</b></strong><small id="heavenChangedHexDetail">--</small></figcaption>
</figure>
</div>
</section>
</div>
</div>
<details id="heavenCalibrationPanel" class="heaven-calibration-panel" hidden>
<summary class="heaven-calibration-heading">
<div><span class="metric-label">量化数据安全门</span><h3 id="heavenCalibrationTitle">六爻数据校验</h3></div>
<div class="heaven-calibration-summary">
<span><i class="status-dot passed"></i>通过</span>
<span><i class="status-dot failed"></i>未通过</span>
<span><i class="status-dot manual"></i>用户补录</span>
<strong id="heavenCalibrationStatus">等待载入</strong>
<b class="fold-label">展开</b>
</div>
</summary>
<div class="calibration-body">
<p>系统只接收客观行情数据,所有补录仍按原量化公式计算阴阳与动爻。</p>
<form id="heavenCalibrationForm">
<div id="heavenLineChecks" class="heaven-line-checks" aria-live="polite"></div>
<label class="form-field calibration-note-field"><span>补录说明</span><input id="heavenCalibrationNote" type="text" maxlength="200" placeholder="可选:记录数据来源或补录原因"></label>
<div class="dialog-actions heaven-calibration-actions">
<button id="resetHeavenCalibrationButton" class="button" type="button">恢复自动数据</button>
<button id="applyHeavenCalibrationButton" class="button primary" type="submit">重新校验并成卦</button>
</div>
</form>
</div>
</details>
</section>
<section id="heavenFortunePanel" class="heaven-panel">
<div class="fortune-heading">
<div class="fortune-calendar-heading"><span id="fortuneLunarDate" class="metric-label">--</span><h3 id="fortunePillars" class="wt-serif">--</h3></div>
<div class="fortune-heading-actions">
<label class="qi-time-field"><span>观测日期</span><input id="qiObservationDate" type="date"></label>
<button id="historyFortuneButton" class="button" type="button">历史记录</button>
<button id="interpretFortuneButton" class="button primary" type="button" disabled>解运</button>
</div>
</div>
<div class="fortune-stage">
<div class="stars" id="fortuneStars" aria-hidden="true"></div>
<svg class="bagua fortune-bagua" id="fortuneBagua" width="560" height="560" viewBox="0 0 300 300" aria-hidden="true"></svg>
<section class="qi-climate-panel">
<span class="qi-section-mark">壹 · 天</span>
<p class="qi-climate-caption wt-serif">今日气候</p>
<h3 id="qiClimateKeyword" class="wt-serif">气机待察</h3>
<strong id="qiClimateTone" class="wt-serif">--</strong>
<p id="humanFieldSummary" class="human-field-summary">--</p>
</section>
<aside class="fortune-basics">
<section class="qi-framework-panel">
<div class="workspace-heading"><div><span>贰 · 气</span><h3 class="wt-serif">三层气机</h3></div><button class="text-fold-button" type="button" data-fold-target="qiFrameworkLayers">收起</button></div>
<p id="qiFrameworkPrinciple" class="qi-framework-principle">--</p>
<div id="qiFrameworkLayers" class="qi-framework-layers"></div>
</section>
<section class="personal-fortune-panel">
<div class="workspace-heading"><div><span>叁 · 人</span><h3 class="wt-serif">个人合参</h3></div></div>
<div id="personalProfileEmpty" class="personal-profile-empty"><span>当前账号尚未设置个人命理资料</span></div>
<div id="personalFortuneResult" class="personal-fortune-result" hidden></div>
</section>
</aside>
</div>
<details id="fortuneSectorCatalog" class="fortune-sector-catalog">
<summary>
<span><small>五行取象</small><strong class="wt-serif">五行对应行业</strong></span>
<span class="fortune-sector-summary-hint">展开查看全部行业 <i aria-hidden="true"></i></span>
</summary>
<div id="fortuneSectorGroups" class="fortune-sector-groups"></div>
</details>
<p id="fortuneNotice" class="heaven-footnote"></p>
<div class="wentian-legacy-hooks" hidden aria-hidden="true">
<canvas id="qiFieldCanvas"></canvas>
<div id="heavenIndexStrip"></div><div id="heavenTrendEvidence"></div>
<div id="fortuneMetrics"></div><div id="fivePhaseBalance"></div>
<div id="humanEmotionList"></div><div id="humanBiasList"></div><div id="humanOperation"></div><div id="humanBalanceActions"></div>
<div id="phaseSectorTitle"></div><div id="phaseSectorContext"></div>
<div id="qiUseMap"><div id="qiUseSources"></div><svg id="qiUseConnections"></svg><div id="phaseSectorList"></div></div>
<button id="openPersonalSettingsButton" type="button"></button>
<details id="sectorPhaseManager"><summary>归类管理</summary><form id="sectorPhaseForm"><input id="sectorPhaseName"><select id="sectorPhaseElement"><option value="木"></option></select><button type="submit">保存</button></form><div id="sectorPhaseOverrides"></div></details>
</div>
</section>
<section id="heavenHeartPanel" class="heaven-panel">
<div class="heart-journey" aria-label="观心进程">
<span class="active" data-heart-step="intro"><i></i>静心</span><b></b>
<span data-heart-step="breathing"><i></i>呼吸</span><b></b>
<span data-heart-step="casting"><i></i>起卦</span><b></b>
<span data-heart-step="reveal"><i></i>察念</span><b></b>
<span data-heart-step="interpretation"><i></i>解卦</span>
</div>
<div class="heart-stage-shell">
<canvas id="heartDustCanvas" hidden></canvas><div id="heartLamp" hidden></div><div id="heartRitualCurtain" hidden></div><div id="heartLineTexts" hidden></div>
<div class="stars" id="heartStars" aria-hidden="true"></div>
<svg class="bagua heart-bagua" id="heartBagua" width="560" height="560" viewBox="0 0 300 300" aria-hidden="true"></svg>
<div id="heartWhispers" class="heart-whispers" aria-hidden="true"></div>
<div class="heart-toolbar-controls">
<button id="historyHeartButton" class="button" type="button">历史记录</button>
<button id="heartSoundToggle" class="button" type="button" aria-pressed="false">静音</button>
</div>
<div id="heartIntro" class="heart-stage active-heart-stage">
<div class="heart-stage-inner">
<span class="heart-stage-index heart-rise" data-heart-delay="0">观心 · 一</span>
<h3 class="heart-rise wt-serif" data-heart-delay="420">把所问之事留在心里</h3>
<div class="heart-guidance heart-rise" data-heart-delay="900">
<p>只问一事,不必说出来。</p>
<p>心里默念它发生的对象与时间。</p>
<p>不求一个喜欢的答案,只看自己真正担心什么。</p>
</div>
<blockquote class="heart-motto heart-rise wt-serif" data-heart-delay="1500">遇事不决可问春风,春风不语即随本心</blockquote>
<button id="startBreathingButton" class="button primary heart-rise" data-heart-delay="2200" type="button">开始静心</button>
</div>
</div>
<div id="heartBreathing" class="heart-stage">
<button class="button heart-return-button" type="button" data-heart-return>← 返回</button>
<div class="heart-stage-inner breathing-stage">
<span class="heart-stage-index">观心 · 二</span>
<div id="breathingScene" class="breathing-scene" data-phase="prepare">
<div class="heart-breath-ripple" aria-hidden="true"><span></span><span></span><span></span><i></i></div>
<b id="breathingPhase" class="breathing-phase" role="status" aria-live="polite"></b>
</div>
<h3 id="breathingPrompt" class="wt-serif">放松片刻,准备呼吸</h3>
<div class="heart-incense" aria-hidden="true"><i id="heartIncenseEmber"></i></div>
<button id="beginCastingButton" class="button primary" type="button" disabled>静心完成,开始起卦</button>
</div>
</div>
<div id="heartCasting" class="heart-stage">
<button class="button heart-return-button" type="button" data-heart-return>← 返回</button>
<div class="heart-casting-layout">
<section class="heart-hexagram-shell">
<div class="workspace-heading"><h3 class="wt-serif">从初爻起</h3><span id="castingProgress">0 / 6</span></div>
<div id="heartCastingLines" class="hexagram-lines ritual-lines"></div>
</section>
<section class="casting-action-panel">
<span class="heart-stage-index">观心 · 三</span>
<div id="coinResult" class="heart-coins" aria-label="三枚铜钱">
<div class="heart-coin" data-coin-index="0"><div class="heart-coin-inner"><span class="heart-coin-face front" aria-label="字面"><b class="coin-glyph coin-glyph-top"></b><b class="coin-glyph coin-glyph-right"></b><b class="coin-glyph coin-glyph-bottom"></b><b class="coin-glyph coin-glyph-left"></b><i class="coin-hole"></i></span><span class="heart-coin-face back" aria-label="背面"><b class="coin-glyph coin-glyph-top"></b><b class="coin-glyph coin-glyph-bottom"></b><i class="coin-hole"></i></span></div><i class="heart-coin-ring"></i></div>
<div class="heart-coin" data-coin-index="1"><div class="heart-coin-inner"><span class="heart-coin-face front" aria-label="字面"><b class="coin-glyph coin-glyph-top"></b><b class="coin-glyph coin-glyph-right"></b><b class="coin-glyph coin-glyph-bottom"></b><b class="coin-glyph coin-glyph-left"></b><i class="coin-hole"></i></span><span class="heart-coin-face back" aria-label="背面"><b class="coin-glyph coin-glyph-top"></b><b class="coin-glyph coin-glyph-bottom"></b><i class="coin-hole"></i></span></div><i class="heart-coin-ring"></i></div>
<div class="heart-coin" data-coin-index="2"><div class="heart-coin-inner"><span class="heart-coin-face front" aria-label="字面"><b class="coin-glyph coin-glyph-top"></b><b class="coin-glyph coin-glyph-right"></b><b class="coin-glyph coin-glyph-bottom"></b><b class="coin-glyph coin-glyph-left"></b><i class="coin-hole"></i></span><span class="heart-coin-face back" aria-label="背面"><b class="coin-glyph coin-glyph-top"></b><b class="coin-glyph coin-glyph-bottom"></b><i class="coin-hole"></i></span></div><i class="heart-coin-ring"></i></div>
</div>
<h3 id="castingPrompt" class="wt-serif">心中默念所问之事,然后掷出初爻</h3>
<button id="tossCoinsButton" class="heart-cast-button" type="button"><i class="heart-hold-charge" aria-hidden="true"></i><span>按住<br>摇初爻</span></button>
</section>
</div>
</div>
<div id="heartReveal" class="heart-stage">
<button class="button heart-return-button" type="button" data-heart-return>← 返回</button>
<div class="heart-reveal-layout">
<section class="hexagram-board heart-reveal-board">
<div class="hexagram-heading">
<div><span class="metric-label">本卦</span><h3 id="heartHexagramName">--</h3></div>
<div class="hexagram-change"><span>之卦</span><strong id="heartTransformedName">--</strong></div>
</div>
<div id="heartHexagramLines" class="hexagram-lines"></div>
<p id="heartHexagramText" class="hexagram-text">--</p>
</section>
<section class="heart-first-thought">
<span class="heart-stage-index">观心 · 四</span>
<h3 class="wt-serif">先不解卦</h3>
<p id="heartFirstThoughtPrompt">看见卦象与爻辞后,心里升起的第一念是什么?</p>
<p>不要修饰,也不必记录。只需看见它。</p>
<button id="interpretHeartButton" class="button primary" type="button">我已察念,开始解卦</button>
</section>
</div>
</div>
<div id="heartInterpretationStage" class="heart-stage">
<div class="heart-interpretation-heading">
<div><span class="heart-stage-index">观心 · 五</span><h3 id="heartReadTitle" class="wt-serif">解卦</h3><small id="heartReadChange">--</small></div>
<div class="heart-read-actions"><button id="viewHeartReadingButton" class="button primary" type="button">查看解卦</button><button id="restartHeartButton" class="button" type="button">重新观心</button></div>
</div>
<p id="heartReadGuaci" class="heart-read-guaci">--</p>
<div class="heart-read-layout">
<div id="heartReadLines" class="heart-read-lines"></div>
<div id="heartReadTexts" class="heart-read-texts"></div>
</div>
<p class="heart-read-motto wt-serif">一念既察,卦只是镜。</p>
</div>
</div>
<p class="heaven-footnote heart-footnote">观心用于观察念头与执着,不用于替代交易计划或预测涨跌。</p>
</section>
</section>
+137 -2
View File
@@ -1,9 +1,95 @@
const HEART_BREATH_INHALE_MS = 3_000;
const HEART_BREATH_HOLD_MS = 2_000;
const HEART_BREATH_EXHALE_MS = 4_000;
const HEART_BREATH_PREPARE_MS = 1_000;
const HEART_BREATH_CYCLE_MS = HEART_BREATH_INHALE_MS + HEART_BREATH_HOLD_MS + HEART_BREATH_EXHALE_MS;
const HEART_BREATH_ACTIVE_MS = HEART_BREATH_CYCLE_MS * 5;
const HEART_BREATH_TOTAL_MS = HEART_BREATH_PREPARE_MS + HEART_BREATH_ACTIVE_MS;
let qiFieldAnimationFrame = 0;
let qiFieldSoloElement = "";
let heavenPerformanceToken = 0;
let heavenReadingAnimation = null;
let heartHoldTimer = null;
let heartHoldTriggered = false;
let heartHoldStartedAt = 0;
let heartHoldAnimationFrame = 0;
let heartCastingBusy = false;
let heartDustAnimationFrame = 0;
let heartDustParticles = [];
let heartIncenseAnimation = null;
const heartCoinRotations = [0, 0, 0];
let heavenResizeTimer = null;
const heartSound = {
enabled: false,
context: null,
ensure() {
if (!this.context) {
const AudioContextClass = window.AudioContext || window.webkitAudioContext;
if (!AudioContextClass) return null;
this.context = new AudioContextClass();
}
if (this.context.state === "suspended") this.context.resume();
return this.context;
},
tone(frequency, duration, gain, type = "sine", delay = 0) {
if (!this.enabled) return;
const context = this.ensure();
if (!context) return;
const start = context.currentTime + delay;
const oscillator = context.createOscillator();
const volume = context.createGain();
oscillator.type = type;
oscillator.frequency.value = frequency;
volume.gain.setValueAtTime(0.0001, start);
volume.gain.linearRampToValueAtTime(gain, start + 0.015);
volume.gain.exponentialRampToValueAtTime(0.0001, start + duration);
oscillator.connect(volume).connect(context.destination);
oscillator.start(start);
oscillator.stop(start + duration + 0.05);
},
chime(frequency = 640) {
this.tone(frequency, 4.8, 0.12);
this.tone(frequency * 2.02, 3.6, 0.045);
this.tone(frequency * 3.96, 2.2, 0.018);
},
coin(delay = 0) {
this.tone(2350 + Math.random() * 260, 0.28, 0.055, "triangle", delay);
this.tone(3250 + Math.random() * 260, 0.18, 0.025, "triangle", delay + 0.01);
},
};
const HEART_WHISPERS = [
["应无所住,而生其心", 10, 12, 0],
["不是风动,不是幡动,仁者心动", 89, 8, 1],
["菩提本无树,明镜亦非台", 16, 52, 2],
["本来无一物,何处惹尘埃", 84, 54, 3],
["心外无物,心外无理", 22, 18, 4],
["知行合一", 78, 30, 5],
["此心光明,亦复何言", 90, 60, 6],
];
window.addEventListener("resize", () => {
clearTimeout(heavenResizeTimer);
heavenResizeTimer = setTimeout(() => {
if (state.activeView !== "heavenView") return;
if (state.heavenPanel === "fortune" && state.heavenSetup?.field) {
renderQiFieldCanvas(state.heavenSetup.field.balance || [], { intro: false });
drawQiUseConnections(false);
}
if (state.heavenPanel === "heart") startHeartDust();
}, 120);
});
window.XiaobaiPageModules.register("heaven", ["heavenView"], {
bind: bindHeavenEvents,
enter: ["loadHeaven"],
leave: ["stopHeaven"],
});
/* PRESERVATION-SOURCE-BEGIN app.js:4828-6667 */
async function loadHeavenSetup(force = false, sector = "", stockCode = "") {
const calendarDate = document.querySelector("#qiObservationDate")?.value || elements.tradeDate.value;
const requestedDate = calendarDate.replaceAll("-", "");
@@ -1827,4 +1913,53 @@ function capitalize(value) {
const LINE_POSITIONS_CLIENT = ["初爻", "二爻", "三爻", "四爻", "五爻", "上爻"];
/* PRESERVATION-SOURCE-END app.js:4828-6667 */
function bindHeavenEvents() {
document.querySelectorAll("[data-heaven-panel]").forEach((button) => {
button.addEventListener("click", () => selectHeavenPanel(button.dataset.heavenPanel, true));
});
document.querySelector("#loadHeavenSelectionButton").addEventListener("click", loadHeavenSelection);
document.querySelector("#heavenCalibrationForm").addEventListener("submit", applyHeavenCalibration);
document.querySelector("#resetHeavenCalibrationButton").addEventListener("click", resetHeavenCalibration);
document.querySelector("#heavenStockInput").addEventListener("keydown", (event) => {
if (event.key === "Enter") {
event.preventDefault();
loadHeavenSelection();
}
});
document.querySelector("#interpretTrendButton").addEventListener("click", () => interpretHeaven("trend"));
document.querySelector("#interpretFortuneButton").addEventListener("click", () => interpretHeaven("fortune"));
document.querySelector("#historyTrendButton").addEventListener("click", () => openHeavenHistory("trend"));
document.querySelector("#historyFortuneButton").addEventListener("click", () => openHeavenHistory("fortune"));
document.querySelector("#qiObservationDate").addEventListener("change", () => {
state.personalField = null;
state.heavenManualData = null;
state.heavenInterpretations.fortune = "";
loadHeavenSetup(
true,
"",
document.querySelector("#heavenStockInput").value.trim(),
);
});
document.querySelector("#openPersonalSettingsButton").addEventListener("click", () => openSettings("profile"));
document.querySelector("#sectorPhaseForm").addEventListener("submit", saveSectorPhaseOverride);
document.querySelector("#startBreathingButton").addEventListener("click", startHeartBreathing);
document.querySelector("#beginCastingButton").addEventListener("click", beginHeartCasting);
document.querySelector("#heartSoundToggle").addEventListener("click", toggleHeartSound);
document.querySelector("#historyHeartButton").addEventListener("click", () => openHeavenHistory("heart"));
initializeHeartCoinHold();
initializeHeartLineInspection();
document.querySelector("#interpretHeartButton").addEventListener("click", () => interpretHeaven("heart"));
document.querySelector("#viewHeartReadingButton").addEventListener("click", () => openHeavenReading("heart"));
document.querySelector("#restartHeartButton").addEventListener("click", resetHeartRitual);
document.querySelector("#closeHeavenReadingDialog").addEventListener("click", () => elements.heavenReadingDialog.close());
elements.heavenReadingDialog.addEventListener("close", stopHeavenReadingAnimation);
document.querySelectorAll("[data-heaven-reading-tab]").forEach((button) => {
button.addEventListener("click", () => selectHeavenReadingTab(button.dataset.heavenReadingTab));
});
document.querySelector("#heavenReadingHistoryList").addEventListener("click", handleHeavenHistorySelection);
document.querySelector("#heavenReadingHistoryDetail").addEventListener("click", handleHeavenHistoryAction);
document.querySelectorAll("[data-heart-return]").forEach((button) => {
button.addEventListener("click", resetHeartRitual);
});
}